From 2b7f48e8a8ef30f467760bd98ee6b909c8d89874 Mon Sep 17 00:00:00 2001 From: Vladimir Paramuzov Date: Tue, 26 Nov 2024 15:15:24 +0400 Subject: [PATCH 01/24] [GPU][TRANSFORMATIONS] Rope for flux (#27719) ### Details: - Enabled ROPE op fusion for flux.1 model on GPU to improve the performance - New mode for RoPE is added which enables the case when coordinates and angles are interleaved and sin/cos tables are separate inputs. It's activated when `is_interleaved=true` and `output_trans0213=false` Signed-off-by: Vladimir Paramuzov --- .../ov_ops/rotary_positional_embeddings.hpp | 15 +- .../fuse_rotary_positional_embeddings.hpp | 8 + .../ov_ops/rotary_positional_embeddings.cpp | 3 +- .../fuse_rotary_positional_embeddings.cpp | 89 +++++++++ .../fuse_rotary_positional_embeddings.cpp | 170 +++++++++++++++++- src/plugins/intel_cpu/src/nodes/rope.cpp | 2 +- .../transformation_pipeline.cpp | 1 + .../include/intel_gpu/primitives/rope.hpp | 4 + .../intel_gpu/src/graph/impls/ocl/rope.cpp | 3 +- src/plugins/intel_gpu/src/graph/rope.cpp | 3 +- .../kernel_selector/cl_kernels/rope_ref.cl | 39 ++++ .../kernels/rope/rope_kernel_base.cpp | 2 + .../kernels/rope/rope_kernel_base.h | 1 + src/plugins/intel_gpu/src/plugin/ops/rope.cpp | 2 + .../subgraph_tests/rotary_pos_emb.cpp | 5 + .../include/subgraph_tests/rotary_pos_emb.hpp | 7 + .../subgraph/rotary_pos_emb.hpp | 14 ++ .../src/subgraph/rotary_pos_emb.cpp | 80 +++++++++ 18 files changed, 436 insertions(+), 12 deletions(-) diff --git a/src/common/transformations/include/ov_ops/rotary_positional_embeddings.hpp b/src/common/transformations/include/ov_ops/rotary_positional_embeddings.hpp index dcb9aef187d2d9..08c1aa8e3f5ad8 100644 --- a/src/common/transformations/include/ov_ops/rotary_positional_embeddings.hpp +++ b/src/common/transformations/include/ov_ops/rotary_positional_embeddings.hpp @@ -23,13 +23,14 @@ class TRANSFORMATIONS_API RoPE : public Op { struct Config { size_t slice_start = 0; // slice inner-most dimensions of input size_t slice_stop = 0; - bool input_trans0213 = false; // transpose input dim 1&2 - bool is_interleaved = false; // interleaved mode, implies trans0213 happens after RoPE - size_t rotary_ndims = 0; // dimensions to be embedded (d in the description) - bool is_chatglm = false; // chatglm is special which overrides other setting - bool support_2d_rope = false; // 2d rope mode, Support 2 dimentional rope which is independant of batch and - // each head. change input order to [batch, head_cnt, 4608] to support 2d rope - bool is_qwen = false; // Qwen is special which overrides other setting + bool input_trans0213 = false; // transpose input dim 1&2 + bool output_trans0213 = false; // implies trans0213 happens after RoPE + bool is_interleaved = false; // coordinates are interleaved + size_t rotary_ndims = 0; // dimensions to be embedded (d in the description) + bool is_chatglm = false; // chatglm is special which overrides other setting + bool support_2d_rope = false; // 2d rope mode, Support 2 dimentional rope which is independant of batch and + // each head. change input order to [batch, head_cnt, 4608] to support 2d rope + bool is_qwen = false; // Qwen is special which overrides other setting size_t head_cnt = 0; size_t head_size = 0; int gather_position_arg_id = diff --git a/src/common/transformations/include/transformations/common_optimizations/fuse_rotary_positional_embeddings.hpp b/src/common/transformations/include/transformations/common_optimizations/fuse_rotary_positional_embeddings.hpp index eb1c92bcf9607f..3449151ab93ac5 100644 --- a/src/common/transformations/include/transformations/common_optimizations/fuse_rotary_positional_embeddings.hpp +++ b/src/common/transformations/include/transformations/common_optimizations/fuse_rotary_positional_embeddings.hpp @@ -12,6 +12,7 @@ namespace pass { class TRANSFORMATIONS_API RoPEFusion; class TRANSFORMATIONS_API RoPEFusionGPTNEOX; +class TRANSFORMATIONS_API RoPEFusionFlux; class TRANSFORMATIONS_API RoPEFusionGPTJ; class TRANSFORMATIONS_API RoPEFusionChatGLM; class TRANSFORMATIONS_API RoPEFusionQwen; @@ -29,6 +30,12 @@ class ov::pass::RoPEFusionGPTNEOX : public ov::pass::MatcherPass { RoPEFusionGPTNEOX(); }; +class ov::pass::RoPEFusionFlux : public ov::pass::MatcherPass { +public: + OPENVINO_RTTI("RoPEFusionFlux", "0"); + RoPEFusionFlux(); +}; + class ov::pass::RoPEFusionGPTJ : public ov::pass::MatcherPass { public: OPENVINO_RTTI("RoPEFusionGPTJ", "0"); @@ -85,6 +92,7 @@ class ov::pass::RoPEFusion : public ov::pass::GraphRewrite { public: OPENVINO_RTTI("RoPEFusion", "0"); RoPEFusion(bool support_2d_rope = false) { + add_matcher(); add_matcher(); add_matcher(); // optional heads & tails are fused in separate matcher pass, diff --git a/src/common/transformations/src/ov_ops/rotary_positional_embeddings.cpp b/src/common/transformations/src/ov_ops/rotary_positional_embeddings.cpp index 3e75e2b88df266..88a42a7f456db1 100644 --- a/src/common/transformations/src/ov_ops/rotary_positional_embeddings.cpp +++ b/src/common/transformations/src/ov_ops/rotary_positional_embeddings.cpp @@ -76,7 +76,7 @@ void RoPE::validate_and_infer_types() { if (m_config.input_trans0213) { // transpose 0213 ([B,L,H,S]=>[B,H,L,S]) happens before RoPE std::swap(input_pshape[2], input_pshape[1]); - } else if (m_config.is_interleaved) { + } else if (m_config.output_trans0213) { // transpose 0213 ([B,L,H,S]=>[B,H,L,S]) happens after RoPE std::swap(input_pshape[2], input_pshape[1]); } @@ -90,6 +90,7 @@ bool RoPE::visit_attributes(ov::AttributeVisitor& visitor) { visitor.on_attribute("slice_start", m_config.slice_start); visitor.on_attribute("slice_stop", m_config.slice_stop); visitor.on_attribute("input_trans0213", m_config.input_trans0213); + visitor.on_attribute("output_trans0213", m_config.output_trans0213); visitor.on_attribute("is_interleaved", m_config.is_interleaved); visitor.on_attribute("rotary_ndims", m_config.rotary_ndims); visitor.on_attribute("is_chatglm", m_config.is_chatglm); 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 f002e0043a8744..ec49dd7152fed1 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 @@ -23,6 +23,94 @@ using namespace ov::gen_pattern; +ov::pass::RoPEFusionFlux::RoPEFusionFlux() { + MATCHER_SCOPE(RoPEFusionFlux); + // x[?,24,?,128] + // x1 = reshape(x, [?,24,?,64,2]) + // x1_0, x1_1 = split(x1, -1) + // x2 = concat(x1_0, x1_1 * (-1), -1) + // x3 = reshape(x2, [?,24,?,128]) + // y1 = x * t_cos + // y2 = x3 * t_sin + // y = y1 + y2 + auto x = makePattern(ov::Rank(4)); + auto t_cos = makePattern(ov::Rank(4)); + auto t_sin = makePattern(ov::Rank(4)); + + auto num_heads = ov::gen_pattern::Symbol("num_heads"); + auto head_size = ov::gen_pattern::Symbol("head_size"); + + auto x1_target_shape = makeConst({0, num_heads, 0, -1, 2}); + auto x1 = makePattern({x, x1_target_shape}, {{"special_zero", true}}); + auto split = makePattern({x1, -1}, {{"num_splits", 2}}); + split->set_output_size(2); + + // 3 versions of mulitply by -1 depending on transformations execution prior to this pass + auto x1_1_neg_1 = makePattern({split->output(1), -1.0f}, {{"auto_broadcast", "numpy"}}); + + auto squeeze_2 = makePattern({split->output(1), -1}); + auto x1_1_neg_2 = makePattern({squeeze_2, -1.0f}, {{"auto_broadcast", "numpy"}}); + auto unsqueeze_2 = makePattern({x1_1_neg_2, -1}); + + auto x1_1_neg_3 = makePattern({split->output(1), -1.0f}, {{"auto_broadcast", "numpy"}}); + auto squeeze_3 = makePattern({x1_1_neg_3, -1}); + auto unsqueeze_3 = makePattern({squeeze_3, -1}); + + auto x2 = makePattern({x1_1_neg_1 | unsqueeze_2 | unsqueeze_3, split->output(0)}, {{"axis", -1}}); + auto x3_target_shape = makeConst({0, num_heads, 0, head_size}); + auto x3 = makePattern({x2, x3_target_shape}, {{"special_zero", true}}); + + auto y1 = makePattern({x, t_cos}, {{"auto_broadcast", "numpy"}}); + auto y2 = makePattern({x3, t_sin}, {{"auto_broadcast", "numpy"}}); + + auto y = makePattern({y1, y2}, {{"auto_broadcast", "numpy"}}); + auto result = y; + + matcher_pass_callback callback = [OV_CAPTURE_CPY_AND_THIS](ov::pass::pattern::Matcher& m) { + PatternValidator validator(m); + if (!validator) { + return false; + } + + const auto& pattern_map = m.get_pattern_value_map(); + auto root = m.get_match_root(); + + op::internal::RoPE::Config config; + config.head_cnt = static_cast(validator["num_heads"]); + config.head_size = static_cast(validator["head_size"]); + config.rotary_ndims = config.head_size; + config.is_interleaved = true; + config.output_trans0213 = false; + + OutputVector new_args; + new_args.push_back(pattern_map.at(x)); + new_args.push_back(pattern_map.at(t_cos)); + new_args.push_back(pattern_map.at(t_sin)); + + auto old_node = root; + auto new_node = std::make_shared(new_args, config); + new_node->set_friendly_name(old_node->get_friendly_name()); + ov::copy_runtime_info({pattern_map.at(x1).get_node_shared_ptr(), + pattern_map.at(split).get_node_shared_ptr(), + pattern_map.at(x2).get_node_shared_ptr(), + pattern_map.at(x3).get_node_shared_ptr(), + pattern_map.at(y1).get_node_shared_ptr(), + pattern_map.at(y2).get_node_shared_ptr(), + pattern_map.at(result).get_node_shared_ptr()}, + new_node); + + ov::replace_node(old_node, new_node); + + // this new node may match following additional matchers + register_new_node(new_node); + + return true; + }; + + auto m = std::make_shared(result, matcher_name); + this->register_matcher(m, callback); +} + ov::pass::RoPEFusionGPTNEOX::RoPEFusionGPTNEOX() { MATCHER_SCOPE(RoPEFusionGPTNEOX); @@ -373,6 +461,7 @@ ov::pass::RoPEFusionGPTJ::RoPEFusionGPTJ() { OutputVector new_args; config.rotary_ndims = static_cast(validator["ndims"]); + config.output_trans0213 = true; config.is_interleaved = true; // input is [B,L,H,S] diff --git a/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp b/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp index ea928de5c01702..a42e11120d7276 100644 --- a/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp +++ b/src/common/transformations/tests/common_optimizations/fuse_rotary_positional_embeddings.cpp @@ -6,7 +6,9 @@ #include +#include "common_test_utils/graph_comparator.hpp" #include "common_test_utils/ov_test_utils.hpp" +#include "openvino/core/node_vector.hpp" #include "openvino/opsets/opset1.hpp" #include "openvino/opsets/opset3.hpp" #include "ov_ops/rotary_positional_embeddings.hpp" @@ -133,6 +135,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_LLama2_no_gather) { {{"config.slice_start", 0}, {"config.slice_stop", 0}, {"config.input_trans0213", true}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.is_chatglm", false}, {"config.support_2d_rope", false}, @@ -169,6 +172,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_LLama2_with_gather) { {{"config.slice_start", 0}, {"config.slice_stop", 0}, {"config.input_trans0213", true}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.is_chatglm", false}, {"config.support_2d_rope", false}, @@ -308,6 +312,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_GPTNEOX_no_gather) { {{"config.slice_start", 0}, {"config.slice_stop", ndims}, {"config.input_trans0213", true}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.is_chatglm", false}, {"config.support_2d_rope", false}, @@ -343,6 +348,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_GPTNEOX_with_gather) { {{"config.slice_start", 0}, {"config.slice_stop", ndims}, {"config.input_trans0213", true}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.is_chatglm", false}, {"config.support_2d_rope", false}, @@ -459,6 +465,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_GPTJ) { {{"config.slice_start", 0}, {"config.slice_stop", 0}, {"config.input_trans0213", false}, + {"config.output_trans0213", true}, {"config.is_interleaved", true}, {"config.is_chatglm", false}, {"config.support_2d_rope", false}, @@ -568,6 +575,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_chatGML) { {{"config.slice_start", 0}, {"config.slice_stop", 4096}, {"config.input_trans0213", false}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.rotary_ndims", rotary_ndims}, {"config.is_chatglm", true}, @@ -646,6 +654,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_chatGML_Slice) { {{"config.slice_start", 0}, {"config.slice_stop", 4096}, {"config.input_trans0213", false}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.rotary_ndims", rotary_ndims}, {"config.is_chatglm", true}, @@ -728,6 +737,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_GPTJ_Slice) { {{"config.slice_start", 0}, {"config.slice_stop", 0}, {"config.input_trans0213", false}, + {"config.output_trans0213", true}, {"config.is_interleaved", true}, {"config.is_chatglm", false}, {"config.support_2d_rope", false}, @@ -843,6 +853,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_chatGML_2d_rope) { {{"config.slice_start", 0}, {"config.slice_stop", 4096}, {"config.input_trans0213", false}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.rotary_ndims", rotary_ndims}, {"config.is_chatglm", true}, @@ -951,6 +962,7 @@ TEST_F(TransformationTestsF, ConvertToROPE_chatGML_nano_2d_rope) { {{"config.slice_start", 0}, {"config.slice_stop", 2048}, {"config.input_trans0213", false}, + {"config.output_trans0213", false}, {"config.is_interleaved", false}, {"config.rotary_ndims", rotary_ndims}, {"config.is_chatglm", true}, @@ -962,4 +974,160 @@ TEST_F(TransformationTestsF, ConvertToROPE_chatGML_nano_2d_rope) { model_ref = std::make_shared(ov::NodeVector{rope}, ov::ParameterVector{input, cos_sin_cache, position_ids}); } -} \ No newline at end of file +} + +TEST_F(TransformationTestsF, ConvertToROPE_Flux_mul) { + disable_rt_info_check(); + const int batch = 2; + const int num_heads = 32; + const int ndims = 128; + { + auto x = + std::make_shared(ov::element::f32, ov::PartialShape{batch, num_heads, -1, ndims}); + auto t_cos = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + auto t_sin = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + + auto x1_shape = makeConst(ov::element::i64, ov::Shape({5}), {0, num_heads, 0, -1, 2}); + auto x1 = std::make_shared(x, x1_shape, true); + + auto split_axis = makeConst(ov::element::i64, ov::Shape(), {-1}); + auto split = std::make_shared(x1, split_axis, 2); + + auto minus_one = makeConst(ov::element::f32, ov::Shape({}), {-1.0f}); + auto x1_1_neg = std::make_shared(split->output(1), minus_one); + + auto x2 = std::make_shared(ov::OutputVector{x1_1_neg->output(0), split->output(0)}, -1); + + auto x3_shape = makeConst(ov::element::i64, ov::Shape({4}), {0, num_heads, 0, ndims}); + auto x3 = std::make_shared(x2, x3_shape, true); + + auto y1 = std::make_shared(x, t_cos); + auto y2 = std::make_shared(x3, t_sin); + auto y = std::make_shared(y1, y2); + + model = std::make_shared(ov::NodeVector{y}, ov::ParameterVector{x, t_cos, t_sin}); + } + manager.register_pass(true); + { + auto x = + std::make_shared(ov::element::f32, ov::PartialShape{batch, num_heads, -1, ndims}); + auto t_cos = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + auto t_sin = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + ov::op::internal::RoPE::Config config; + config.is_interleaved = true; + config.rotary_ndims = ndims; + config.head_cnt = num_heads; + config.head_size = ndims; + auto rope = std::make_shared(ov::OutputVector{x, t_cos, t_sin}, config); + model_ref = std::make_shared(ov::NodeVector{rope}, ov::ParameterVector{x, t_cos, t_sin}); + } + comparator.enable(FunctionsComparator::ATTRIBUTES); +} + +TEST_F(TransformationTestsF, ConvertToROPE_Flux_squeeze_mul_unsqueeze) { + disable_rt_info_check(); + const int batch = 2; + const int num_heads = 32; + const int ndims = 128; + { + auto x = + std::make_shared(ov::element::f32, ov::PartialShape{batch, num_heads, -1, ndims}); + auto t_cos = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + auto t_sin = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + + auto x1_shape = makeConst(ov::element::i64, ov::Shape({5}), {0, num_heads, 0, -1, 2}); + auto x1 = std::make_shared(x, x1_shape, true); + + auto split_axis = makeConst(ov::element::i64, ov::Shape(), {-1}); + auto split = std::make_shared(x1, split_axis, 2); + + auto squeeze_axis = makeConst(ov::element::i32, ov::Shape({}), {-1}); + auto squeeze = std::make_shared(split->output(1), squeeze_axis); + + auto minus_one = makeConst(ov::element::f32, ov::Shape({}), {-1.0f}); + auto x1_1_neg = std::make_shared(squeeze, minus_one); + + auto unsqueeze_axis = makeConst(ov::element::i32, ov::Shape({}), {-1}); + auto unsqueeze = std::make_shared(x1_1_neg, unsqueeze_axis); + + auto x2 = std::make_shared(ov::OutputVector{unsqueeze->output(0), split->output(0)}, -1); + + auto x3_shape = makeConst(ov::element::i64, ov::Shape({4}), {0, num_heads, 0, ndims}); + auto x3 = std::make_shared(x2, x3_shape, true); + + auto y1 = std::make_shared(x, t_cos); + auto y2 = std::make_shared(x3, t_sin); + auto y = std::make_shared(y1, y2); + + model = std::make_shared(ov::NodeVector{y}, ov::ParameterVector{x, t_cos, t_sin}); + } + manager.register_pass(true); + { + auto x = + std::make_shared(ov::element::f32, ov::PartialShape{batch, num_heads, -1, ndims}); + auto t_cos = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + auto t_sin = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + ov::op::internal::RoPE::Config config; + config.is_interleaved = true; + config.rotary_ndims = ndims; + config.head_cnt = num_heads; + config.head_size = ndims; + auto rope = std::make_shared(ov::OutputVector{x, t_cos, t_sin}, config); + model_ref = std::make_shared(ov::NodeVector{rope}, ov::ParameterVector{x, t_cos, t_sin}); + } + comparator.enable(FunctionsComparator::ATTRIBUTES); +} + +TEST_F(TransformationTestsF, ConvertToROPE_Flux_mul_squeeze_unsqueeze) { + disable_rt_info_check(); + const int batch = 2; + const int num_heads = 32; + const int ndims = 128; + { + auto x = + std::make_shared(ov::element::f32, ov::PartialShape{batch, num_heads, -1, ndims}); + auto t_cos = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + auto t_sin = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + + auto x1_shape = makeConst(ov::element::i64, ov::Shape({5}), {0, num_heads, 0, -1, 2}); + auto x1 = std::make_shared(x, x1_shape, true); + + auto split_axis = makeConst(ov::element::i64, ov::Shape(), {-1}); + auto split = std::make_shared(x1, split_axis, 2); + + auto minus_one = makeConst(ov::element::f32, ov::Shape({}), {-1.0f}); + auto x1_1_neg = std::make_shared(split->output(1), minus_one); + + auto squeeze_axis = makeConst(ov::element::i32, ov::Shape({}), {-1}); + auto squeeze = std::make_shared(x1_1_neg, squeeze_axis); + + auto unsqueeze_axis = makeConst(ov::element::i32, ov::Shape({}), {-1}); + auto unsqueeze = std::make_shared(squeeze, unsqueeze_axis); + + auto x2 = std::make_shared(ov::OutputVector{unsqueeze->output(0), split->output(0)}, -1); + + auto x3_shape = makeConst(ov::element::i64, ov::Shape({4}), {0, num_heads, 0, ndims}); + auto x3 = std::make_shared(x2, x3_shape, true); + + auto y1 = std::make_shared(x, t_cos); + auto y2 = std::make_shared(x3, t_sin); + auto y = std::make_shared(y1, y2); + + model = std::make_shared(ov::NodeVector{y}, ov::ParameterVector{x, t_cos, t_sin}); + } + manager.register_pass(true); + { + auto x = + std::make_shared(ov::element::f32, ov::PartialShape{batch, num_heads, -1, ndims}); + auto t_cos = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + auto t_sin = std::make_shared(ov::element::f32, ov::PartialShape{1, 1, -1, ndims}); + ov::op::internal::RoPE::Config config; + config.is_interleaved = true; + config.rotary_ndims = ndims; + config.head_cnt = num_heads; + config.head_size = ndims; + auto rope = std::make_shared(ov::OutputVector{x, t_cos, t_sin}, config); + model_ref = std::make_shared(ov::NodeVector{rope}, ov::ParameterVector{x, t_cos, t_sin}); + } + comparator.enable(FunctionsComparator::ATTRIBUTES); +} diff --git a/src/plugins/intel_cpu/src/nodes/rope.cpp b/src/plugins/intel_cpu/src/nodes/rope.cpp index f089b67a122beb..73a23c5e7cdcd7 100644 --- a/src/plugins/intel_cpu/src/nodes/rope.cpp +++ b/src/plugins/intel_cpu/src/nodes/rope.cpp @@ -392,7 +392,7 @@ void RoPE::initSupportedPrimitiveDescriptors() { m_executor = std::make_shared>(m_config); rtPrecision = ov::element::f32; } - } else if (m_config.is_interleaved) { + } else if (m_config.is_interleaved && m_config.output_trans0213) { OPENVINO_ASSERT(m_config.input_trans0213 == false); OPENVINO_ASSERT(m_config.slice_start == 0); OPENVINO_ASSERT(m_config.slice_stop == 0); diff --git a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp index 9dd1da2d471e5a..27afb95a73a1e9 100644 --- a/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp +++ b/src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp @@ -842,6 +842,7 @@ void Transformations::PostLpt() { CPU_REGISTER_PASS_X64(postLPTPassManager, ov::pass::RoPEFusion, true); CPU_REGISTER_PASS_ARM64(postLPTPassManager, ov::pass::RoPEFusion, true); + CPU_DISABLE_PASS_COMMON(postLPTPassManager, ov::pass::RoPEFusionFlux); CPU_REGISTER_PASS_X64(postLPTPassManager, CausalMaskPreprocessFusion); #if defined(OPENVINO_ARCH_X86_64) diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/rope.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/rope.hpp index d7933e2180fe6f..a90caaa8a8cb9f 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/primitives/rope.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/rope.hpp @@ -43,6 +43,7 @@ struct rope : public primitive_base { seed = hash_combine(seed, config.input_trans0213); seed = hash_combine(seed, config.is_chatglm); seed = hash_combine(seed, config.support_2d_rope); + seed = hash_combine(seed, config.output_trans0213); seed = hash_combine(seed, config.is_interleaved); seed = hash_combine(seed, config.is_qwen); seed = hash_combine(seed, config.rotary_ndims); @@ -64,6 +65,7 @@ struct rope : public primitive_base { config.input_trans0213 == rhs_casted.config.input_trans0213 && config.is_chatglm == rhs_casted.config.is_chatglm && config.support_2d_rope == rhs_casted.config.support_2d_rope && + config.output_trans0213 == rhs_casted.config.output_trans0213 && config.is_interleaved == rhs_casted.config.is_interleaved && config.is_qwen == rhs_casted.config.is_qwen && config.rotary_ndims == rhs_casted.config.rotary_ndims && @@ -80,6 +82,7 @@ struct rope : public primitive_base { ob << config.input_trans0213; ob << config.is_chatglm; ob << config.support_2d_rope; + ob << config.output_trans0213; ob << config.is_interleaved; ob << config.is_qwen; ob << config.rotary_ndims; @@ -96,6 +99,7 @@ struct rope : public primitive_base { ib >> config.input_trans0213; ib >> config.is_chatglm; ib >> config.support_2d_rope; + ib >> config.output_trans0213; ib >> config.is_interleaved; ib >> config.is_qwen; ib >> config.rotary_ndims; diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/rope.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/rope.cpp index 27ce085ab83c3f..d06e643c71ad18 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/rope.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/rope.cpp @@ -45,7 +45,7 @@ struct rope_impl : typed_primitive_impl_ocl { params.slice_stop = primitive->config.slice_stop; params.axis = primitive->config.is_qwen || primitive->config.is_chatglm ? 2 : 3; - params.num_of_inputs = primitive->config.is_chatglm || primitive->config.is_interleaved ? 2 : 3; + params.num_of_inputs = primitive->config.is_chatglm || (primitive->config.output_trans0213 && primitive->config.is_interleaved) ? 2 : 3; if (params.gather_rank > 0) { params.num_of_inputs++; @@ -53,6 +53,7 @@ struct rope_impl : typed_primitive_impl_ocl { params.is_qwen = primitive->config.is_qwen; params.is_chatglm = primitive->config.is_chatglm; + params.is_interleaved = primitive->config.is_interleaved; params.support_2d_rope = primitive->config.support_2d_rope; params.transposed_input = primitive->config.input_trans0213; diff --git a/src/plugins/intel_gpu/src/graph/rope.cpp b/src/plugins/intel_gpu/src/graph/rope.cpp index e168626f8d69a2..bef3f6dfcd93c0 100644 --- a/src/plugins/intel_gpu/src/graph/rope.cpp +++ b/src/plugins/intel_gpu/src/graph/rope.cpp @@ -54,7 +54,7 @@ std::vector rope_inst::calc_output_layouts(rope_node const& node, kernel output_shape[3] = input_slice_size; } - if (desc->config.input_trans0213 || desc->config.is_interleaved) { + if (desc->config.input_trans0213 || desc->config.output_trans0213) { std::swap(output_shape[2], output_shape[1]); } } @@ -77,6 +77,7 @@ std::string rope_inst::to_string(rope_node const& node) { rope_info.add("input_trans0213", desc->config.input_trans0213); rope_info.add("is_chatglm", desc->config.is_chatglm); rope_info.add("support_2d_rope", desc->config.support_2d_rope); + rope_info.add("output_trans0213", desc->config.output_trans0213); rope_info.add("is_interleaved", desc->config.is_interleaved); rope_info.add("is_qwen", desc->config.is_qwen); rope_info.add("rotary_ndims", desc->config.rotary_ndims); diff --git a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rope_ref.cl b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rope_ref.cl index 133440a21301f2..d429916b46d69a 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rope_ref.cl +++ b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/rope_ref.cl @@ -160,3 +160,42 @@ KERNEL(rope_ref)( sin[sin_idx + HALF_ROTARY_NDIMS + r] * in1; } #endif + +#ifdef RotateInterleaved +KERNEL(rope_ref)( + OPTIONAL_SHAPE_INFO_ARG + const __global INPUT0_TYPE* input, + const __global INPUT1_TYPE* cos, + const __global INPUT2_TYPE* sin, + __global OUTPUT_TYPE* output) +{ + const uint b = get_global_id(0); + const uint h = get_global_id(1); + const uint p = (uint)get_global_id(2) / HALF_ROTARY_NDIMS; + const uint r = 2 * ((uint)get_global_id(2) % HALF_ROTARY_NDIMS); + + uint input_idx = INPUT0_GET_INDEX(b, h, p, 0); + + uint cos_sin_b = b < INPUT1_BATCH_NUM ? b : 0; + uint cos_sin_h = h < INPUT1_FEATURE_NUM ? h : 0; + uint cos_sin_p = p < INPUT1_SIZE_Y ? p : 0; + +#ifndef SIN_COS_HAVE_DYNAMIC_PADDINGS + uint cos_sin_idx = INPUT1_GET_INDEX(cos_sin_b, cos_sin_h, cos_sin_p, 0); + + uint cos_idx = cos_sin_idx; + uint sin_idx = cos_sin_idx; +#else + uint cos_idx = INPUT1_GET_INDEX(cos_sin_b, cos_sin_h, cos_sin_p, 0); + uint sin_idx = INPUT2_GET_INDEX(cos_sin_b, cos_sin_h, cos_sin_p, 0); +#endif + + uint output_idx = OUTPUT_GET_INDEX(b, h, p, 0); + + INPUT0_TYPE in1 = input[input_idx + r]; + INPUT0_TYPE in2 = input[input_idx + r + 1]; + + output[output_idx + r] = cos[cos_idx + r] * in1 - sin[sin_idx + r] * in2; + output[output_idx + r + 1] = cos[cos_idx + r + 1] * in2 + sin[sin_idx + r + 1] * in1; +} +#endif diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.cpp index 130c5a69d4262c..98212254be9e3c 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.cpp +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.cpp @@ -51,6 +51,8 @@ JitConstants RoPEKernelBase::GetJitConstants(const rope_params& params, RoPEKern jit.AddConstant(MakeJitConstant("SUPPORT_2D_ROPE", true)); } jit.AddConstant(MakeJitConstant("CHATGLM", true)); + } else if (params.is_interleaved) { + jit.AddConstant(MakeJitConstant("RotateInterleaved", true)); } else { jit.AddConstant(MakeJitConstant("RotateHalf", true)); } diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.h b/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.h index 472131eba5d82f..8e95c12d9a78dd 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.h +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/rope/rope_kernel_base.h @@ -24,6 +24,7 @@ struct rope_params : public base_params { bool is_qwen = false; bool is_chatglm = false; + bool is_interleaved = false; bool support_2d_rope = false; bool transposed_input = false; }; diff --git a/src/plugins/intel_gpu/src/plugin/ops/rope.cpp b/src/plugins/intel_gpu/src/plugin/ops/rope.cpp index 321342b3395660..04eae769612bfc 100644 --- a/src/plugins/intel_gpu/src/plugin/ops/rope.cpp +++ b/src/plugins/intel_gpu/src/plugin/ops/rope.cpp @@ -29,6 +29,8 @@ static void CreateRoPEOp(ProgramBuilder& p, const std::shared_ptrget_input_partial_shape(config.gather_position_arg_id).size(); } + OPENVINO_ASSERT(!config.is_interleaved || !config.output_trans0213, "[GPU] Unsupported ROPE parameters"); + auto rope = cldnn::rope(layer_type_name_ID(op), inputs, config, diff --git a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/subgraph_tests/rotary_pos_emb.cpp b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/subgraph_tests/rotary_pos_emb.cpp index 741014b461e7f0..3f7fe91da86d93 100644 --- a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/subgraph_tests/rotary_pos_emb.cpp +++ b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/subgraph_tests/rotary_pos_emb.cpp @@ -7,6 +7,11 @@ namespace ov { namespace test { +INSTANTIATE_TEST_SUITE_P(smoke_RoPETestFlux, + RoPETestFlux, + ::testing::Values(ov::test::utils::DEVICE_GPU), + RoPETestFlux::getTestCaseName); + INSTANTIATE_TEST_SUITE_P(smoke_RoPETestChatGLM, RoPETestChatGLMStridedSlice, ::testing::Values(ov::test::utils::DEVICE_GPU), diff --git a/src/tests/functional/plugin/shared/include/subgraph_tests/rotary_pos_emb.hpp b/src/tests/functional/plugin/shared/include/subgraph_tests/rotary_pos_emb.hpp index 7100ddca1083e3..c3f0b8ef0b6015 100644 --- a/src/tests/functional/plugin/shared/include/subgraph_tests/rotary_pos_emb.hpp +++ b/src/tests/functional/plugin/shared/include/subgraph_tests/rotary_pos_emb.hpp @@ -24,6 +24,13 @@ inline void CheckNumberOfNodesWithType(std::shared_ptr function ASSERT_EQ(num_ops, expectedCount); } +TEST_P(RoPETestFlux, CompareWithRefs) { + SKIP_IF_CURRENT_TEST_IS_DISABLED(); + run(); + auto function = compiledModel.get_runtime_model(); + CheckNumberOfNodesWithType(function, {"RoPE"}, 1); +}; + TEST_P(RoPETestLlama2StridedSlice, CompareWithRefs) { SKIP_IF_CURRENT_TEST_IS_DISABLED(); run(); diff --git a/src/tests/functional/shared_test_classes/include/shared_test_classes/subgraph/rotary_pos_emb.hpp b/src/tests/functional/shared_test_classes/include/shared_test_classes/subgraph/rotary_pos_emb.hpp index e1182bd3b16e13..39cdb871710e64 100644 --- a/src/tests/functional/shared_test_classes/include/shared_test_classes/subgraph/rotary_pos_emb.hpp +++ b/src/tests/functional/shared_test_classes/include/shared_test_classes/subgraph/rotary_pos_emb.hpp @@ -9,6 +9,20 @@ namespace ov { namespace test { +class RoPETestFlux : public SubgraphBaseTest, public testing::WithParamInterface { +private: + std::shared_ptr build_rope_flux(int batch, + int seq_length, + int num_head, + int ndims); +protected: + void generate_inputs(const std::vector& targetInputStaticShapes) override; + void SetUp() override; + +public: + static std::string getTestCaseName(const testing::TestParamInfo& obj); +}; + class RoPETestLlama2StridedSlice : public SubgraphBaseTest, public testing::WithParamInterface { private: std::shared_ptr buildROPE_Llama2(int batch, diff --git a/src/tests/functional/shared_test_classes/src/subgraph/rotary_pos_emb.cpp b/src/tests/functional/shared_test_classes/src/subgraph/rotary_pos_emb.cpp index a1848903bb76a2..1a078d9b49ebb7 100644 --- a/src/tests/functional/shared_test_classes/src/subgraph/rotary_pos_emb.cpp +++ b/src/tests/functional/shared_test_classes/src/subgraph/rotary_pos_emb.cpp @@ -5,6 +5,7 @@ #include "shared_test_classes/subgraph/rotary_pos_emb.hpp" #include "common_test_utils/ov_tensor_utils.hpp" +#include "openvino/core/node_vector.hpp" #include "transformations/utils/gen_pattern.hpp" using namespace ov::gen_pattern; @@ -13,6 +14,85 @@ using namespace ov; namespace ov { namespace test { +std::shared_ptr RoPETestFlux::build_rope_flux(int batch, + int seq_length, + int num_head, + int ndims) { + auto x = std::make_shared(ov::element::f32, PartialShape{batch, num_head, seq_length, ndims}); + auto t_cos = std::make_shared(ov::element::f32, PartialShape{1, 1, seq_length, ndims}); + auto t_sin = std::make_shared(ov::element::f32, PartialShape{1, 1, seq_length, ndims}); + + auto x1_shape = makeConst(element::i64, ov::Shape({5}), {0, num_head, 0, -1, 2}); + auto x1 = std::make_shared(x, x1_shape, true); + + auto split_axis = makeConst(element::i64, ov::Shape(), {-1}); + auto split = std::make_shared(x1, split_axis, 2); + + auto minus_one = makeConst(element::f32, ov::Shape({}), {-1.0f}); + auto x1_1_neg = std::make_shared(split->output(1), minus_one); + + auto x2 = std::make_shared(OutputVector{x1_1_neg->output(0), split->output(0)}, -1); + + auto x3_shape = makeConst(element::i64, ov::Shape({4}), {0, num_head, 0, ndims}); + auto x3 = std::make_shared(x2, x3_shape, true); + + auto y1 = std::make_shared(x, t_cos); + auto y2 = std::make_shared(x3, t_sin); + auto y = std::make_shared(y1, y2); + + return std::make_shared(ov::NodeVector{y}, ov::ParameterVector{x, t_cos, t_sin}); +} + +void RoPETestFlux::generate_inputs(const std::vector& targetInputStaticShapes) { + const auto& funcInputs = function->inputs(); + + ov::test::utils::InputGenerateData in_data; + in_data.start_from = -1; + in_data.range = 2; + in_data.resolution = 32768; + + auto cos_data = in_data; + cos_data.seed = 10; + + auto sin_data = in_data; + sin_data.seed = 20; + + ov::Tensor t_input = utils::create_and_fill_tensor(funcInputs[0].get_element_type(), targetInputStaticShapes[0], in_data); + ov::Tensor t_cos = utils::create_and_fill_tensor(funcInputs[1].get_element_type(), targetInputStaticShapes[1], cos_data); + ov::Tensor t_sin = utils::create_and_fill_tensor(funcInputs[2].get_element_type(), targetInputStaticShapes[2], sin_data); + + inputs.clear(); + inputs.insert({funcInputs[0].get_node_shared_ptr(), t_input}); + inputs.insert({funcInputs[1].get_node_shared_ptr(), t_cos}); + inputs.insert({funcInputs[2].get_node_shared_ptr(), t_sin}); +} + +void RoPETestFlux::SetUp() { + targetDevice = this->GetParam(); + + const int batch = 1; + const int seq_length = 7; + const size_t max_position_embeddings = 2048; + const size_t ndims = 128; + const size_t num_head = 24; + + std::vector input_shapes = { + {{batch, num_head, seq_length, ndims}, {{batch, num_head, seq_length, ndims}}}, + {{1, 1, seq_length, ndims}, {{1, 1, seq_length, ndims}}}, + {{1, 1, seq_length, ndims}, {{1, 1, seq_length, ndims}}} + }; + init_input_shapes(input_shapes); + function = build_rope_flux(batch, -1, num_head, ndims); +} + +std::string RoPETestFlux::getTestCaseName(const testing::TestParamInfo& obj) { + std::string targetDevice = obj.param; + std::ostringstream result; + result << "targetDevice=" << targetDevice; + return result.str(); +} + + ov::OutputVector RoPETestLlama2StridedSlice::makeCosSinCache(int max_position_embeddings, int rotary_ndims) { std::vector lut_sin(max_position_embeddings * rotary_ndims, 0.0f); std::vector lut_cos(max_position_embeddings * rotary_ndims, 0.0f); From 1b72ca5ff38f25c87b6eacd729330a17eb414c8d Mon Sep 17 00:00:00 2001 From: Andrzej Kopytko Date: Tue, 26 Nov 2024 12:44:01 +0100 Subject: [PATCH 02/24] [DOCS] Port for Ovms data update to master (#27749) port for https://github.com/openvinotoolkit/openvino/pull/27748 --- .../OV-2024.5-Performance-Data.xlsx | Bin 249213 -> 225814 bytes .../data/graph-data-ovms.json | 2326 +++++++++-------- 2 files changed, 1281 insertions(+), 1045 deletions(-) diff --git a/docs/sphinx_setup/_static/benchmarks_files/OV-2024.5-Performance-Data.xlsx b/docs/sphinx_setup/_static/benchmarks_files/OV-2024.5-Performance-Data.xlsx index 0c29b3282790fac37d8685149a46dedffab22830..e5a6a4b039b029eaf557547080948ff33211065b 100644 GIT binary patch delta 94225 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%0Alp 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

E{2pEB1IN=)hSXa=|v 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 F{{w3#9E$(| delta 108909 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 MYSCz44uG8YKdhF50ssI2 diff --git a/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ovms.json b/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ovms.json index 18a36073d582f5..f601a8120117d6 100644 --- a/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ovms.json +++ b/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ovms.json @@ -1,1047 +1,1283 @@ [ - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "bert-base-cased", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 159.534, - "fp32_ovms": 157.334, - "int8_ov": 432.339, - "int8_ovms": 420.793 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "bert-large-uncased-whole-word-masking-squad-0001", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 13.125, - "fp32_ovms": 13.254, - "int8_ov": 38.151, - "int8_ovms": 37.623 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "efficientdet-d0", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 247.445, - "fp32_ovms": 253.09, - "int8_ov": 413.083, - "int8_ovms": 377.844 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "mask_rcnn_resnet50_atrous_coco", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1.638, - "fp32_ovms": 1.714, - "int8_ov": 6.202, - "int8_ovms": 6.126 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "mobilenet-v2", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 3333.399, - "fp32_ovms": 2905.171, - "int8_ov": 10422.241, - "int8_ovms": 7461.99 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "resnet-50", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 575.208, - "fp32_ovms": 569.925, - "int8_ov": 2199.072, - "int8_ovms": 2064.581 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "ssd-resnet34-1200", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 10.598, - "fp32_ovms": 10.472, - "int8_ov": 40.683, - "int8_ovms": 38.737 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "ssd_mobilenet_v1_coco", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1219.441, - "fp32_ovms": 1201.096, - "int8_ov": 4400.471, - "int8_ovms": 4270.702 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "unet-camvid-onnx-0001", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 15.924, - "fp32_ovms": 15.763, - "int8_ov": 67.731, - "int8_ovms": 64.658 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "yolo_v5m", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 74.189, - "fp32_ovms": 68.788, - "int8_ov": 247.757, - "int8_ovms": 180.302 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Gold 6238M", - "Model": "yolo_v8n", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 332.326, - "fp32_ovms": 278.054, - "int8_ov": 740.985, - "int8_ovms": 609.062 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "bert-base-cased", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 182.025, - "fp32_ovms": 180.764, - "int8_ov": 485.82, - "int8_ovms": 472.842 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "bert-large-uncased-whole-word-masking-squad-0001", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 14.625, - "fp32_ovms": 15.132, - "int8_ov": 42.906, - "int8_ovms": 42.406 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "efficientdet-d0", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 288.531, - "fp32_ovms": 278.548, - "int8_ov": 483.438, - "int8_ovms": 443.032 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "mask_rcnn_resnet50_atrous_coco", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1.872, - "fp32_ovms": 1.95, - "int8_ov": 6.856, - "int8_ovms": 6.763 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "mobilenet-v2", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 3909.405, - "fp32_ovms": 3327.621, - "int8_ov": 12375.018, - "int8_ovms": 7554.235 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "resnet-50", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 634.732, - "fp32_ovms": 634.102, - "int8_ov": 2481.256, - "int8_ovms": 2349.872 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "ssd-resnet34-1200", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 12.166, - "fp32_ovms": 12.027, - "int8_ov": 47.295, - "int8_ovms": 44.525 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "ssd_mobilenet_v1_coco", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1384.145, - "fp32_ovms": 1356.126, - "int8_ov": 5037.197, - "int8_ovms": 4834.045 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "unet-camvid-onnx-0001", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 18.26, - "fp32_ovms": 18.052, - "int8_ov": 77.933, - "int8_ovms": 73.527 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "yolo_v5m", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 85.149, - "fp32_ovms": 78.205, - "int8_ov": 281.889, - "int8_ovms": 204.353 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Xeon® Platinum 8260M", - "Model": "yolo_v8n", - "PlatformType": "Server Platforms (Intel® Xeon®)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 376.079, - "fp32_ovms": 312.181, - "int8_ov": 801.556, - "int8_ovms": 678.929 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "bert-base-cased", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 35.915, - "fp32_ovms": 34.381, - "int8_ov": 101.976, - "int8_ovms": 99.024 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "bert-large-uncased-whole-word-masking-squad-0001", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 3.232, - "fp32_ovms": 3.266, - "int8_ov": 10.132, - "int8_ovms": 10.133 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "efficientdet-d0", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 51.747, - "fp32_ovms": 48.906, - "int8_ov": 142.489, - "int8_ovms": 124.167 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "mask_rcnn_resnet50_atrous_coco", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 0.352, - "fp32_ovms": 0.364, - "int8_ov": 1.322, - "int8_ovms": 1.336 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "mobilenet-v2", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 795.18, - "fp32_ovms": 664.842, - "int8_ov": 2721.454, - "int8_ovms": 2063.761 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "resnet-50", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 114.859, - "fp32_ovms": 110.835, - "int8_ov": 467.591, - "int8_ovms": 445.408 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "ssd-resnet34-1200", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 2.053, - "fp32_ovms": 2.074, - "int8_ov": 8.023, - "int8_ovms": 7.987 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "ssd_mobilenet_v1_coco", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 260.104, - "fp32_ovms": 250.094, - "int8_ov": 991.064, - "int8_ovms": 930.128 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "unet-camvid-onnx-0001", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 3.273, - "fp32_ovms": 3.3, - "int8_ov": 12.884, - "int8_ovms": 12.727 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "yolo_v5m", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 14.714, - "fp32_ovms": 14.243, - "int8_ov": 55.058, - "int8_ovms": 47.548 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i7-11700K", - "Model": "yolo_v8n", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 71.446, - "fp32_ovms": 64.775, - "int8_ov": 200.864, - "int8_ovms": 144.792 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "bert-base-cased", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 36.227, - "fp32_ovms": 35.646, - "int8_ov": 101.562, - "int8_ovms": 100.382 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "bert-large-uncased-whole-word-masking-squad-0001", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 3.23, - "fp32_ovms": 3.254, - "int8_ov": 10.05, - "int8_ovms": 10.092 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "efficientdet-d0", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 59.759, - "fp32_ovms": 55.851, - "int8_ov": 149.505, - "int8_ovms": 131.453 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "mask_rcnn_resnet50_atrous_coco", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 0.368, - "fp32_ovms": 0.394, - "int8_ov": 1.308, - "int8_ovms": 1.338 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "mobilenet-v2", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 865.806, - "fp32_ovms": 734.822, - "int8_ov": 2743.201, - "int8_ovms": 2163.412 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "resnet-50", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 116.784, - "fp32_ovms": 113.046, - "int8_ov": 457.358, - "int8_ovms": 440.924 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "ssd-resnet34-1200", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 2.006, - "fp32_ovms": 2.031, - "int8_ov": 7.817, - "int8_ovms": 7.75 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "ssd_mobilenet_v1_coco", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 274.42, - "fp32_ovms": 264.153, - "int8_ov": 997.987, - "int8_ovms": 915.681 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "unet-camvid-onnx-0001", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 3.246, - "fp32_ovms": 3.272, - "int8_ov": 12.668, - "int8_ovms": 12.585 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "yolo_v5m", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 14.985, - "fp32_ovms": 14.514, - "int8_ov": 54.937, - "int8_ovms": 47.767 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i9-11900K", - "Model": "yolo_v8n", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 74.1, - "fp32_ovms": 67.472, - "int8_ov": 203.493, - "int8_ovms": 151.175 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "bert-base-cased", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 17.054, - "fp32_ovms": 17.124, - "int8_ov": 26.043, - "int8_ovms": 25.872 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "bert-large-uncased-whole-word-masking-squad-0001", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1.434, - "fp32_ovms": 1.456, - "int8_ov": 2.421, - "int8_ovms": 2.450 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "efficientdet-d0", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 31.321, - "fp32_ovms": 30.316, - "int8_ov": 50.629, - "int8_ovms": 47.377 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "mask_rcnn_resnet50_atrous_coco", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 0.151, - "fp32_ovms": 0.182, - "int8_ov": 0.361, - "int8_ovms": 0.389 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "mobilenet-v2", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 442.763, - "fp32_ovms": 380.661, - "int8_ov": 724.232, - "int8_ovms": 617.393 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "resnet-50", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 57.978, - "fp32_ovms": 57.038, - "int8_ov": 118.213, - "int8_ovms": 113.691 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "ssd-resnet34-1200", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1.0, - "fp32_ovms": 1.031, - "int8_ov": 1.937, - "int8_ovms": 1.954 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "ssd_mobilenet_v1_coco", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 133.421, - "fp32_ovms": 129.949, - "int8_ov": 267.141, - "int8_ovms": 256.821 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "unet-camvid-onnx-0001", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 1.515, - "fp32_ovms": 1.534, - "int8_ov": 2.96, - "int8_ovms": 2.973 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "yolo_v5m", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 7.691, - "fp32_ovms": 7.511, - "int8_ov": 14.919, - "int8_ovms": 13.832 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - }, - { - "Platform": "Intel® Core™ i3-10100", - "Model": "yolo_v8n", - "PlatformType": "Client Platforms (Intel® Core™)", - "Parameters": { - "throughput": { - "Precisions": [ - { - "fp32_ov": 38.482, - "fp32_ovms": 34.513, - "int8_ov": 68.126, - "int8_ovms": 55.698 - } - ], - "Unit": "FPS", - "UnitDesc": "higher is better" - } - } - } + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 161.041, + "fp32_ovms": 157.547, + "int8_ov": 435.257, + "int8_ovms": 422.689 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 259.576, + "fp32_ovms": 256.524, + "int8_ov": 412.419, + "int8_ovms": 376.69 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "manual_yolo11", + "featured_SKU": false, + "whats_new_model": true, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 385.381, + "fp32_ovms": 312.784, + "int8_ov": "", + "int8_ovms": "" + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 1.64, + "fp32_ovms": 1.718, + "int8_ov": 6.426, + "int8_ovms": 6.258 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 3349.207, + "fp32_ovms": 2904.878, + "int8_ov": 10365.087, + "int8_ovms": 7521.115 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 580.494, + "fp32_ovms": 572.921, + "int8_ov": 2196.814, + "int8_ovms": 2072.444 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 10.627, + "fp32_ovms": 10.524, + "int8_ov": 40.619, + "int8_ovms": 38.733 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 1234.49, + "fp32_ovms": 1203.314, + "int8_ov": 4445.793, + "int8_ovms": 4261.084 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Gold 6238M", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 337.397, + "fp32_ovms": 279.585, + "int8_ov": 758.758, + "int8_ovms": 641.433 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 182.454, + "fp32_ovms": 181.015, + "int8_ov": 487.412, + "int8_ovms": 475.32 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 291.999, + "fp32_ovms": 289.402, + "int8_ov": 485.657, + "int8_ovms": 442.145 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "manual_yolo11", + "featured_SKU": false, + "whats_new_model": true, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 437.295, + "fp32_ovms": 354.521, + "int8_ov": "", + "int8_ovms": "" + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 1.889, + "fp32_ovms": 1.961, + "int8_ov": 7.085, + "int8_ovms": 6.985 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 3923.365, + "fp32_ovms": 3332.521, + "int8_ov": 12328.807, + "int8_ovms": 7562.762 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 645.638, + "fp32_ovms": 639.958, + "int8_ov": 2493.033, + "int8_ovms": 2349.919 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 12.196, + "fp32_ovms": 12.091, + "int8_ov": 47.197, + "int8_ovms": 44.379 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 1385.809, + "fp32_ovms": 1374.891, + "int8_ov": 5079.624, + "int8_ovms": 4836.539 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Xeon® Platinum 8260M", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Server Platforms (Intel® Xeon®)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 383.81, + "fp32_ovms": 315.245, + "int8_ov": 858.66, + "int8_ovms": 704.713 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 34.685, + "fp32_ovms": 32.405, + "int8_ov": 100.893, + "int8_ovms": 94.564 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 51.125, + "fp32_ovms": 46.351, + "int8_ov": 141.548, + "int8_ovms": 115.788 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 0.331, + "fp32_ovms": 0.336, + "int8_ov": 1.331, + "int8_ovms": 1.354 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 774.104, + "fp32_ovms": 628.503, + "int8_ov": 2723.303, + "int8_ovms": 1832.886 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 113.363, + "fp32_ovms": 106.029, + "int8_ov": 466.473, + "int8_ovms": 433.532 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 2.047, + "fp32_ovms": 2.047, + "int8_ov": 8.016, + "int8_ovms": 7.886 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 259.504, + "fp32_ovms": 236.341, + "int8_ov": 995.124, + "int8_ovms": 869.518 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 71.24, + "fp32_ovms": 62.319, + "int8_ov": 199.772, + "int8_ovms": 133.145 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 36.251, + "fp32_ovms": 35.465, + "int8_ov": 101.305, + "int8_ovms": 99.151 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 59.247, + "fp32_ovms": 55.459, + "int8_ov": 148.119, + "int8_ovms": 130.171 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 0.37, + "fp32_ovms": 0.388, + "int8_ov": 1.321, + "int8_ovms": 1.332 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 855.526, + "fp32_ovms": 713.553, + "int8_ov": 2745.282, + "int8_ovms": 2129.129 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 115.109, + "fp32_ovms": 112.189, + "int8_ov": 455.027, + "int8_ovms": 437.03 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 2.004, + "fp32_ovms": 2.022, + "int8_ov": 7.796, + "int8_ovms": 7.729 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 274.523, + "fp32_ovms": 260.272, + "int8_ov": 966.639, + "int8_ovms": 893.165 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 74.006, + "fp32_ovms": 67.143, + "int8_ov": 204.296, + "int8_ovms": 151.136 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 17.146, + "fp32_ovms": 17.085, + "int8_ov": 26.112, + "int8_ovms": 25.962 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 30.601, + "fp32_ovms": 29.76, + "int8_ov": 49.646, + "int8_ovms": 47.222 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "manual_yolo11", + "featured_SKU": false, + "whats_new_model": true, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 43.751, + "fp32_ovms": 38.752, + "int8_ov": "", + "int8_ovms": "" + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 0.148, + "fp32_ovms": 0.18, + "int8_ov": 0.36, + "int8_ovms": 0.39 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 440.453, + "fp32_ovms": 380.439, + "int8_ov": 714.915, + "int8_ovms": 611.391 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 57.896, + "fp32_ovms": 56.88, + "int8_ov": 117.702, + "int8_ovms": 113.447 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 0.996, + "fp32_ovms": 1.033, + "int8_ov": 1.935, + "int8_ovms": 1.946 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 132.73, + "fp32_ovms": 128.89, + "int8_ov": 266.502, + "int8_ovms": 256.113 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i3-10100", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 38.386, + "fp32_ovms": 34.599, + "int8_ov": 68.072, + "int8_ovms": 55.668 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 34.685, + "fp32_ovms": 33.575, + "int8_ov": 100.893, + "int8_ovms": 96.251 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 51.125, + "fp32_ovms": 47.06, + "int8_ov": 141.548, + "int8_ovms": 117.642 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "manual_yolo11", + "featured_SKU": false, + "whats_new_model": true, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 80.399, + "fp32_ovms": 68.631, + "int8_ov": "", + "int8_ovms": "" + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 0.331, + "fp32_ovms": 0.344, + "int8_ov": 1.331, + "int8_ovms": 1.417 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 774.104, + "fp32_ovms": 628.386, + "int8_ov": 2723.303, + "int8_ovms": 1905.703 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 113.363, + "fp32_ovms": 106.07, + "int8_ov": 466.473, + "int8_ovms": 433.345 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 2.047, + "fp32_ovms": 2.055, + "int8_ov": 8.016, + "int8_ovms": 7.884 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 259.504, + "fp32_ovms": 238.91, + "int8_ov": 995.124, + "int8_ovms": 880.377 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i7-11700K", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 71.24, + "fp32_ovms": 62.386, + "int8_ov": 199.772, + "int8_ovms": 139.345 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "bert-base-cased", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 36.251, + "fp32_ovms": 35.522, + "int8_ov": 101.305, + "int8_ovms": 99.886 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "efficientdet-d0", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 59.247, + "fp32_ovms": 55.715, + "int8_ov": 148.119, + "int8_ovms": 131.749 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "manual_yolo11", + "featured_SKU": false, + "whats_new_model": true, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 85.883, + "fp32_ovms": 76.288, + "int8_ov": "", + "int8_ovms": "" + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "mask_rcnn_resnet50_atrous_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 0.37, + "fp32_ovms": 0.396, + "int8_ov": 1.321, + "int8_ovms": 1.337 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "mobilenet-v2", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 855.526, + "fp32_ovms": 731.031, + "int8_ov": 2745.282, + "int8_ovms": 2154.044 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "resnet-50", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 115.109, + "fp32_ovms": 112.697, + "int8_ov": 455.027, + "int8_ovms": 439.19 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "ssd-resnet34-1200", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 2.004, + "fp32_ovms": 2.027, + "int8_ov": 7.796, + "int8_ovms": 7.748 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "ssd_mobilenet_v1_coco", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 274.523, + "fp32_ovms": 263.584, + "int8_ov": 966.639, + "int8_ovms": 916.111 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + }, + { + "Platform": "Intel® Core™ i9-11900K", + "Model": "yolo_v8n", + "featured_SKU": false, + "whats_new_model": false, + "PlatformType": "Client Platforms (Intel® Core™)", + "Parameters": { + "throughput": { + "Precisions": [ + { + "fp32_ov": 74.006, + "fp32_ovms": 67.401, + "int8_ov": 204.296, + "int8_ovms": 151.665 + } + ], + "Unit": "FPS", + "UnitDesc": "higher is better" + } + } + } ] \ No newline at end of file From c85e88bb705ba3546de394ba4261bd996c869738 Mon Sep 17 00:00:00 2001 From: Piotr Kowalczyk Date: Tue, 26 Nov 2024 14:43:06 +0100 Subject: [PATCH 03/24] [GPU]: SearchSorted basic implementation. (#27356) Added GPU reference SearchSorted op implementation with unit and func tests. Kernel supports dynamic shapes. ### Details: - Fixed a bug in reference implementation, when sorted had exactly one element. Added tests for that case. ### Tickets: - CVS-156238 --- .../openvino/reference/search_sorted.hpp | 12 +- .../intel_gpu/plugin/primitives_list.hpp | 1 + .../intel_gpu/primitives/search_sorted.hpp | 54 +++++++ .../src/graph/impls/ocl/register.cpp | 1 + .../src/graph/impls/ocl/register.hpp | 1 + .../src/graph/impls/ocl/search_sorted.cpp | 107 +++++++++++++ .../src/graph/impls/registry/registry.hpp | 1 + .../src/graph/include/search_sorted_inst.h | 46 ++++++ .../intel_gpu/src/graph/search_sorted.cpp | 59 +++++++ .../cl_kernels/search_sorted_ref.cl | 56 +++++++ .../src/kernel_selector/common_types.h | 3 +- .../search_sorted_kernel_base.cpp | 72 +++++++++ .../search_sorted/search_sorted_kernel_base.h | 34 ++++ .../search_sorted_kernel_ref.cpp | 45 ++++++ .../search_sorted/search_sorted_kernel_ref.h | 18 +++ .../search_sorted_kernel_selector.cpp | 14 ++ .../search_sorted_kernel_selector.h | 21 +++ .../src/plugin/ops/search_sorted.cpp | 25 +++ .../intel_gpu/src/plugin/program_builder.cpp | 7 + .../single_layer_tests/search_sorted.cpp | 18 +++ .../test_cases/search_sorted_gpu_test.cpp | 148 ++++++++++++++++++ .../src/single_op/search_sorted.cpp | 19 +++ .../tests_data/search_sorted_data.h | 40 ++++- 23 files changed, 790 insertions(+), 12 deletions(-) create mode 100644 src/plugins/intel_gpu/include/intel_gpu/primitives/search_sorted.hpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/ocl/search_sorted.cpp create mode 100644 src/plugins/intel_gpu/src/graph/include/search_sorted_inst.h create mode 100644 src/plugins/intel_gpu/src/graph/search_sorted.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/cl_kernels/search_sorted_ref.cl create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.h create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.h create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.h create mode 100644 src/plugins/intel_gpu/src/plugin/ops/search_sorted.cpp create mode 100644 src/plugins/intel_gpu/tests/functional/shared_tests_instances/single_layer_tests/search_sorted.cpp create mode 100644 src/plugins/intel_gpu/tests/unit/test_cases/search_sorted_gpu_test.cpp diff --git a/src/core/reference/include/openvino/reference/search_sorted.hpp b/src/core/reference/include/openvino/reference/search_sorted.hpp index 7ea8ec1078a2a1..629509b28ef78d 100644 --- a/src/core/reference/include/openvino/reference/search_sorted.hpp +++ b/src/core/reference/include/openvino/reference/search_sorted.hpp @@ -32,6 +32,7 @@ void search_sorted(const T* sorted, } const size_t size = shape_size(values_shape); + const size_t sorted_inner_dim = sorted_shape.back(); auto func = [&](size_t i) { auto it = values_transform.begin(); @@ -44,15 +45,12 @@ void search_sorted(const T* sorted, Coordinate sorted_coord_begin = values_coord; sorted_coord_begin.back() = 0; - Coordinate sorted_coord_last = values_coord; - sorted_coord_last.back() = sorted_shape.back(); - const auto sorted_index_begin = coordinate_index(sorted_coord_begin, sorted_shape); - const auto sorted_index_last = coordinate_index(sorted_coord_last, sorted_shape); - - const T* idx_ptr = compare_func(sorted + sorted_index_begin, sorted + sorted_index_last, value); + const T* sorted_begin_ptr = sorted + sorted_index_begin; + const T* sorted_end_ptr = sorted_begin_ptr + sorted_inner_dim; + const T* idx_ptr = compare_func(sorted_begin_ptr, sorted_end_ptr, value); - const ptrdiff_t sorted_index = (idx_ptr - sorted) - sorted_index_begin; + const ptrdiff_t sorted_index = idx_ptr - sorted_begin_ptr; out[values_index] = static_cast(sorted_index); }; diff --git a/src/plugins/intel_gpu/include/intel_gpu/plugin/primitives_list.hpp b/src/plugins/intel_gpu/include/intel_gpu/plugin/primitives_list.hpp index ced915d25610e8..e234bc68de0750 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/plugin/primitives_list.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/plugin/primitives_list.hpp @@ -272,6 +272,7 @@ REGISTER_FACTORY(v13, BitwiseXor); REGISTER_FACTORY(v15, ROIAlignRotated); REGISTER_FACTORY(v15, BitwiseRightShift); REGISTER_FACTORY(v15, BitwiseLeftShift); +REGISTER_FACTORY(v15, SearchSorted); // --------------------------- Supported internal ops --------------------------- // REGISTER_FACTORY(internal, NonMaxSuppressionIEInternal); diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/search_sorted.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/search_sorted.hpp new file mode 100644 index 00000000000000..4dfb5c87f8c58c --- /dev/null +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/search_sorted.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once +#include "primitive.hpp" + +namespace cldnn { + +/// @brief +/// @details +struct search_sorted : public primitive_base { + CLDNN_DECLARE_PRIMITIVE(search_sorted) + + search_sorted() : primitive_base("", {}) {} + + /// @brief Constructs search_sorted primitive. + /// @param id This primitive id. + /// @param sorted Sorted input. + /// @param values Values input. + /// @param right_mode Enable/Disable right mode(check specification for details).. + search_sorted(const primitive_id& id, const input_info& sorted, const input_info& values, bool right_mode) + : primitive_base(id, {sorted, values}), + right_mode(right_mode) {} + + /// @brief Enable/Disable right mode(check specification for details). + bool right_mode = false; + + size_t hash() const override { + size_t seed = primitive::hash(); + seed = hash_combine(seed, right_mode); + return seed; + } + + bool operator==(const primitive& rhs) const override { + if (!compare_common_params(rhs)) + return false; + + auto rhs_casted = downcast(rhs); + + return right_mode == rhs_casted.right_mode; + } + + void save(BinaryOutputBuffer& ob) const override { + primitive_base::save(ob); + ob << right_mode; + } + + void load(BinaryInputBuffer& ib) override { + primitive_base::load(ib); + ib >> right_mode; + } +}; +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp index 2597e419e66a41..7f2fab7a6d1581 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp @@ -88,6 +88,7 @@ void register_implementations() { REGISTER_OCL(unique_gather); REGISTER_OCL(scaled_dot_product_attention); REGISTER_OCL(rope); + REGISTER_OCL(search_sorted); } } // namespace ocl diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp b/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp index d4b08b5154ef4b..0a605945fcf6cc 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp @@ -162,6 +162,7 @@ REGISTER_OCL(unique_count); REGISTER_OCL(unique_gather); REGISTER_OCL(scaled_dot_product_attention); REGISTER_OCL(rope); +REGISTER_OCL(search_sorted); #undef REGISTER_OCL diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/search_sorted.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/search_sorted.cpp new file mode 100644 index 00000000000000..4243d75b5c7367 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/search_sorted.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "primitive_base.hpp" +#include "search_sorted/search_sorted_kernel_base.h" +#include "search_sorted/search_sorted_kernel_selector.h" +#include "search_sorted_inst.h" + +namespace cldnn { +namespace ocl { + +struct search_sorted_impl : typed_primitive_impl_ocl { + using parent = typed_primitive_impl_ocl; + using parent::parent; + using kernel_selector_t = kernel_selector::search_sorted_kernel_selector; + using kernel_params_t = kernel_selector::search_sorted_params; + + DECLARE_OBJECT_TYPE_SERIALIZATION(cldnn::ocl::search_sorted_impl) + + std::unique_ptr clone() const override { + return make_unique(*this); + } + + void load(BinaryInputBuffer& ib) override { + parent::load(ib); + if (is_dynamic()) { + auto& kernel_selector = kernel_selector_t::Instance(); + auto kernel_impl = kernel_selector.GetImplementation(_kernel_data.kernelName); + kernel_impl->GetUpdateDispatchDataFunc(_kernel_data); + } + } + + void update_dispatch_data(const kernel_impl_params& impl_param) override { + // If model loaded from cache, params are not initialized, so we create a new object and reuse it in the future + if (_kernel_data.params == nullptr) { + _kernel_data.params = std::make_shared(get_kernel_params(impl_param, true)); + } + + update_shapes(*_kernel_data.params, impl_param); + (_kernel_data.update_dispatch_data_func)(*_kernel_data.params, _kernel_data); + } + + static kernel_params_t get_kernel_params(const kernel_impl_params& impl_param, bool shape_agnostic = false) { + const auto& primitive = impl_param.typed_desc(); + auto params = get_default_params(impl_param, shape_agnostic); + + // Manually add all inputs except first one, since get_default_params does not handle it. + for (size_t i = 1; i < impl_param.input_layouts.size(); ++i) { + params.inputs.push_back(convert_data_tensor(impl_param.get_input_layout(i))); + } + + params.right_mode = primitive->right_mode; + return params; + } + + // [NOTE]: Has to be added as a separete static function, since it is called via static dispatching in + // typed_primitive_impl_ocl::create().. + static kernel_impl_params static_canonicalize_shapes(const kernel_impl_params& impl_params) { + auto updated_impl_params = canonicalize_fused_shapes(impl_params); + + for (auto& input_layout : updated_impl_params.input_layouts) { + input_layout.set_partial_shape(extend_shape_to_rank_from_begin(input_layout.get_partial_shape())); + } + + for (auto& output_layout : updated_impl_params.output_layouts) { + output_layout.set_partial_shape(extend_shape_to_rank_from_begin(output_layout.get_partial_shape())); + } + + return updated_impl_params; + } + + kernel_impl_params canonicalize_shapes(const kernel_impl_params& impl_params) const override { + return static_canonicalize_shapes(impl_params); + } +}; + +namespace detail { + +attach_search_sorted_impl::attach_search_sorted_impl() { + auto types = { + data_types::i8, + data_types::u8, + data_types::i16, + data_types::u16, + data_types::i32, + data_types::u32, + data_types::i64, + data_types::f16, + data_types::f32, + }; + + auto formats = {format::bfyx, format::bfzyx}; + + implementation_map::add(impl_types::ocl, + shape_types::any, + typed_primitive_impl_ocl::create, + types, + formats); +} + +} // namespace detail +} // namespace ocl +} // namespace cldnn + +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::ocl::search_sorted_impl) +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::search_sorted) diff --git a/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp b/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp index a6bb8ad6eebcc2..77c4262a7513cc 100644 --- a/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp @@ -214,3 +214,4 @@ REGISTER_DEFAULT_IMPLS(unique_count, OCL_S, OCL_D); REGISTER_DEFAULT_IMPLS(unique_gather, OCL_S, OCL_D); REGISTER_DEFAULT_IMPLS(scaled_dot_product_attention, OCL_S, OCL_D); REGISTER_DEFAULT_IMPLS(rope, OCL_S, OCL_D); +REGISTER_DEFAULT_IMPLS(search_sorted, OCL_S, OCL_D); diff --git a/src/plugins/intel_gpu/src/graph/include/search_sorted_inst.h b/src/plugins/intel_gpu/src/graph/include/search_sorted_inst.h new file mode 100644 index 00000000000000..50ffdf8112e2ae --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/include/search_sorted_inst.h @@ -0,0 +1,46 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// +#pragma once + +#include + +#include "primitive_inst.h" + +namespace cldnn { + +template <> +struct typed_program_node : public typed_program_node_base { + using parent = typed_program_node_base; + typed_program_node(const std::shared_ptr prim, program& prog) : parent(prim, prog) {} + +public: + using parent::parent; + + program_node& input(size_t idx = 0) const { + return get_dependency(idx); + } + std::vector get_shape_infer_dependencies() const override { + return {}; + } +}; + +using search_sorted_node = typed_program_node; + +template <> +class typed_primitive_inst : public typed_primitive_inst_base { + using parent = typed_primitive_inst_base; + using parent::parent; + +public: + typed_primitive_inst(network& network, search_sorted_node const& desc); + template + static std::vector calc_output_layouts(search_sorted_node const& node, + kernel_impl_params const& impl_param); + static layout calc_output_layout(search_sorted_node const& node, kernel_impl_params const& impl_param); + static std::string to_string(search_sorted_node const& node); +}; + +using search_sorted_inst = typed_primitive_inst; + +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/search_sorted.cpp b/src/plugins/intel_gpu/src/graph/search_sorted.cpp new file mode 100644 index 00000000000000..761b6751ace3b7 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/search_sorted.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include + +#include "openvino/core/enum_names.hpp" +#include "primitive_type_base.h" +#include "search_sorted_shape_inference.hpp" + +namespace cldnn { +GPU_DEFINE_PRIMITIVE_TYPE_ID(search_sorted) + +search_sorted_inst::typed_primitive_inst(network& network, search_sorted_node const& node) : parent(network, node) {} + +layout search_sorted_inst::calc_output_layout(search_sorted_node const& node, kernel_impl_params const& impl_param) { + return calc_output_layouts(node, impl_param)[0]; +} + +template +std::vector search_sorted_inst::calc_output_layouts(search_sorted_node const& node, + kernel_impl_params const& impl_param) { + auto primitive = impl_param.typed_desc(); + + auto input0_layout = impl_param.get_input_layout(0); + auto input1_layout = impl_param.get_input_layout(1); + + const data_types output_type = impl_param.desc->output_data_types[0].value_or(data_types::i64); + + std::vector input_shapes = { + input0_layout.get(), // sorted shape + input1_layout.get(), // values shape + }; + + std::vector output_shapes; + + ov::op::v15::SearchSorted op; + op.set_right_mode(primitive->right_mode); + output_shapes = shape_infer(&op, input_shapes); + + return {layout{output_shapes[0], output_type, input1_layout.format}}; +} + +std::string search_sorted_inst::to_string(search_sorted_node const& node) { + auto node_info = node.desc_to_json(); + json_composite search_sorted_info; + search_sorted_info.add("sorted id", node.input(0).id()); + search_sorted_info.add("values id", node.input(1).id()); + search_sorted_info.add("right_mode", node.get_primitive()->right_mode); + node_info->add("search_sorted info", search_sorted_info); + std::stringstream primitive_description; + node_info->dump(primitive_description); + return primitive_description.str(); +} + +} // namespace cldnn \ No newline at end of file diff --git a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/search_sorted_ref.cl b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/search_sorted_ref.cl new file mode 100644 index 00000000000000..b9e26405688f12 --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/search_sorted_ref.cl @@ -0,0 +1,56 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "include/batch_headers/fetch_data.cl" + +#if RIGHT_MODE == 0 +#define CMP <= +#else +#define CMP < +#endif + +OUTPUT_TYPE FUNC(binary_search_thread)(const INPUT0_TYPE search_val, + const __global INPUT0_TYPE* restrict sorted, + OUTPUT_TYPE sorted_begin_idx, + OUTPUT_TYPE sorted_end_idx) { + while(sorted_begin_idx != sorted_end_idx) { + const OUTPUT_TYPE half_offset = (sorted_end_idx-sorted_begin_idx)/2; + const OUTPUT_TYPE half_idx = sorted_begin_idx+half_offset; + const INPUT0_TYPE half_val = sorted[half_idx]; + if ( search_val CMP half_val ) + sorted_end_idx = half_idx; + else + sorted_begin_idx = half_idx + 1; + } + + return sorted_begin_idx; +} + +#undef CMP + +KERNEL(search_sorted_ref)( + OPTIONAL_SHAPE_INFO_ARG + const __global INPUT0_TYPE* restrict sorted, + const __global INPUT1_TYPE* restrict values, + __global OUTPUT_TYPE* restrict output) +{ + // INPUT0_TYPE has to be egual to INPUT1_TYPE + const int this_thread_idx = get_global_id(0); + const INPUT0_TYPE search_val = values[this_thread_idx]; + + const int SORTED_STRIDE = INPUT0_BATCH_NUM*INPUT0_FEATURE_NUM*INPUT0_SIZE_Y*INPUT0_SIZE_Z; + + // NOTE: SORTED_STRIDE-1 handles here a special case when sorted is actually 1D + // tensor and values is ND tensor. In such case we effectively want sorted_offset + // to be 0. + const int sorted_offset = min(this_thread_idx/INPUT1_SIZE_X, SORTED_STRIDE-1); + + OUTPUT_TYPE sorted_begin_idx = sorted_offset * INPUT0_SIZE_X; + const OUTPUT_TYPE idx = FUNC_CALL(binary_search_thread)(search_val, + sorted + sorted_begin_idx, + 0, + INPUT0_SIZE_X); + + output[this_thread_idx] = idx; +} \ No newline at end of file diff --git a/src/plugins/intel_gpu/src/kernel_selector/common_types.h b/src/plugins/intel_gpu/src/kernel_selector/common_types.h index bc9cc9f5b8da07..37139dbaeeffd2 100644 --- a/src/plugins/intel_gpu/src/kernel_selector/common_types.h +++ b/src/plugins/intel_gpu/src/kernel_selector/common_types.h @@ -101,7 +101,8 @@ enum class KernelType { RMS, SWIGLU, ROPE, - DYNAMIC_QUANTIZE + DYNAMIC_QUANTIZE, + SEARCH_SORTED }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.cpp new file mode 100644 index 00000000000000..ce4527a1f93aa7 --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.cpp @@ -0,0 +1,72 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "search_sorted_kernel_base.h" + +#include + +#include "kernel_selector_utils.h" + +namespace kernel_selector { +JitConstants SearchSortedKernelBase::GetJitConstants(const search_sorted_params& params) const { + JitConstants jit = MakeBaseParamsJitConstants(params); + + jit.AddConstants({MakeJitConstant("RIGHT_MODE", params.right_mode)}); + + return jit; +} + +void SearchSortedKernelBase::GetUpdateDispatchDataFunc(KernelData& kd) const { + kd.update_dispatch_data_func = [](const Params& params, KernelData& kd) { + const auto& prim_params = static_cast(params); + auto dispatchData = SetDefault(prim_params); + OPENVINO_ASSERT(kd.kernels.size() == 1, "[GPU] Invalid kernels size for update dispatch data func"); + kd.kernels[0].params.workGroups.global = dispatchData.gws; + kd.kernels[0].params.workGroups.local = dispatchData.lws; + kd.kernels[0].skip_execution = KernelData::SkipKernelExecution(prim_params); + }; +} + +SearchSortedKernelBase::DispatchData SearchSortedKernelBase::SetDefault(const search_sorted_params& params) { + DispatchData dispatchData; + dispatchData.gws[0] = params.outputs[0].LogicalSize(); + dispatchData.gws[1] = 1; + dispatchData.gws[2] = 1; + dispatchData.lws = GetOptimalLocalWorkGroupSizes(dispatchData.gws, params.engineInfo); + + return dispatchData; +} + +KernelsData SearchSortedKernelBase::GetCommonKernelsData(const Params& params) const { + assert(params.GetType() == KernelType::SEARCH_SORTED); + + const auto& prim_params = static_cast(params); + + auto dispatchData = SetDefault(prim_params); + KernelData k_data = KernelData::Default(params); + + auto cldnn_jit = GetJitConstants(prim_params); + auto entry_point = GetEntryPoint(kernelName, prim_params.layerID, params); + auto jit = CreateJit(kernelName, cldnn_jit, entry_point); + + GetUpdateDispatchDataFunc(k_data); + + auto& kernel = k_data.kernels[0]; + FillCLKernelData(kernel, + dispatchData, + params.engineInfo, + kernelName, + jit, + entry_point, + "", + false, + false, + 2, + GetFusedPrimitiveInputsCount(params), + 1, + prim_params.is_shape_agnostic); + + return {k_data}; +} +} // namespace kernel_selector diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.h b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.h new file mode 100644 index 00000000000000..734229b6645fd6 --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_base.h @@ -0,0 +1,34 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "kernel_base_opencl.h" +#include "kernel_selector_params.h" + +namespace kernel_selector { +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// search_sorted +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct search_sorted_params : public base_params { + search_sorted_params() : base_params(KernelType::SEARCH_SORTED), right_mode(false) {} + bool right_mode; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SearchSortedKernelBase +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class SearchSortedKernelBase : public KernelBaseOpenCL { +public: + using KernelBaseOpenCL::KernelBaseOpenCL; + + using DispatchData = CommonDispatchData; + +protected: + JitConstants GetJitConstants(const search_sorted_params& params) const; + static DispatchData SetDefault(const search_sorted_params& params); + KernelsData GetCommonKernelsData(const Params& params) const; + void GetUpdateDispatchDataFunc(KernelData& kd) const override; +}; +} // namespace kernel_selector diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.cpp new file mode 100644 index 00000000000000..5bbd22f24ebfec --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "search_sorted_kernel_ref.h" + +namespace kernel_selector { +ParamsKey SearchSortedKernelRef::GetSupportedKey() const { + ParamsKey k; + + k.EnableInputDataType(Datatype::INT8); + k.EnableInputDataType(Datatype::UINT8); + k.EnableInputDataType(Datatype::INT16); + k.EnableInputDataType(Datatype::UINT16); + k.EnableInputDataType(Datatype::INT32); + k.EnableInputDataType(Datatype::UINT32); + k.EnableInputDataType(Datatype::INT64); + k.EnableInputDataType(Datatype::F32); + k.EnableInputDataType(Datatype::F16); + + k.EnableOutputDataType(Datatype::INT32); + k.EnableOutputDataType(Datatype::INT64); + + k.EnableInputLayout(DataLayout::bfyx); + k.EnableInputLayout(DataLayout::bfzyx); + + k.EnableOutputLayout(DataLayout::bfyx); + k.EnableOutputLayout(DataLayout::bfzyx); + + k.EnableTensorOffset(); + k.EnableTensorPitches(); + k.EnableBatching(); + k.EnableDifferentTypes(); + k.EnableDynamicShapesSupport(); + return k; +} + +KernelsData SearchSortedKernelRef::GetKernelsData(const Params& params) const { + return GetCommonKernelsData(params); +} + +KernelsPriority SearchSortedKernelRef::GetKernelsPriority(const Params& /*params*/) const { + return FORCE_PRIORITY_9; +} +} // namespace kernel_selector diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.h b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.h new file mode 100644 index 00000000000000..bc7738013c4867 --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_ref.h @@ -0,0 +1,18 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "search_sorted_kernel_base.h" + +namespace kernel_selector { +class SearchSortedKernelRef : public SearchSortedKernelBase { +public: + SearchSortedKernelRef() : SearchSortedKernelBase("search_sorted_ref") {} + + KernelsData GetKernelsData(const Params& params) const override; + KernelsPriority GetKernelsPriority(const Params& params) const override; + ParamsKey GetSupportedKey() const override; +}; +} // namespace kernel_selector diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.cpp b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.cpp new file mode 100644 index 00000000000000..b83c4d09fd56dd --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "search_sorted_kernel_selector.h" +#include "search_sorted_kernel_ref.h" + +namespace kernel_selector { +search_sorted_kernel_selector::search_sorted_kernel_selector() { Attach(); } + +KernelsData search_sorted_kernel_selector::GetBestKernels(const Params& params) const { + return GetNaiveBestKernel(params, KernelType::SEARCH_SORTED); +} +} // namespace kernel_selector diff --git a/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.h b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.h new file mode 100644 index 00000000000000..25f9a30fb0d895 --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/kernels/search_sorted/search_sorted_kernel_selector.h @@ -0,0 +1,21 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "kernel_selector.h" + +namespace kernel_selector { +class search_sorted_kernel_selector : public kernel_selector_base { +public: + static search_sorted_kernel_selector& Instance() { + static search_sorted_kernel_selector instance; + return instance; + } + + search_sorted_kernel_selector(); + + KernelsData GetBestKernels(const Params& params) const override; +}; +} // namespace kernel_selector diff --git a/src/plugins/intel_gpu/src/plugin/ops/search_sorted.cpp b/src/plugins/intel_gpu/src/plugin/ops/search_sorted.cpp new file mode 100644 index 00000000000000..dbb4fecbd66ab5 --- /dev/null +++ b/src/plugins/intel_gpu/src/plugin/ops/search_sorted.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/op/search_sorted.hpp" + +#include "intel_gpu/plugin/common_utils.hpp" +#include "intel_gpu/plugin/program_builder.hpp" +#include "intel_gpu/primitives/search_sorted.hpp" + +namespace ov { +namespace intel_gpu { + +static void CreateSearchSortedOp(ProgramBuilder& p, const std::shared_ptr& op) { + validate_inputs_count(op, {2}); + auto inputs = p.GetInputInfo(op); + auto prim = cldnn::search_sorted(layer_type_name_ID(op), inputs[0], inputs[1], op->get_right_mode()); + prim.output_data_types = get_output_data_types(op, {{ov::element::i64, ov::element::i32}}); + p.add_primitive(*op, prim); +} + +REGISTER_FACTORY_IMPL(v15, SearchSorted); + +} // namespace intel_gpu +} // namespace ov diff --git a/src/plugins/intel_gpu/src/plugin/program_builder.cpp b/src/plugins/intel_gpu/src/plugin/program_builder.cpp index 899110872ba633..a2316270e1ef3a 100644 --- a/src/plugins/intel_gpu/src/plugin/program_builder.cpp +++ b/src/plugins/intel_gpu/src/plugin/program_builder.cpp @@ -8,6 +8,7 @@ #include "openvino/op/variadic_split.hpp" #include "openvino/op/lstm_cell.hpp" #include "openvino/op/loop.hpp" +#include "openvino/op/search_sorted.hpp" #include "intel_gpu/plugin/common_utils.hpp" #include "intel_gpu/plugin/program_builder.hpp" @@ -349,6 +350,12 @@ bool ProgramBuilder::requires_new_shape_infer(const std::shared_ptr& o return true; } + // HACK: SearchSorted has specific shape requirements. + // E.g. static input shapes: sorted:[8], values:[2,3,4] are prefectly fine, + // but sorted:[8,1,1,1], values:[2,3,4,1] is not valid. + if (ov::is_type(op)) + return true; + if (ov::is_type(op)) { const auto body_function = std::static_pointer_cast(op)->get_function(); if (body_function->is_dynamic()) diff --git a/src/plugins/intel_gpu/tests/functional/shared_tests_instances/single_layer_tests/search_sorted.cpp b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/single_layer_tests/search_sorted.cpp new file mode 100644 index 00000000000000..0117463880a607 --- /dev/null +++ b/src/plugins/intel_gpu/tests/functional/shared_tests_instances/single_layer_tests/search_sorted.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "single_op_tests/search_sorted.hpp" + +namespace ov { +namespace test { + +INSTANTIATE_TEST_SUITE_P(smoke_SearchSortedTest, + SearchSortedLayerTest, + ::testing::Combine(::testing::ValuesIn(SearchSortedLayerTest::GenerateParams()), + testing::Values(ElementType::f32, ElementType::f16, ElementType::i64, ElementType::u32), + testing::Values(ov::test::utils::DEVICE_GPU)), + SearchSortedLayerTest::getTestCaseName); + +} // namespace test +} // namespace ov diff --git a/src/plugins/intel_gpu/tests/unit/test_cases/search_sorted_gpu_test.cpp b/src/plugins/intel_gpu/tests/unit/test_cases/search_sorted_gpu_test.cpp new file mode 100644 index 00000000000000..f9dfa0aeb0fc2b --- /dev/null +++ b/src/plugins/intel_gpu/tests/unit/test_cases/search_sorted_gpu_test.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include "test_utils.h" + +using namespace cldnn; +using namespace ::tests; + +namespace { + +constexpr float EPS = 2e-3f; + +namespace helpers { +// TODO: Move to common place. + +// Converts float vector to another type vector. +template +std::vector ConverFloatVector(const std::vector& vec) { + std::vector ret; + ret.reserve(vec.size()); + for (const auto& val : vec) { + ret.push_back(T(val)); + } + return ret; +} + +// Allocates tensoer with given shape and data. +template +memory::ptr AllocateTensor(ov::PartialShape shape, const std::vector& data) { + const layout lo = {shape, ov::element::from(), cldnn::format::bfyx}; + EXPECT_EQ(lo.get_linear_size(), data.size()); + memory::ptr tensor = get_test_engine().allocate_memory(lo); + set_values(tensor, data); + return tensor; +} +} // namespace helpers + +struct SearchSortedTestParams { + ov::PartialShape sortedShape; + ov::PartialShape valuesShape; + bool rightMode; + std::vector sortedData; + std::vector valuesData; + std::vector expectedOutput; + std::string testcaseName; +}; + +class search_sorted_test : public ::testing::TestWithParam { +public: + static std::string getTestCaseName(const testing::TestParamInfo& obj) { + auto param = obj.param; + std::ostringstream result; + result << "sortedShape=" << param.sortedShape; + result << "_valuesShape=" << param.valuesShape; + result << "_rightMode=" << param.rightMode; + result << "_" << param.testcaseName; + return result.str(); + } + + struct SearchSortedInferenceParams { + bool rightMode; + memory::ptr sorted; + memory::ptr values; + memory::ptr expectedOutput; + }; + + template + SearchSortedInferenceParams PrepareInferenceParams(const SearchSortedTestParams& testParam) { + using T = typename ov::element_type_traits::value_type; + SearchSortedInferenceParams ret; + + ret.rightMode = testParam.rightMode; + + ret.sorted = + helpers::AllocateTensor(testParam.sortedShape, helpers::ConverFloatVector(testParam.sortedData)); + ret.values = + helpers::AllocateTensor(testParam.valuesShape, helpers::ConverFloatVector(testParam.valuesData)); + ret.expectedOutput = helpers::AllocateTensor(testParam.valuesShape, testParam.expectedOutput); + + return ret; + } + + void Execute(const SearchSortedInferenceParams& params) { + // Prepare the network. + auto stream = get_test_stream_ptr(get_test_default_config(engine_)); + + topology topology; + topology.add(input_layout("sorted", params.sorted->get_layout())); + topology.add(input_layout("values", params.values->get_layout())); + topology.add(search_sorted("search_sorted", input_info("sorted"), input_info("values"), params.rightMode)); + + cldnn::network::ptr network = get_network(engine_, topology, get_test_default_config(engine_), stream, false); + + network->set_input_data("sorted", params.sorted); + network->set_input_data("values", params.values); + + // Run and check results. + auto outputs = network->execute(); + + auto output = outputs.at("search_sorted").get_memory(); + cldnn::mem_lock output_ptr(output, get_test_stream()); + cldnn::mem_lock wanted_output_ptr(params.expectedOutput, get_test_stream()); + + ASSERT_EQ(output->get_layout(), params.expectedOutput->get_layout()); + ASSERT_EQ(output_ptr.size(), wanted_output_ptr.size()); + for (size_t i = 0; i < output_ptr.size(); ++i) + ASSERT_TRUE(are_equal(wanted_output_ptr[i], output_ptr[i], EPS)); + } + +private: + engine& engine_ = get_test_engine(); +}; + +std::vector generateTestParams() { + std::vector params; +#define TEST_DATA(sorted_shape, values_shape, right_mode, sorted_data, values_data, expected_output_data, description) \ + params.push_back(SearchSortedTestParams{sorted_shape, \ + values_shape, \ + right_mode, \ + sorted_data, \ + values_data, \ + expected_output_data, \ + description}); + +#include "unit_test_utils/tests_data/search_sorted_data.h" +#undef TEST_DATA + return params; +} + +} // namespace + +#define SEARCH_SORTED_TEST_P(precision) \ + TEST_P(search_sorted_test, ref_comp_##precision) { \ + Execute(PrepareInferenceParams(GetParam())); \ + } + +SEARCH_SORTED_TEST_P(f16); +SEARCH_SORTED_TEST_P(u8); + +INSTANTIATE_TEST_SUITE_P(search_sorted_test_suit, + search_sorted_test, + testing::ValuesIn(generateTestParams()), + search_sorted_test::getTestCaseName); diff --git a/src/tests/functional/shared_test_classes/src/single_op/search_sorted.cpp b/src/tests/functional/shared_test_classes/src/single_op/search_sorted.cpp index a92d87d51f9a10..c7c10ad8767ff6 100644 --- a/src/tests/functional/shared_test_classes/src/single_op/search_sorted.cpp +++ b/src/tests/functional/shared_test_classes/src/single_op/search_sorted.cpp @@ -88,11 +88,30 @@ void SearchSortedLayerTest::SetUp() { const std::vector SearchSortedLayerTest::GenerateParams() { const std::vector params = { + SearchSortedSpecificParams{InputShape{PartialShape::dynamic(3), {{1, 18, 104}}}, + InputShape{PartialShape::dynamic(3), {{1, 18, 104}}}, + true}, + SearchSortedSpecificParams{InputShape{PartialShape::dynamic(4), {{1, 2, 3, 100}}}, + InputShape{PartialShape::dynamic(4), {{1, 2, 3, 10}}}, + true}, + SearchSortedSpecificParams{InputShape{PartialShape::dynamic(5), {{2, 1, 2, 3, 10}}}, + InputShape{PartialShape::dynamic(5), {{2, 1, 2, 3, 20}}}, + false}, + SearchSortedSpecificParams{InputShape{PartialShape::dynamic(1), {{1}}}, + InputShape{PartialShape::dynamic(5), {{2, 1, 2, 3, 20}}}, + false}, + SearchSortedSpecificParams{InputShape{PartialShape::dynamic(1), {{50}}}, + InputShape{{1, -1, 10}, {{1, 18, 10}}}, + false}, SearchSortedSpecificParams{InputShape{{}, {{1, 18, 104}}}, InputShape{{}, {{1, 18, 104}}}, true}, SearchSortedSpecificParams{InputShape{{}, {{1, 2, 3, 100}}}, InputShape{{}, {{1, 2, 3, 10}}}, true}, SearchSortedSpecificParams{InputShape{{}, {{2, 1, 2, 3, 10}}}, InputShape{{}, {{2, 1, 2, 3, 20}}}, false}, SearchSortedSpecificParams{InputShape{{}, {{1}}}, InputShape{{}, {{2, 1, 2, 3, 20}}}, false}, SearchSortedSpecificParams{InputShape{{}, {{50}}}, InputShape{{1, -1, 10}, {{1, 18, 10}}}, false}, + SearchSortedSpecificParams{InputShape{{2, -1, 50}, {{2, 3, 50}}}, + InputShape{{-1, -1, 10}, {{2, 3, 10}}}, + false}, + SearchSortedSpecificParams{InputShape{{2, -1, 50}, {{2, 3, 50}}}, InputShape{{-1, 3, 10}, {{2, 3, 10}}}, false}, }; return params; diff --git a/src/tests/test_utils/unit_test_utils/tests_data/search_sorted_data.h b/src/tests/test_utils/unit_test_utils/tests_data/search_sorted_data.h index ee355c2daee15e..43e680aa080686 100644 --- a/src/tests/test_utils/unit_test_utils/tests_data/search_sorted_data.h +++ b/src/tests/test_utils/unit_test_utils/tests_data/search_sorted_data.h @@ -13,6 +13,22 @@ // NOTE: expected output were generated using pyTorch.searchsorted implementation. +TEST_DATA(LIST(5), + LIST(2, 3), + false, + LIST(3, 3, 3, 3, 3), + LIST(3, 6, 9, 3, 6, 9), + LIST(0, 5, 5, 0, 5, 5), + "1d_tensor_0"); + +TEST_DATA(LIST(5), + LIST(2, 3), + true, + LIST(3, 3, 3, 3, 3), + LIST(3, 6, 9, 3, 6, 9), + LIST(5, 5, 5, 5, 5, 5), + "1d_tensor_0_right_mode"); + TEST_DATA(LIST(5), LIST(2, 3), false, @@ -53,6 +69,22 @@ TEST_DATA(LIST(5), LIST(0, 3, 5, 1, 3, 5, 1, 0, 0, 5, 5, 5), "1d_tensor_3_right_mode"); +TEST_DATA(LIST(1), + LIST(2, 2, 3), + false, + LIST(2), + LIST(0, 6, 20, 2, 6, 9, 1, 0, 0, 9, 10, 20), + LIST(0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1), + "1d_tensor_4"); + +TEST_DATA(LIST(1), + LIST(2, 2, 3), + true, + LIST(2), + LIST(0, 6, 20, 2, 6, 9, 1, 0, 0, 9, 10, 20), + LIST(0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1), + "1d_tensor_4_right_mode"); + TEST_DATA(LIST(2, 5), LIST(2, 3), false, @@ -72,15 +104,15 @@ TEST_DATA(LIST(2, 5), TEST_DATA(LIST(2, 2, 5), LIST(2, 2, 3), false, - LIST(1, 3, 5, 7, 9, 0, 2, 4, 6, 8, -20, 5, 10, 23, 41, 100, 125, 130, 132, 139), + LIST(1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 0, 5, 10, 23, 41, 100, 125, 130, 132, 139), LIST(0, 6, 20, 1, 6, 9, 1, 0, 0, 9, 10, 20), - LIST(0, 3, 5, 1, 3, 5, 1, 1, 1, 0, 0, 0), + LIST(0, 3, 5, 1, 3, 5, 1, 0, 0, 0, 0, 0), "nd_tensor_2"); TEST_DATA(LIST(2, 2, 5), LIST(2, 2, 3), true, - LIST(1, 3, 5, 7, 9, 0, 2, 4, 6, 8, -20, 5, 10, 23, 41, 100, 125, 130, 132, 139), + LIST(1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 0, 5, 10, 23, 41, 100, 125, 130, 132, 139), LIST(0, 6, 20, 1, 6, 9, 1, 0, 0, 9, 10, 20), LIST(0, 3, 5, 1, 4, 5, 1, 1, 1, 0, 0, 0), - "nd_tensor_2"); \ No newline at end of file + "nd_tensor_2_right_mode"); \ No newline at end of file From 6d2e8408f346811aaf2ff7e991edb4c752e5c6c6 Mon Sep 17 00:00:00 2001 From: Bogdan Pereanu Date: Tue, 26 Nov 2024 16:14:40 +0200 Subject: [PATCH 04/24] [NPUW] Disable failing unit test (#27746) ### Details: - *Disable failing unit test* Signed-off-by: Bogdan Pereanu --- src/plugins/intel_npu/tests/unit/npuw/online_partitioning.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/intel_npu/tests/unit/npuw/online_partitioning.cpp b/src/plugins/intel_npu/tests/unit/npuw/online_partitioning.cpp index 99f372694fe3dc..e20e0304123af1 100644 --- a/src/plugins/intel_npu/tests/unit/npuw/online_partitioning.cpp +++ b/src/plugins/intel_npu/tests/unit/npuw/online_partitioning.cpp @@ -664,6 +664,9 @@ TEST(OnlinePartitioningTest, Partitioning_Compiler_Compute_SmallModel) { } TEST(OnlinePartitioningTest, Partitioning_Compiler_Compute_RepeatedModel) { + // Disabling it for now, please enable it after fixing it + GTEST_SKIP() << "Skipping failing test"; + ModelGenerator mg; auto model = mg.get_model_with_repeated_blocks(); From 5b9aa748189c2857c872a42c5cf03d8c4b6846cf Mon Sep 17 00:00:00 2001 From: Karol Blaszczak Date: Tue, 26 Nov 2024 16:13:41 +0100 Subject: [PATCH 05/24] [DOCS] json tweak 24.5 (#27759) --- .../benchmarks_files/data/graph-data-ov.json | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ov.json b/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ov.json index f3a770eae4d067..44b5b5707042df 100644 --- a/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ov.json +++ b/docs/sphinx_setup/_static/benchmarks_files/data/graph-data-ov.json @@ -11729,7 +11729,7 @@ "Model": "stable-diffusion-v1-5", "featured_SKU": false, "whats_new_model": false, - "PlatformType": "Client Platforms (Intel® Core™)", + "PlatformType": "Intel® Core™, iGPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -12324,7 +12324,7 @@ "Model": "stable-diffusion-v1-5", "featured_SKU": false, "whats_new_model": false, - "PlatformType": "Client Platforms (Intel® Core™)", + "PlatformType": "Intel® Core™, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -15509,7 +15509,7 @@ "Model": "stable-diffusion-v1-5", "featured_SKU": false, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -16139,7 +16139,7 @@ "Model": "stable-diffusion-v1-5", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -16769,7 +16769,7 @@ "Model": "stable-diffusion-v1-5", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -16874,7 +16874,7 @@ "Model": "bert-base-cased", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -16909,7 +16909,7 @@ "Model": "efficientdet-d0", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -16944,7 +16944,7 @@ "Model": "mask_rcnn_resnet50_atrous_coco", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -16979,7 +16979,7 @@ "Model": "mobilenet-v2", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17014,7 +17014,7 @@ "Model": "resnet-50", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17049,7 +17049,7 @@ "Model": "ssd-resnet34-1200", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17084,7 +17084,7 @@ "Model": "ssd_mobilenet_v1_coco", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17119,7 +17119,7 @@ "Model": "yolo_v8n", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17154,7 +17154,7 @@ "Model": "gemma-2-9b", "featured_SKU": true, "whats_new_model": true, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17189,7 +17189,7 @@ "Model": "glm-4-9b-chat", "featured_SKU": true, "whats_new_model": true, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17224,7 +17224,7 @@ "Model": "llama-2-7b-chat", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17259,7 +17259,7 @@ "Model": "llama-3.2-3b-instruct", "featured_SKU": true, "whats_new_model": true, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17294,7 +17294,7 @@ "Model": "llama-3-8b", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17329,7 +17329,7 @@ "Model": "mistral-7b-v0.1", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17364,7 +17364,7 @@ "Model": "phi-3-mini-4k-instruct", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17399,7 +17399,7 @@ "Model": "qwen2-7b", "featured_SKU": true, "whats_new_model": true, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ @@ -17434,7 +17434,7 @@ "Model": "stable-diffusion-v1-5", "featured_SKU": true, "whats_new_model": false, - "PlatformType": "Server Platforms (Intel® Xeon®)", + "PlatformType": "Intel® Xeon®, CPU-only", "Parameters": { "throughput": { "Precisions": [ From a88bf5af35e2037f505602cda393ab5d4bbb1a71 Mon Sep 17 00:00:00 2001 From: Michal Miotk Date: Tue, 26 Nov 2024 16:52:44 +0100 Subject: [PATCH 06/24] [GPU] LSTMSequence and LSTMCell optimization (#26767) ### Details: - creating simple primitive for lstm_sequence to be faster than previous approach using many primitives - using oneDNN - based on commit c99ddc0 from 25732 ### Tickets: - 146601 --------- Signed-off-by: Michal Miotk --- .../include/intel_gpu/primitives/lstm.hpp | 148 --------- .../intel_gpu/primitives/lstm_cell.hpp | 24 ++ .../include/intel_gpu/primitives/rnn.hpp | 188 ++++++++++++ .../intel_gpu/runtime/internal_properties.hpp | 1 + .../intel_gpu/src/graph/concatenation.cpp | 2 +- .../graph_optimizer/post_optimize_weights.cpp | 149 ++++++++- .../graph_optimizer/prepare_buffer_fusing.cpp | 9 +- .../prepare_primitive_fusing.cpp | 14 +- .../remove_redundant_reorders.cpp | 2 +- .../graph/graph_optimizer/reorder_inputs.cpp | 4 +- .../select_preferred_formats.cpp | 2 +- .../impls/ocl/kernel_selector_helper.cpp | 2 + .../src/graph/impls/ocl/lstm_cell.cpp | 93 ++++++ .../src/graph/impls/ocl/lstm_cell.hpp | 47 +++ .../src/graph/impls/ocl/lstm_elt.cpp | 137 --------- .../src/graph/impls/ocl/register.cpp | 1 - .../src/graph/impls/ocl/register.hpp | 1 - .../intel_gpu/src/graph/impls/ocl/rnn_seq.cpp | 94 ++++++ .../intel_gpu/src/graph/impls/ocl/rnn_seq.hpp | 46 +++ .../graph/impls/onednn/lstm_seq_onednn.cpp | 217 +++++++++++++ .../graph/impls/onednn/lstm_seq_onednn.hpp | 89 ++++++ .../src/graph/impls/onednn/utils.cpp | 4 + .../graph/impls/registry/lstm_cell_impls.cpp | 27 ++ .../graph/impls/registry/lstm_seq_impls.cpp | 32 ++ .../src/graph/impls/registry/registry.hpp | 3 +- .../src/graph/include/layout_optimizer.h | 32 +- .../src/graph/include/lstm_cell_inst.h | 38 +++ .../src/graph/include/lstm_elt_inst.h | 64 ---- .../src/graph/include/lstm_seq_inst.h | 39 +++ .../src/graph/include/pass_manager.h | 5 + .../src/graph/include/primitive_type_base.h | 4 +- .../intel_gpu/src/graph/layout_optimizer.cpp | 66 +++- src/plugins/intel_gpu/src/graph/lstm_cell.cpp | 51 ++++ src/plugins/intel_gpu/src/graph/lstm_elt.cpp | 84 ----- src/plugins/intel_gpu/src/graph/lstm_seq.cpp | 71 +++++ .../intel_gpu/src/graph/primitive_inst.cpp | 3 +- src/plugins/intel_gpu/src/graph/program.cpp | 47 ++- .../cl_kernels/lstm_cell_and_seq_bfyx.cl | 215 +++++++++++++ .../cl_kernels/lstm_cell_and_seq_ref.cl | 170 +++++++++++ .../cl_kernels/lstm_elt_gpu_bfyx_ref.cl | 44 --- .../src/kernel_selector/common_types.h | 2 +- .../kernel_selector/kernel_selector_params.h | 4 - .../lstm/lstm_cell_and_seq_kernel_bfyx.cpp | 33 ++ .../lstm/lstm_cell_and_seq_kernel_bfyx.h | 19 ++ ...f.cpp => lstm_cell_and_seq_kernel_ref.cpp} | 12 +- ...l_ref.h => lstm_cell_and_seq_kernel_ref.h} | 8 +- .../lstm_cell_and_seq_kernel_selector.cpp | 18 ++ ....h => lstm_cell_and_seq_kernel_selector.h} | 10 +- .../kernels/lstm/lstm_elt_kernel_base.cpp | 94 ------ .../kernels/lstm/lstm_elt_kernel_selector.cpp | 14 - .../kernels/lstm/lstm_kernel_base.cpp | 122 ++++++++ ...m_elt_kernel_base.h => lstm_kernel_base.h} | 31 +- .../kernels/reorder/reorder_kernel_base.cpp | 5 + .../src/kernel_selector/tensor_type.cpp | 1 + .../src/kernel_selector/tensor_type.h | 2 + src/plugins/intel_gpu/src/plugin/graph.cpp | 3 +- src/plugins/intel_gpu/src/plugin/ops/rnn.cpp | 288 ++---------------- .../intel_gpu/src/plugin/program_builder.cpp | 5 + .../src/plugin/transformations_pipeline.cpp | 7 +- .../src/runtime/execution_config.cpp | 7 +- .../behavior/ov_plugin/caching_tests.cpp | 2 +- .../single_layer_tests/lstm_sequence.cpp | 2 +- .../tests/unit/fusions/reduce_fusion_test.cpp | 21 ++ .../unit/module_tests/impls_registry_test.cpp | 2 - .../tests/unit/module_tests/impls_test.cpp | 7 +- ...dd_onednn_optimization_attributes_test.cpp | 2 +- .../tests/unit/passes/handle_reshape.cpp | 2 +- .../passes/select_preferred_formats_test.cpp | 4 +- .../passes/test_module_fusing_reorder.cpp | 12 +- .../tests/unit/test_cases/gemm_gpu_test.cpp | 2 +- .../unit/test_cases/reorder_gpu_test.cpp | 10 +- .../intel_gpu/thirdparty/CMakeLists.txt | 4 +- .../infer_request_dynamic.cpp | 21 +- .../src/behavior/ov_plugin/caching_tests.cpp | 17 +- 74 files changed, 2062 insertions(+), 1000 deletions(-) delete mode 100644 src/plugins/intel_gpu/include/intel_gpu/primitives/lstm.hpp create mode 100644 src/plugins/intel_gpu/include/intel_gpu/primitives/lstm_cell.hpp create mode 100644 src/plugins/intel_gpu/include/intel_gpu/primitives/rnn.hpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.cpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.hpp delete mode 100644 src/plugins/intel_gpu/src/graph/impls/ocl/lstm_elt.cpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.cpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.hpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.cpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.hpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/registry/lstm_cell_impls.cpp create mode 100644 src/plugins/intel_gpu/src/graph/impls/registry/lstm_seq_impls.cpp create mode 100644 src/plugins/intel_gpu/src/graph/include/lstm_cell_inst.h delete mode 100644 src/plugins/intel_gpu/src/graph/include/lstm_elt_inst.h create mode 100644 src/plugins/intel_gpu/src/graph/include/lstm_seq_inst.h create mode 100644 src/plugins/intel_gpu/src/graph/lstm_cell.cpp delete mode 100644 src/plugins/intel_gpu/src/graph/lstm_elt.cpp create mode 100644 src/plugins/intel_gpu/src/graph/lstm_seq.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/cl_kernels/lstm_cell_and_seq_bfyx.cl create mode 100644 src/plugins/intel_gpu/src/kernel_selector/cl_kernels/lstm_cell_and_seq_ref.cl delete mode 100644 src/plugins/intel_gpu/src/kernel_selector/cl_kernels/lstm_elt_gpu_bfyx_ref.cl create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/lstm_cell_and_seq_kernel_bfyx.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/lstm_cell_and_seq_kernel_bfyx.h rename src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/{lstm_elt_kernel_ref.cpp => lstm_cell_and_seq_kernel_ref.cpp} (62%) rename src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/{lstm_elt_kernel_ref.h => lstm_cell_and_seq_kernel_ref.h} (64%) create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/lstm_cell_and_seq_kernel_selector.cpp rename src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/{lstm_elt_kernel_selector.h => lstm_cell_and_seq_kernel_selector.h} (51%) delete mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/lstm_elt_kernel_base.cpp delete mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/lstm_elt_kernel_selector.cpp create mode 100644 src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/lstm_kernel_base.cpp rename src/plugins/intel_gpu/src/kernel_selector/kernels/lstm/{lstm_elt_kernel_base.h => lstm_kernel_base.h} (78%) diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/lstm.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/lstm.hpp deleted file mode 100644 index 76f59e3448e694..00000000000000 --- a/src/plugins/intel_gpu/include/intel_gpu/primitives/lstm.hpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (C) 2018-2024 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#pragma once -#include "primitive.hpp" -#include "activation.hpp" -#include -#include -#include "intel_gpu/graph/serialization/activation_serializer.hpp" - -namespace cldnn { - -/// @brief Weights orders -/// @details Specifies the order in which the weights are concatenated. -/// e.g. [i, o, f, z] : [input, output, forget, block] -/// ONNX order: iofz -/// Caffe order: ifoz -/// pyTorch order: izof -/// OV order: fizo -enum class lstm_weights_order { - iofz, - ifoz, - izof, - fizo -}; - -struct lstm_elt : public primitive_base { - CLDNN_DECLARE_PRIMITIVE(lstm_elt) - - lstm_elt() : primitive_base("", {}), clip(0), input_forget(0), offset_order(lstm_weights_order::iofz), direction(0) {} - - using vec_activation = std::vector; - using vec_activation_param = std::vector; - - /// @brief Constructs lstm layer. - /// @param id This primitive id. - /// @param input input primitive id. - /// @param input cell Primitive id containing cell data. Provide empty string if using lstm without cell values. - /// @param clip Clip threshold. Provide 0 if using lstm without activations clip threshold. - /// @param input_forget Provide 0 if using lstm without coupled input-forget gates. - /// @param offset_order. Order of the concatenated weights, recurrent, and bias. ONNX default is iofz [input, output, forget, block]. - /// @param direction default = 0, bidirectional = 1. - lstm_elt(const primitive_id& id, - const input_info& input, - const primitive_id& cell = "", - const float clip = 0, - const bool input_forget = 0, - const std::vector activations = {activation_func::logistic, - activation_func::hyperbolic_tan, - activation_func::hyperbolic_tan}, - const std::vector activation_params = {}, - const lstm_weights_order offset_order = lstm_weights_order::iofz, - const uint32_t direction = 0) - : primitive_base(id, {input}), - cell(cell), - clip(clip), - input_forget(input_forget), - activations(activations), - activation_params(activation_params), - offset_order(offset_order), - direction(direction) {} - - /// @brief Primitive id containing the initial value of the cell state data. - primitive_id cell; - /// @brief Cell clip threshold T. It is applied to the input of activations [-T, T]. No clip is applied if it is not specified. - float clip; - /// @brief Couple the input and forget gates if input_forget is 1. Default is 0. - bool input_forget; - /// @brief A list of 3 activation functions for the input, output, forget, cell, and hidden. - std::vector activations; - /// @brief Optional scaling values used by some activation functions. The values are consumed in the order of activation functions. - std::vector activation_params; - /// @brief Weights, recurrent weights, and biases order. [iofz] : ONNX, [ifoz] : Caffe - lstm_weights_order offset_order; - /// @brief direction default = 0, bidirectional = 1. - uint32_t direction; - - size_t hash() const override { - size_t seed = primitive::hash(); - seed = hash_combine(seed, clip); - seed = hash_combine(seed, input_forget); - seed = hash_range(seed, activations.begin(), activations.end()); - for (auto& act_param : activation_params) { - seed = hash_combine(seed, act_param.a); - seed = hash_combine(seed, act_param.b); - } - seed = hash_combine(seed, offset_order); - seed = hash_combine(seed, direction); - seed = hash_combine(seed, cell.empty()); - return seed; - } - - bool operator==(const primitive& rhs) const override { - if (!compare_common_params(rhs)) - return false; - - auto rhs_casted = downcast(rhs); - - bool act_params_eq = activation_params.size() == rhs_casted.activation_params.size(); - for (size_t i = 0; i < activation_params.size(); ++i) { - act_params_eq &= activation_params[i].a == rhs_casted.activation_params[i].a && - activation_params[i].b == rhs_casted.activation_params[i].b; - } - - #define cmp_fields(name) name == rhs_casted.name - return act_params_eq && - cmp_fields(clip) && - cmp_fields(input_forget) && - cmp_fields(activations) && - cmp_fields(offset_order) && - cmp_fields(direction) && - cmp_fields(cell.empty()); - #undef cmp_fields - } - - void save(BinaryOutputBuffer& ob) const override { - primitive_base::save(ob); - ob << cell; - ob << clip; - ob << input_forget; - ob << activations; - ob << activation_params; - ob << make_data(&offset_order, sizeof(lstm_weights_order)); - ob << direction; - } - - void load(BinaryInputBuffer& ib) override { - primitive_base::load(ib); - ib >> cell; - ib >> clip; - ib >> input_forget; - ib >> activations; - ib >> activation_params; - ib >> make_data(&offset_order, sizeof(lstm_weights_order)); - ib >> direction; - } - -protected: - std::vector get_dependencies() const override { - std::vector ret; - if (!cell.empty()) - ret.push_back(cell); - return ret; - } -}; - -} // namespace cldnn diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/lstm_cell.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/lstm_cell.hpp new file mode 100644 index 00000000000000..c53840d4bfd0c4 --- /dev/null +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/lstm_cell.hpp @@ -0,0 +1,24 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once +#include "primitive.hpp" +#include "activation.hpp" +#include +#include +#include "intel_gpu/graph/serialization/activation_serializer.hpp" +#include "rnn.hpp" + + +namespace cldnn { + +struct lstm_cell : public RNNParams { + CLDNN_DECLARE_PRIMITIVE(lstm_cell) + using vec_activation = std::vector; + using vec_activation_param = std::vector; + using RNNParams::RNNParams; + lstm_cell(const lstm_cell&) = default; + lstm_cell() : RNNParams() {} +}; +} // namespace cldnn diff --git a/src/plugins/intel_gpu/include/intel_gpu/primitives/rnn.hpp b/src/plugins/intel_gpu/include/intel_gpu/primitives/rnn.hpp new file mode 100644 index 00000000000000..a7c87c25bba235 --- /dev/null +++ b/src/plugins/intel_gpu/include/intel_gpu/primitives/rnn.hpp @@ -0,0 +1,188 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once +#include "primitive.hpp" +#include "activation.hpp" +#include +#include +#include +#include "intel_gpu/graph/serialization/activation_serializer.hpp" + +namespace cldnn { + +/// @brief Weights orders +/// @details Specifies the order in which the weights are concatenated. +/// e.g. [i, o, f, z] : [input, output, forget, block] +/// ONNX order: iofz +/// Caffe order: ifoz +/// pyTorch order: izof +/// OV order: fizo +enum class lstm_weights_order { + iofz, + ifoz, + izof, + fizo +}; + +template +struct RNNParams : public primitive_base { + RNNParams() : primitive_base("", {}) {} + RNNParams(const RNNParams&) = default; + RNNParams(const primitive_id& id, + const input_info& x, + const input_info& initial_hidden_state, + const input_info& initial_cell_state, + const input_info& W, + const input_info& R, + const input_info& B, + const input_info& seq_lenghts, + const float clip = 0, + bool input_forget = false, + const std::vector& activations = {activation_func::logistic, + activation_func::hyperbolic_tan, + activation_func::hyperbolic_tan}, + const std::vector& activation_params = {}, + const lstm_weights_order& offset_order = lstm_weights_order::iofz, + const ov::op::RecurrentSequenceDirection direction = ov::op::RecurrentSequenceDirection::FORWARD, + const int num_outputs = 1) + : primitive_base(id, {x}, num_outputs), + x(x), + initial_hidden_state(initial_hidden_state), + initial_cell_state(initial_cell_state), + W(W), + R(R), + B(B), + seq_lenghts(seq_lenghts), + clip(clip), + input_forget(input_forget), + activations(activations), + activation_params(activation_params), + offset_order(offset_order), + direction(direction) { + std::vector pids{initial_hidden_state.pid, initial_cell_state.pid, W.pid, R.pid, B.pid, seq_lenghts.pid}; + for (auto pid : pids) { + if (!pid.empty()) { + primitive_base::input.push_back(pid); + } + } + } + + input_info x; + input_info initial_hidden_state; + input_info initial_cell_state; + input_info W; + input_info R; + input_info B; + input_info seq_lenghts; + /// @brief Cell clip threshold T. It is applied to the input of activations [-T, T]. No clip is applied if it is not specified. + float clip; + bool input_forget; + /// @brief A list of 3 activation functions for the input, output, forget, cell, and hidden. + std::vector activations; + /// @brief Optional scaling values used by some activation functions. The values are consumed in the order of activation functions. + std::vector activation_params; + /// @brief Weights, recurrent weights, and biases order. [iofz] : ONNX, [ifoz] : Caffe + lstm_weights_order offset_order; + /// @brief direction of LSTMSequence - only FORWARD or REVERSE, currently BIDIRECTIONAL not supported + ov::op::RecurrentSequenceDirection direction; + + int num_directions() const { + return direction == ov::op::RecurrentSequenceDirection::BIDIRECTIONAL ? 2 : 1; + } + + size_t hash() const override { + size_t seed = primitive::hash(); + seed = hash_combine(seed, !x.pid.empty()); + seed = hash_combine(seed, !initial_hidden_state.pid.empty()); + seed = hash_combine(seed, !initial_cell_state.pid.empty()); + seed = hash_combine(seed, !seq_lenghts.pid.empty()); + seed = hash_combine(seed, !W.pid.empty()); + seed = hash_combine(seed, !R.pid.empty()); + seed = hash_combine(seed, !B.pid.empty()); + seed = hash_combine(seed, clip); + seed = hash_range(seed, activations.begin(), activations.end()); + for (auto& act_param : activation_params) { + seed = hash_combine(seed, act_param.a); + seed = hash_combine(seed, act_param.b); + } + seed = hash_combine(seed, offset_order); + seed = hash_combine(seed, direction); + return seed; + } + + bool operator==(const primitive& rhs) const override { + if (!primitive::compare_common_params(rhs)) + return false; + + auto rhs_casted = downcast(rhs); + bool act_params_eq = activation_params.size() == rhs_casted.activation_params.size(); + for (size_t i = 0; i < activation_params.size(); ++i) { + act_params_eq &= activation_params[i].a == rhs_casted.activation_params[i].a && + activation_params[i].b == rhs_casted.activation_params[i].b; + } + + #define cmp_fields(name) name == rhs_casted.name + return act_params_eq && + cmp_fields(x.pid.empty()) && + cmp_fields(initial_hidden_state.pid.empty()) && + cmp_fields(initial_cell_state.pid.empty()) && + cmp_fields(seq_lenghts.pid.empty()) && + cmp_fields(W.pid.empty()) && + cmp_fields(R.pid.empty()) && + cmp_fields(B.pid.empty()) && + cmp_fields(clip) && + cmp_fields(activations) && + cmp_fields(offset_order) && + cmp_fields(direction); + #undef cmp_fields + } + + void save(BinaryOutputBuffer& ob) const override { + primitive_base::save(ob); + ob << x; + ob << initial_hidden_state; + ob << initial_cell_state; + ob << W; + ob << R; + ob << B; + ob << seq_lenghts; + ob << clip; + ob << activations; + ob << activation_params; + ob << make_data(&offset_order, sizeof(lstm_weights_order)); + ob << make_data(&direction, sizeof(ov::op::RecurrentSequenceDirection)); + } + + void load(BinaryInputBuffer& ib) override{ + primitive_base::load(ib); + ib >> x; + ib >> initial_hidden_state; + ib >> initial_cell_state; + ib >> W; + ib >> R; + ib >> B; + ib >> seq_lenghts; + ib >> clip; + ib >> activations; + ib >> activation_params; + ib >> make_data(&offset_order, sizeof(lstm_weights_order)); + ib >> make_data(&direction, sizeof(ov::op::RecurrentSequenceDirection)); + } +}; + +struct lstm_seq : public RNNParams { + CLDNN_DECLARE_PRIMITIVE(lstm_seq) + using vec_activation = std::vector; + using vec_activation_param = std::vector; + using RNNParams::RNNParams; + lstm_seq() : RNNParams() { + weights = W.pid; + input = x.pid; + } + lstm_seq(const lstm_seq&) = default; + primitive_id input; + primitive_id weights; +}; +} //namespace cldnn diff --git a/src/plugins/intel_gpu/include/intel_gpu/runtime/internal_properties.hpp b/src/plugins/intel_gpu/include/intel_gpu/runtime/internal_properties.hpp index febcabd57efba0..199261772dcf2e 100644 --- a/src/plugins/intel_gpu/include/intel_gpu/runtime/internal_properties.hpp +++ b/src/plugins/intel_gpu/include/intel_gpu/runtime/internal_properties.hpp @@ -56,6 +56,7 @@ static constexpr Property max_dynamic_batch{"DYN static constexpr Property nv12_two_inputs{"GPU_NV12_TWO_INPUTS"}; static constexpr Property buffers_preallocation_ratio{"GPU_BUFFERS_PREALLOCATION_RATIO"}; static constexpr Property max_kernels_per_batch{"GPU_MAX_KERNELS_PER_BATCH"}; +static constexpr Property use_onednn{"USE_ONEDNN"}; } // namespace intel_gpu } // namespace ov diff --git a/src/plugins/intel_gpu/src/graph/concatenation.cpp b/src/plugins/intel_gpu/src/graph/concatenation.cpp index b493bb217b1c32..3f1e1b03a2c564 100644 --- a/src/plugins/intel_gpu/src/graph/concatenation.cpp +++ b/src/plugins/intel_gpu/src/graph/concatenation.cpp @@ -114,7 +114,7 @@ concatenation_inst::typed_primitive_inst(network& network, concatenation_node co auto input_size = input_layout.get_dims(); auto output_size = output_layout.get_dims(); for (const auto& i : node.get_dependencies()) { - auto input_i_layout = i.first->get_output_layout(); + auto input_i_layout = i.first->get_output_layout(false, i.second); auto input_mem_size = input_i_layout.get_dims(); for (int64_t dim = 0; dim < static_cast(output_layout.get_rank()); ++dim) { if (dim == node.get_primitive()->axis) { diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/post_optimize_weights.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/post_optimize_weights.cpp index 9805b45ad005ed..377e0eef2402f0 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/post_optimize_weights.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/post_optimize_weights.cpp @@ -9,7 +9,10 @@ #include "convolution_inst.h" #include "deconvolution_inst.h" #include "fully_connected_inst.h" +#include "lstm_seq_inst.h" #include "intel_gpu/runtime/format.hpp" +#include "permute_inst.h" +#include "crop_inst.h" #ifdef ENABLE_ONEDNN_FOR_GPU #include "graph/impls/onednn/utils.hpp" #endif // ENABLE_ONEDNN_FOR_GPU @@ -18,10 +21,17 @@ namespace cldnn { post_optimize_weights::post_optimize_weights(reorder_factory& rf_ref) : base_pass("post_optimize_weights"), _rf(rf_ref) {} -template post_optimize_weights::weights_bias_offset post_optimize_weights::get_weights_bias_offset(const T& node) { +template +post_optimize_weights::weights_bias_offset post_optimize_weights::get_weights_bias_offset(const T& node) { return weights_bias_offset(node.get_primitive()->input.size(), program_helpers::wrap_if_single(node.get_primitive()->weights).size()); } +template <> +post_optimize_weights::weights_bias_offset post_optimize_weights::get_weights_bias_offset(const lstm_seq_node& node) { + const int W_idx = 3; + return weights_bias_offset(W_idx, 3); +} + // function which prepares given primitive for weights optimization template void post_optimize_weights::optimize_weights(T& node, program& p) { @@ -109,15 +119,26 @@ void post_optimize_weights::optimize_weights(T& node, program& p) { set_implementation(weights_reorder_node); } } else { - auto weights_reorder = _rf.get_weights_reorder(prev_node.id(), weights_reorder_params); - // insert new weights reorder node to topology - p.add_intermediate(weights_reorder.first, node, i, !weights_reorder.second); - // set weights reorder's node output layout and implementation - auto& weights_reorder_node = node.get_dependency(i); - weights_reorder_node.get_output_layout(false); + if (node.type() == lstm_seq::type_id()) { + program_node& prev_node = node.get_dependency(i); + if (i == 5) { + add_lstm_bias_reorder(prev_node.id(), weights_reorder_params, p, prev_node, node); + } else { + add_lstm_weights_reorder(prev_node.id(), weights_reorder_params, p, prev_node, node, i); + } + auto& weights_reorder_node = node.get_dependency(i); + weights_reorder_node.get_output_layout(false); + } else { + auto weights_reorder = _rf.get_weights_reorder(prev_node.id(), weights_reorder_params); + // insert new weights reorder node to topology + p.add_intermediate(weights_reorder.first, node, i, !weights_reorder.second); + // set weights reorder's node output layout and implementation + auto& weights_reorder_node = node.get_dependency(i); + weights_reorder_node.get_output_layout(false); - if (!weights_reorder.second) { - set_implementation(weights_reorder_node); + if (!weights_reorder.second) { + set_implementation(weights_reorder_node); + } } } } @@ -126,7 +147,112 @@ void post_optimize_weights::optimize_weights(T& node, program& p) { node.set_output_layout(output_layout, false); } +void post_optimize_weights::select_implementation(program& p, program_node& node) { + node.set_selected_impl(node.type()->create_impl(node)); + if (auto impl = node.get_selected_impl()) { + auto params = node.get_kernel_impl_params(); + p.get_kernels_cache().add_kernels_source(*params, impl->get_kernels_source()); + } +} + +void post_optimize_weights::add_lstm_weights_reorder(primitive_id input_id, std::shared_ptr reorder_params, program& p, \ + cldnn::program_node& prev, cldnn::program_node& node, size_t i) { + OPENVINO_ASSERT(reorder_params != nullptr, "[GPU] WeightsReorderParams is not initialized."); + std::string reorder_id = input_id + "_reo_" + std::to_string(i); + const auto dir_num = static_cast(reorder_params->get_input_layout().get_shape()[0]); + auto hiddenSize = reorder_params->get_input_layout().get_shape()[1] / 4; + auto inputSize = static_cast(reorder_params->get_input_layout().get_shape()[2]); + int size_third; + const int W_idx = 3; + if (i == W_idx) { + size_third = inputSize; + } else { + size_third = static_cast(hiddenSize); + } + auto cropSizeR = cldnn::tensor{dir_num, static_cast(hiddenSize), 1, size_third}; + std::string crop_id_b = input_id + "_c"; + auto get_crop_node = [&](int cropNum) -> cldnn::program_node& { + auto crop_id = primitive_id(crop_id_b + std::to_string(cropNum)); + auto crop_prim = std::make_shared(crop_id, reorder_id, cropSizeR, cldnn::tensor{0, static_cast(cropNum*hiddenSize), 0, 0}); + return p.get_or_create(crop_prim); + }; + + auto& crop0_node = get_crop_node(0); + auto& crop1_node = get_crop_node(1); + auto crop2_id = primitive_id(crop_id_b + std::to_string(2)); + auto crop2_prim = std::make_shared(crop2_id, reorder_id, cldnn::tensor{dir_num, static_cast(2*hiddenSize), 1, size_third}, + cldnn::tensor{0, static_cast(2*hiddenSize), 0, 0}); + auto& crop2_node = p.get_or_create(crop2_prim); + std::vector con_input{input_info(crop_id_b + "1"), input_info(crop_id_b + "0"), input_info(crop_id_b + "2")}; + cldnn::primitive_id concat_id{input_id + "cont"}; + auto con = std::make_shared(concat_id, con_input, 1); + auto& con_node = p.get_or_create(con); + p.add_intermediate(con_node, node, prev, true); + p.add_intermediate(crop1_node, con_node, prev, true); + p.add_connection(prev, crop0_node, 0); + p.add_connection(prev, crop2_node, 0); + p.add_connection(crop0_node, con_node, 0); + p.add_connection(crop2_node, con_node, 0); + std::string permute_id = input_id + "_perx"; + std::vector ord{0, 2, 1}; + auto permute = std::make_shared(permute_id, input_info{concat_id}, ord); + auto& permute_node = p.get_or_create(permute); + p.add_intermediate(permute_node, node, con_node, true); + auto set_implementation_and_output = [this, &p](program_node& node) { + node.get_output_layout(false); + select_implementation(p, node); + p.mark_if_constant(node); + node.recalc_output_layout(false); + }; + set_implementation_and_output(crop1_node); + set_implementation_and_output(crop0_node); + set_implementation_and_output(crop2_node); + set_implementation_and_output(con_node); + set_implementation_and_output(permute_node); +} + +void post_optimize_weights::add_lstm_bias_reorder(primitive_id input_id, std::shared_ptr reorder_params, program& p, \ + cldnn::program_node& prev, cldnn::program_node& node) { + OPENVINO_ASSERT(reorder_params != nullptr, "[GPU] WeightsReorderParams is not initialized."); + const auto dir_num = static_cast(reorder_params->get_input_layout().get_shape()[0]); + auto hiddenSize = reorder_params->get_output_layout().get_shape()[1] / 4; + auto cropSize = cldnn::tensor{dir_num, static_cast(hiddenSize), 1, 1}; + std::string crop_id_b = input_id + "_c"; + auto get_crop_node = [&](int cropNum) -> cldnn::program_node& { + auto crop_id = primitive_id(crop_id_b + std::to_string(cropNum)); + auto crop_prim = std::make_shared(crop_id, input_id, cropSize, cldnn::tensor{0, static_cast(cropNum*hiddenSize), 0, 0}); + return p.get_or_create(crop_prim); + }; + auto& crop0_node = get_crop_node(0); + auto& crop1_node = get_crop_node(1); + auto crop2_id = primitive_id(crop_id_b + std::to_string(2)); + auto crop2_prim = std::make_shared(crop2_id, input_id, cldnn::tensor{dir_num, static_cast(2*hiddenSize), 1, 1}, + cldnn::tensor{0, static_cast(2*hiddenSize), 0, 0}); + auto& crop2_node = p.get_or_create(crop2_prim); + std::vector con_input{input_info(crop1_node.id()), input_info(crop0_node.id()), input_info(crop2_node.id())}; + cldnn::primitive_id concat_id{input_id + "concat"}; + auto con = std::make_shared(concat_id, con_input, 1); + auto& con_node = p.get_or_create(con); + p.add_intermediate(con_node, node, prev, true); + p.add_intermediate(crop1_node, con_node, prev, true); + p.add_connection(prev, crop0_node, 0); + p.add_connection(prev, crop2_node, 0); + p.add_connection(crop0_node, con_node, 0); + p.add_connection(crop2_node, con_node, 0); + auto set_implementation_and_output = [this, &p](program_node& node) { + node.get_output_layout(false); + select_implementation(p, node); + p.mark_if_constant(node); + node.recalc_output_layout(false); + }; + set_implementation_and_output(crop0_node); + set_implementation_and_output(crop1_node); + set_implementation_and_output(crop2_node); + set_implementation_and_output(con_node); +} + void post_optimize_weights::run(program& p) { + bool found_lstm = false; for (auto& node : p.get_processing_order()) { if (node->is_type()) { optimize_weights(node->as(), p); @@ -134,8 +260,13 @@ void post_optimize_weights::run(program& p) { optimize_weights(node->as(), p); } else if (node->is_type()) { optimize_weights(node->as(), p); + } else if (node->is_type()) { + optimize_weights(node->as(), p); + found_lstm = true; } } + if (found_lstm) + p.get_processing_order().calc_processing_order(p); } } // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp index e94714c84fdebf..de7f51b071ae53 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_buffer_fusing.cpp @@ -17,11 +17,12 @@ #include "depth_to_space_inst.h" #include "resample_inst.h" #include "loop_inst.h" -#include "lstm_elt_inst.h" +#include "lstm_cell_inst.h" #include "strided_slice_inst.h" #include "shape_of_inst.h" #include "non_max_suppression_inst.h" #include "experimental_detectron_roi_feature_extractor_inst.hpp" +#include "lstm_seq_inst.h" #include "border_inst.h" #include "pass_manager.h" @@ -504,6 +505,8 @@ bool crop_in_place_optimization::match(const program_node& node, } if (user->is_type() && user->get_dependency_index(node) == 0) return false; + if (user->is_type() || user->is_type()) + return false; } // do not optimize crop, that must be calculated in propagate_constants @@ -519,10 +522,6 @@ bool crop_in_place_optimization::match(const program_node& node, return false; if (node.get_users().size() > 0) { - if (node.get_program().is_body_program() && node.get_dependency(0).is_type()) { - return false; - } - GPU_DEBUG_GET_INSTANCE(debug_config); GPU_DEBUG_IF(debug_config->disable_runtime_buffer_fusing && node.is_dynamic()) { return false; diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_primitive_fusing.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_primitive_fusing.cpp index c323109850c489..60d1e8aa7e10b7 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_primitive_fusing.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/prepare_primitive_fusing.cpp @@ -439,7 +439,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { }; auto conv_supports_fusings = [&](convolution_node& node) -> bool { - if (lo.get_optimization_attributes().use_onednn_impls == 1 && + if (lo.has_all_enabled_onednn_impls_optimization_attribute() && lo.get_preferred_impl_type(node, format::byxf /*dummy value to disable format checking*/) == impl_types::onednn) { return true; } @@ -491,7 +491,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { }; auto fc_supports_fusings = [&](fully_connected_node& node) -> bool { - if (lo.get_optimization_attributes().use_onednn_impls && + if (lo.has_all_enabled_onednn_impls_optimization_attribute() && lo.get_preferred_impl_type(node, format::any /*dummy*/) == impl_types::onednn) { return true; } else { @@ -589,7 +589,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { // Do not fuse if the estimated format is fs_b_yx_fsv32 because the optimized kernel does not support fusion if (out_layout.data_type == data_types::f16 && out_layout.is_static() && out_layout.batch() > 1 && ((lo.get_optimization_attributes().fs_b_yx_fsv32_network && - !lo.get_optimization_attributes().use_onednn_impls && !has_reorder_behind_mvn()) || + !lo.has_all_enabled_onednn_impls_optimization_attribute() && !has_reorder_behind_mvn()) || out_layout.format == format::fs_b_yx_fsv32)) { return false; } @@ -665,7 +665,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { if (input.in_shape_of_subgraph || node->in_shape_of_subgraph) return; - if (lo.get_optimization_attributes().use_onednn_impls) { + if (lo.has_all_enabled_onednn_impls_optimization_attribute()) { if (input.is_type() || input.is_type()) return; auto additional_params_input = activation_node.get_primitive()->additional_params_input; @@ -768,7 +768,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { return; // Onednn reorder does not support eltwise nor binary post operation - if (lo.get_optimization_attributes().use_onednn_impls && input.is_type()) { + if (lo.has_all_enabled_onednn_impls_optimization_attribute() && input.is_type()) { return; } @@ -809,7 +809,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { (lo.should_select_b_fs_yx_fsv16_layout(input_data.as(), input_data.get_input_layout(1)) && !is_grouped_conv(input_data.as())) || // Avoid fusing to b_fs_yx_fsv16 (and similar) kernels - lo.get_optimization_attributes().use_onednn_impls || + (lo.has_all_enabled_onednn_impls_optimization_attribute()) || (in_dt_is_i8_u8 && out_dt_is_i8_u8)); should_fuse |= input_data.is_type() && quantize_node.get_scale_shift_opt(); @@ -1067,7 +1067,7 @@ void prepare_primitive_fusing::fuse_simple_primitives(program &p) { } } - if (lo.get_optimization_attributes().use_onednn_impls && lo.is_primitive_implemented_for_onednn(*fused_node)) { + if (lo.has_all_enabled_onednn_impls_optimization_attribute() && lo.is_primitive_implemented_for_onednn(*fused_node)) { auto eltw_in_size = peer_node->get_output_layout(); if (eltw_in_size.is_dynamic() // this whitelist condition is temporarily and to be relaxed soon. diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp index 28ee84c4a4ec02..1e5f943600fc05 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/remove_redundant_reorders.cpp @@ -489,7 +489,7 @@ void remove_redundant_reorders::run(program& p) { (dep.get_output_layout().format == format::b_fs_yx_fsv16 || dep.get_output_layout().format == format::bfyx || (dep.get_output_layout().format == format::fs_b_yx_fsv32 && - !lo.get_optimization_attributes().use_onednn_impls)); + !lo.has_all_enabled_onednn_impls_optimization_attribute())); auto convert_color_opt = usr->is_type() && prim_desc->has_surface_input(); diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/reorder_inputs.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/reorder_inputs.cpp index 4f15800c70970b..b29be318593348 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/reorder_inputs.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/reorder_inputs.cpp @@ -56,9 +56,9 @@ std::map get_preferred_formats(program& p, layout_o onednn_impls_counter++; } - if (lo.get_optimization_attributes().use_onednn_impls && onednn_impls_counter < 1) { + if (!lo.is_empty_onednn_impls_optimization_attribute() && onednn_impls_counter < 1) { should_update_fmt_map = true; - lo.set_optimization_attribute(layout_optimizer::optimization_attributes_type::use_onednn_impls, 0); + lo.clear_onednn_impls_optimization_attribute(); GPU_DEBUG_LOG << "Disable oneDNN implementations globally" << std::endl; } diff --git a/src/plugins/intel_gpu/src/graph/graph_optimizer/select_preferred_formats.cpp b/src/plugins/intel_gpu/src/graph/graph_optimizer/select_preferred_formats.cpp index 8a1197dfb843a6..fcd6dab33754fd 100644 --- a/src/plugins/intel_gpu/src/graph/graph_optimizer/select_preferred_formats.cpp +++ b/src/plugins/intel_gpu/src/graph/graph_optimizer/select_preferred_formats.cpp @@ -69,7 +69,7 @@ void select_preferred_formats::run(program& p) { #ifdef ENABLE_ONEDNN_FOR_GPU auto& engine = p.get_engine(); - if (p.get_layout_optimizer().get_optimization_attributes().use_onednn_impls) { + if (!p.get_layout_optimizer().is_empty_onednn_impls_optimization_attribute()) { engine.create_onednn_engine(p.get_config()); } #endif // ENABLE_ONEDNN_FOR_GPU diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp index 409fd824063da6..0a999a5a124d3b 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/kernel_selector_helper.cpp @@ -309,6 +309,8 @@ kernel_selector::data_layout to_data_layout(format f) { return kernel_selector::data_layout::bfzyx; case format::bzyxf: return kernel_selector::data_layout::bzyxf; + case format::ybfx: + return kernel_selector::data_layout::ybfx; case format::fs_b_yx_fsv32: return kernel_selector::data_layout::fs_b_yx_fsv32; case format::bfwzyx: diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.cpp new file mode 100644 index 00000000000000..a41cd1065122de --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.cpp @@ -0,0 +1,93 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "primitive_base.hpp" + +#include "lstm_cell_inst.h" +#include "lstm/lstm_cell_and_seq_kernel_selector.h" +#include "lstm/lstm_kernel_base.h" +#include "openvino/op/lstm_cell.hpp" +#include "lstm_cell.hpp" + +namespace cldnn { +namespace ocl { + +struct lstm_cell_impl : typed_primitive_impl_ocl { + using parent = typed_primitive_impl_ocl; + using parent::parent; + using kernel_selector_t = kernel_selector::lstm_cell_and_seq_kernel_selector; + using kernel_params_t = kernel_selector::lstm_params; + + DECLARE_OBJECT_TYPE_SERIALIZATION(cldnn::ocl::lstm_cell_impl) + + std::unique_ptr clone() const override { + return make_unique(*this); + } + +protected: + kernel_arguments_data get_arguments(const typed_primitive_inst& instance) const override { + kernel_arguments_data args; + for (size_t i = 0; i < instance.inputs_memory_count(); i++) { + args.inputs.push_back(instance.input_memory_ptr(i)); + } + + for (size_t i = 0; i < instance.outputs_memory_count(); i++) { + args.outputs.push_back(instance.output_memory_ptr(i)); + } + return args; + } + +public: + static kernel_params_t get_kernel_params(const kernel_impl_params& impl_param) { + const auto& primitive = impl_param.typed_desc(); + auto params = get_default_params(impl_param); + for (size_t i = 1; i < 6; ++i) { + params.inputs.push_back(convert_data_tensor(impl_param.get_input_layout(i))); + } + + if (!primitive->activations.empty()) { + auto a_sz = primitive->activations.size(); + auto param_sz = primitive->activation_params.size(); + OPENVINO_ASSERT(param_sz == 0 || a_sz == param_sz, "[GPU] Unexpected activation params count in lstm_cell impl: ", param_sz); + for (size_t i = 0; i < a_sz; i++) { + params.activations.emplace_back(get_kernel_selector_activation_param(primitive->activations[i]), + param_sz ? primitive->activation_params[i].a : 0.0f, + param_sz ? primitive->activation_params[i].b : 0.0f); + } + } + + if (primitive->clip > 0.0f) { + params.activations.emplace_back(get_kernel_selector_activation_param(activation_func::clamp), -primitive->clip, primitive->clip); + } + + params.SetOffsetOrder(static_cast(primitive->offset_order)); + params.clip = primitive->clip; + params.direction = primitive->direction; + + return params; + } + + static kernel_impl_params static_canonicalize_shapes(const kernel_impl_params& impl_params) { + if (impl_params.get_input_layout().get_partial_shape().size() != 3) { + return primitive_impl::static_canonicalize_shapes(impl_params); + } + auto updated_impl_params = canonicalize_fused_shapes(impl_params); + return updated_impl_params; + } + + kernel_impl_params canonicalize_shapes(const kernel_impl_params& impl_params) const override { + return static_canonicalize_shapes(impl_params); + } +}; + +std::unique_ptr LSTMCellImplementationManager::create_impl(const program_node& node, const kernel_impl_params& params) const { + OPENVINO_ASSERT(node.is_type()); + return typed_primitive_impl_ocl::create(static_cast(node), params); +} + +} // namespace ocl +} // namespace cldnn + +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::ocl::lstm_cell_impl) +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::lstm_cell) diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.hpp b/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.hpp new file mode 100644 index 00000000000000..731bacf2e17e4f --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_cell.hpp @@ -0,0 +1,47 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "lstm_cell_inst.h" +#include "impls/registry/implementation_manager.hpp" +#include "intel_gpu/runtime/layout.hpp" + +#include +namespace cldnn { +namespace ocl { + +struct LSTMCellImplementationManager: public ImplementationManager { + OV_GPU_PRIMITIVE_IMPL("ocl::lstm_cell") + LSTMCellImplementationManager(shape_types shape_type, ValidateFunc vf = nullptr) : ImplementationManager(impl_types::ocl, shape_type, vf) {} + + std::unique_ptr create_impl(const program_node& node, const kernel_impl_params& params) const override; + + bool validate_impl(const program_node& node) const override { + assert(node.is_type()); + + const auto& input_layout = node.get_input_layout(0); + const auto& output_layout = node.get_output_layout(0); + + auto input_fmt = input_layout.format; + auto output_fmt = output_layout.format; + auto in_dt = input_layout.data_type; + auto out_dt = output_layout.data_type; + static const std::vector supported_formats = { + format::bfyx, + format::fyxb, + }; + static const std::vector supported_data_types = { + data_types::f32, + data_types::f16, + }; + + if (!one_of(in_dt, supported_data_types) || !one_of(out_dt, supported_data_types)) { + return false; + } + + return one_of(input_fmt.value, supported_formats) && one_of(output_fmt.value, supported_formats); + } +}; + +} // namespace ocl +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_elt.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_elt.cpp deleted file mode 100644 index 5de12d83fdbab3..00000000000000 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/lstm_elt.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2018-2024 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#include "primitive_base.hpp" - -#include "lstm_elt_inst.h" -#include "lstm/lstm_elt_kernel_selector.h" -#include "lstm/lstm_elt_kernel_base.h" - -namespace cldnn { -namespace ocl { - -struct lstm_elt_impl : typed_primitive_impl_ocl { - using parent = typed_primitive_impl_ocl; - using parent::parent; - using kernel_selector_t = kernel_selector::lstm_elt_kernel_selector; - using kernel_params_t = kernel_selector::lstm_elt_params; - - DECLARE_OBJECT_TYPE_SERIALIZATION(cldnn::ocl::lstm_elt_impl) - - std::unique_ptr clone() const override { - return make_deep_copy(*this); - } - -protected: - kernel_arguments_data get_arguments(const typed_primitive_inst& instance) const override { - kernel_arguments_data args = parent::get_arguments(instance); - - args.cell = instance.cell_term() ? instance.cell_memory() : nullptr; - args.outputs = { instance.output_memory_ptr() }; - - return args; - } - -public: - static kernel_params_t get_kernel_params(const kernel_impl_params& impl_param) { - const auto& primitive = impl_param.typed_desc(); - auto params = get_default_params(impl_param); - - if (!primitive->cell.empty()) { - const auto& cell_idx = 1; - const auto& cell_layout = impl_param.input_layouts[cell_idx]; - params.SetCell(convert_data_tensor(cell_layout)); - // TODO: make a generic function to get the direction - if (cell_layout.spatial(1) > 1) { - params.cell_direction = primitive->direction; - } - } - - if (!primitive->activations.empty()) { - auto a_sz = primitive->activations.size(); - auto param_sz = primitive->activation_params.size(); - OPENVINO_ASSERT(param_sz == 0|| a_sz == param_sz, "[GPU] Unexpected activation params count in lstm_elt impl: ", param_sz); - for (size_t i = 0; i < a_sz; i++) { - params.activations.emplace_back(get_kernel_selector_activation_param(primitive->activations[i]), - param_sz ? primitive->activation_params[i].a : 0.0f, - param_sz ? primitive->activation_params[i].b : 0.0f); - } - } - - if (primitive->clip > 0.0f) { - params.activations.emplace_back(get_kernel_selector_activation_param(activation_func::clamp), -primitive->clip, primitive->clip); - } - - params.SetOffsetOrder(static_cast(primitive->offset_order)); - params.clip = primitive->clip; - params.input_forget = primitive->input_forget; - params.direction = primitive->direction; - - return params; - } - - static kernel_impl_params static_canonicalize_shapes(const kernel_impl_params& impl_params) { - if (impl_params.get_input_layout().get_partial_shape().size() != 2) { - return primitive_impl::static_canonicalize_shapes(impl_params); - } - auto updated_impl_params = canonicalize_fused_shapes(impl_params); - - auto& input_layout = updated_impl_params.input_layouts[0]; - auto& weights_layout = updated_impl_params.input_layouts[1]; - auto& output_layout = updated_impl_params.output_layouts[0]; - - auto input_pshape = input_layout.get_partial_shape(); - auto weights_pshape = weights_layout.get_partial_shape(); - auto output_pshape = output_layout.get_partial_shape(); - - auto lstm_input_size = static_cast(input_pshape[1].get_length()); - auto lstm_batch_size = static_cast(input_pshape[0].get_length()); - auto lstm_hidden_size = static_cast(lstm_input_size / 4); - - GPU_DEBUG_LOG << "lstm_input_size : " << lstm_input_size << std::endl; - GPU_DEBUG_LOG << "lstm_batch_size : " << lstm_batch_size << std::endl; - GPU_DEBUG_LOG << "lstm_hidden_size : " << lstm_hidden_size << std::endl; - - GPU_DEBUG_LOG << "origin input_pshape : " << input_layout.to_short_string() << std::endl; - GPU_DEBUG_LOG << "origin weights_layout : " << weights_layout.to_short_string() << std::endl; - - input_pshape = {lstm_batch_size, 1, 1, lstm_input_size}; - input_layout.set_partial_shape(input_pshape); - - weights_pshape = {lstm_batch_size, 1, 1, lstm_hidden_size}; // {batch, direction, 1, hidden_size} - weights_layout.format = format::adjust_to_rank(weights_layout.format, weights_pshape.size()); - weights_layout.set_partial_shape(weights_pshape); - - updated_impl_params.weights_layout = weights_layout; - - GPU_DEBUG_LOG << "input_layout : " << input_layout.to_short_string() << std::endl; - GPU_DEBUG_LOG << "weights_layout : " << weights_layout.to_short_string() << std::endl; - GPU_DEBUG_LOG << "output_layout : " << output_layout.to_short_string() << std::endl; - - OPENVINO_ASSERT(input_pshape.size() == 4 && weights_pshape.size() == 4, "input and weights shape should be rank 4"); - return updated_impl_params; - } - - kernel_impl_params canonicalize_shapes(const kernel_impl_params& impl_params) const override { - return static_canonicalize_shapes(impl_params); - } -}; - -namespace detail { - -attach_lstm_elt_impl::attach_lstm_elt_impl() { - implementation_map::add(impl_types::ocl, typed_primitive_impl_ocl::create, { - std::make_tuple(data_types::f32, format::bfyx), - std::make_tuple(data_types::f16, format::bfyx), - std::make_tuple(data_types::f32, format::fyxb), - std::make_tuple(data_types::f16, format::fyxb), - }); -} - -} // namespace detail -} // namespace ocl -} // namespace cldnn - -BIND_BINARY_BUFFER_WITH_TYPE(cldnn::ocl::lstm_elt_impl) -BIND_BINARY_BUFFER_WITH_TYPE(cldnn::lstm_elt) diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp index 7f2fab7a6d1581..2a38d20ac8c9bc 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/register.cpp @@ -39,7 +39,6 @@ void register_implementations() { REGISTER_OCL(kv_cache); REGISTER_OCL(paged_attention); REGISTER_OCL(lrn); - REGISTER_OCL(lstm_elt); REGISTER_OCL(multiclass_nms); REGISTER_OCL(multinomial); REGISTER_OCL(mutable_data); diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp b/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp index 0a605945fcf6cc..c65a23822a6922 100644 --- a/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/register.hpp @@ -113,7 +113,6 @@ REGISTER_OCL(group_normalization); REGISTER_OCL(kv_cache); REGISTER_OCL(paged_attention); REGISTER_OCL(lrn); -REGISTER_OCL(lstm_elt); REGISTER_OCL(multiclass_nms); REGISTER_OCL(multinomial); REGISTER_OCL(mutable_data); diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.cpp b/src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.cpp new file mode 100644 index 00000000000000..3fb8ae13d3baa4 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "primitive_base.hpp" + +#include "lstm_seq_inst.h" +#include "rnn_seq.hpp" +#include "lstm/lstm_cell_and_seq_kernel_selector.h" +#include "lstm/lstm_kernel_base.h" +#include "openvino/op/lstm_sequence.hpp" +#include "impls/registry/implementation_manager.hpp" + +namespace cldnn { +namespace ocl { + +struct rnn_seq_impl : typed_primitive_impl_ocl { + using parent = typed_primitive_impl_ocl; + using parent::parent; + using kernel_selector_t = kernel_selector::lstm_cell_and_seq_kernel_selector; + using kernel_params_t = kernel_selector::lstm_params; + + DECLARE_OBJECT_TYPE_SERIALIZATION(cldnn::ocl::rnn_seq_impl) + + std::unique_ptr clone() const override { + return make_unique(*this); + } + +protected: + kernel_arguments_data get_arguments(const typed_primitive_inst& instance) const override { + kernel_arguments_data args; + for (size_t i = 0; i < instance.inputs_memory_count(); i++) { + args.inputs.push_back(instance.input_memory_ptr(i)); + } + + for (size_t i = 0; i < instance.outputs_memory_count(); i++) { + args.outputs.push_back(instance.output_memory_ptr(i)); + } + return args; + } + +public: + static kernel_params_t get_kernel_params(const kernel_impl_params& impl_param) { + const auto& primitive = impl_param.typed_desc(); + auto params = get_default_params(impl_param); + params.sequential = true; + for (size_t i = 1; i < impl_param.input_layouts.size(); ++i) { + params.inputs.push_back(convert_data_tensor(impl_param.get_input_layout(i))); + } + + if (!primitive->activations.empty()) { + auto a_sz = primitive->activations.size(); + auto param_sz = primitive->activation_params.size(); + OPENVINO_ASSERT(param_sz == 0|| a_sz == param_sz, "[GPU] Unexpected activation params count in lstm_seq impl: ", param_sz); + for (size_t i = 0; i < a_sz; i++) { + params.activations.emplace_back(get_kernel_selector_activation_param(primitive->activations[i]), + param_sz ? primitive->activation_params[i].a : 0.0f, + param_sz ? primitive->activation_params[i].b : 0.0f); + } + } + + if (primitive->clip > 0.0f) { + params.activations.emplace_back(get_kernel_selector_activation_param(activation_func::clamp), -primitive->clip, primitive->clip); + } + + params.SetOffsetOrder(static_cast(primitive->offset_order)); + params.clip = primitive->clip; + params.direction = primitive->direction; + return params; + } + + static kernel_impl_params static_canonicalize_shapes(const kernel_impl_params& impl_params) { + if (impl_params.get_input_layout().get_partial_shape().size() != 3) { + return primitive_impl::static_canonicalize_shapes(impl_params); + } + auto updated_impl_params = canonicalize_fused_shapes(impl_params); + return updated_impl_params; + } + + kernel_impl_params canonicalize_shapes(const kernel_impl_params& impl_params) const override { + return static_canonicalize_shapes(impl_params); + } +}; + +std::unique_ptr RNNSeqImplementationManager::create_impl(const program_node& node, const kernel_impl_params& params) const { + OPENVINO_ASSERT(node.is_type()); + return typed_primitive_impl_ocl::create(static_cast(node), params); +} + +} // namespace ocl +} // namespace cldnn + +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::ocl::rnn_seq_impl) +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::lstm_seq) diff --git a/src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.hpp b/src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.hpp new file mode 100644 index 00000000000000..3e71ad2be51192 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/ocl/rnn_seq.hpp @@ -0,0 +1,46 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "lstm_seq_inst.h" +#include "impls/registry/implementation_manager.hpp" +#include "intel_gpu/runtime/layout.hpp" + +#include +namespace cldnn { +namespace ocl { + +struct RNNSeqImplementationManager: public ImplementationManager { + OV_GPU_PRIMITIVE_IMPL("ocl::lstm_seq") + RNNSeqImplementationManager(shape_types shape_type, ValidateFunc vf = nullptr) : ImplementationManager(impl_types::ocl, shape_type, vf) {} + + std::unique_ptr create_impl(const program_node& node, const kernel_impl_params& params) const override; + + bool validate_impl(const program_node& node) const override { + assert(node.is_type()); + + const auto& input_layout = node.get_input_layout(0); + const auto& output_layout = node.get_output_layout(0); + + auto input_fmt = input_layout.format; + auto output_fmt = output_layout.format; + auto in_dt = input_layout.data_type; + auto out_dt = output_layout.data_type; + static const std::vector supported_formats = { + format::bfyx + }; + static const std::vector supported_data_types = { + data_types::f32, + data_types::f16, + }; + + if (!one_of(in_dt, supported_data_types) || !one_of(out_dt, supported_data_types)) { + return false; + } + + return one_of(input_fmt.value, supported_formats) && one_of(output_fmt.value, supported_formats); + } +}; + +} // namespace ocl +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.cpp b/src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.cpp new file mode 100644 index 00000000000000..8189e6e7b37412 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.cpp @@ -0,0 +1,217 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "lstm_seq_inst.h" +#include "primitive_onednn_base.h" +#include "lstm_seq_onednn.hpp" +#include "impls/registry/implementation_map.hpp" + +#include "kernel_selector_common.h" + +#include + +#include +#include +namespace cldnn { +namespace onednn { + +struct lstm_seq_onednn : typed_primitive_onednn_impl { + using parent = typed_primitive_onednn_impl; + using parent::parent; + + DECLARE_OBJECT_TYPE_SERIALIZATION(cldnn::onednn::lstm_seq_onednn) + +protected: + std::unique_ptr clone() const override { + return make_unique(*this); + } + + std::unordered_map get_arguments(lstm_seq_inst& instance) const override { + std::unordered_map args; + std::vector> dnnl_arg{{DNNL_ARG_SRC_LAYER, DNNL_ARG_SRC_ITER, DNNL_ARG_SRC_ITER_C}, {DNNL_ARG_WEIGHTS_LAYER, + DNNL_ARG_WEIGHTS_ITER, DNNL_ARG_BIAS}, {DNNL_ARG_DST_LAYER, DNNL_ARG_DST_ITER, DNNL_ARG_DST_ITER_C}}; + + for (int i = 0; i < 3; i++) { + for (int j = 0 ; j < 3; j++) { + dnnl::memory mem; + switch (i) { + case 0: + { + auto& input = instance.input_memory(j); + auto offset = onednn::get_offset(instance.get_input_layout(j), _pd.dnnl::primitive_desc_base::src_desc(j)); + mem = input.get_onednn_memory(_pd.dnnl::primitive_desc_base::src_desc(j), offset); + break; + } + case 1: + { + auto& input = instance.input_memory(3+j); + auto offset = onednn::get_offset(instance.get_input_layout(3+j), _pd.dnnl::primitive_desc_base::weights_desc(j)); + mem = input.get_onednn_memory(_pd.dnnl::primitive_desc_base::weights_desc(j), offset); + break; + } + case 2: + { + auto& output = instance.output_memory(j); + auto offset = onednn::get_offset(instance.get_output_layout(j), _pd.dnnl::primitive_desc_base::dst_desc(j)); + mem = output.get_onednn_memory(_pd.dnnl::primitive_desc_base::dst_desc(j), offset); + break; + } + default: + break; + } + args.insert({dnnl_arg[i][j], mem}); + } + } + return args; + } + + static cldnn::layout get_reorder_layout(const kernel_impl_params& impl_params, size_t layout_nr) { + auto weights_shape = impl_params.get_input_layout(layout_nr).get_shape(); + auto target_weights_layout = impl_params.get_input_layout(layout_nr); + target_weights_layout.format = cldnn::format::bfzyx; + auto layout = target_weights_layout.clone_with_other_shape(ov::Shape{weights_shape[0], weights_shape[1], weights_shape[2], 1, 1}); + return layout; + } + + static std::shared_ptr get_weights_reorder(const kernel_impl_params& impl_params, const dnnl::primitive_desc& pd) { + const auto weights_layout_idx = 3; + auto source_weights_layout = impl_params.get_input_layout(weights_layout_idx); + auto target_weights_layout = get_reorder_layout(impl_params, weights_layout_idx); + auto W_desc = onednn::layout_to_memory_desc(source_weights_layout); + auto grouped_weights = format::is_grouped(source_weights_layout.format); + + return std::make_shared(source_weights_layout, + target_weights_layout, + W_desc, + W_desc, + false, + grouped_weights); + } + static std::shared_ptr get_lstm_primitive_descriptor(const kernel_impl_params& impl_params, cldnn::engine& engine, + const dnnl::primitive_attr& attr, + ov::op::RecurrentSequenceDirection direction) { + auto prim = impl_params.typed_desc(); + auto num_dir = static_cast(prim->num_directions()); + const auto& src_shape = impl_params.get_input_layout(0).get_shape(); + auto mod_src_shape = src_shape; + std::swap(mod_src_shape[0], mod_src_shape[1]); + auto input_md = onednn::layout_to_memory_desc(impl_params.get_input_layout(0).clone_with_other_shape(mod_src_shape), dnnl::memory::format_tag::abc); + auto initial_hidden_shape_mod = impl_params.get_input_layout(1).get_shape(); + initial_hidden_shape_mod = { 1, num_dir, initial_hidden_shape_mod[0], initial_hidden_shape_mod[2] }; + auto initial_hidden = onednn::layout_to_memory_desc(impl_params.get_input_layout(1).clone_with_other_shape(initial_hidden_shape_mod)); + auto initial_cell = onednn::layout_to_memory_desc(impl_params.get_input_layout(2).clone_with_other_shape(initial_hidden_shape_mod)); + auto W_shape_mod = impl_params.get_input_layout(3).get_shape(); + W_shape_mod = {1, num_dir, W_shape_mod[2], 4, W_shape_mod[1]/4}; + auto w_layout = impl_params.get_input_layout(3).clone_with_other_shape(W_shape_mod); + w_layout.format = cldnn::format::bfzyx; + auto W_md = onednn::layout_to_memory_desc(w_layout); + auto R_shape_mod = impl_params.get_input_layout(4).get_shape(); + R_shape_mod = {1, num_dir, R_shape_mod[2], 4, R_shape_mod[1]/4}; + auto r_layout = impl_params.get_input_layout(4).clone_with_other_shape(R_shape_mod); + r_layout.format = cldnn::format::bfzyx; + auto R_md = onednn::layout_to_memory_desc(r_layout); + auto B_shape_mod = impl_params.get_input_layout(5).get_shape(); + B_shape_mod = {1, num_dir, 4, B_shape_mod[1]/4}; + auto b_layout = impl_params.get_input_layout(5).clone_with_other_shape(B_shape_mod); + b_layout.format = cldnn::format::bfyx; + auto B_md = onednn::layout_to_memory_desc(b_layout); + auto out_shape = impl_params.get_output_layout().get_shape(); + out_shape = {out_shape[2], out_shape[0], out_shape[3]*num_dir}; + auto output_md = onednn::layout_to_memory_desc(impl_params.get_output_layout().clone_with_other_shape(out_shape), dnnl::memory::format_tag::abc); + auto output1_md = onednn::layout_to_memory_desc(impl_params.get_output_layout(1).clone_with_other_shape(initial_hidden_shape_mod)); + auto output2_md = onednn::layout_to_memory_desc(impl_params.get_output_layout(2).clone_with_other_shape(initial_hidden_shape_mod)); + OPENVINO_ASSERT(input_md.get_format_kind() != dnnl::memory::format_kind::any, + "[GPU] The format kind of the input memory descriptor of onednn lstm_seq cannot be 'any'."); + OPENVINO_ASSERT(output_md.get_format_kind() != dnnl::memory::format_kind::any, + "[GPU] The format kind of the output memory descriptor of onednn lstm_seq cannot be 'any'."); + + auto eng = engine.get_onednn_engine(); + dnnl::rnn_direction lstm_desc_dir; + if (direction == ov::op::RecurrentSequenceDirection::FORWARD) { + lstm_desc_dir = dnnl::rnn_direction::unidirectional_left2right; + } else if (direction == ov::op::RecurrentSequenceDirection::REVERSE) { + lstm_desc_dir = dnnl::rnn_direction::unidirectional_right2left; + } else { + lstm_desc_dir = dnnl::rnn_direction::bidirectional_concat; + } + return std::make_shared( + eng, + dnnl::prop_kind::forward_inference, + lstm_desc_dir, + input_md, + initial_hidden, + initial_cell, + W_md, + R_md, + B_md, + output_md, + output1_md, + output2_md); + } + +public: + void save(BinaryOutputBuffer& ob) const override { +#ifdef ONEDNN_PRIMITIVE_SERIALIZATION + parent::save(ob); + + std::vector prim_cache; + prim_cache = _prim.get_cache_blob(); + ob << prim_cache; +#endif + } + + void load(BinaryInputBuffer& ib) override { +#ifdef ONEDNN_PRIMITIVE_SERIALIZATION + parent::load(ib); + + const kernel_impl_params* impl_params = reinterpret_cast(ib.getKernelImplParams()); + + auto input_md = onednn::layout_to_memory_desc(impl_params->get_input_layout(0)); + auto initial_hidden_md = onednn::layout_to_memory_desc(impl_params->get_input_layout(1)); + auto initial_cell_md = onednn::layout_to_memory_desc(impl_params->get_input_layout(2)); + auto W_md = onednn::layout_to_memory_desc(impl_params->get_input_layout(3)); + auto R_md = onednn::layout_to_memory_desc(impl_params->get_input_layout(4)); + auto B_md = onednn::layout_to_memory_desc(impl_params->get_input_layout(5)); + auto output_md = onednn::layout_to_memory_desc(impl_params->get_output_layout()); + auto output2_md = onednn::layout_to_memory_desc(impl_params->get_output_layout()); + auto prim_desc = std::make_shared( + ib.get_engine().get_onednn_engine(), + dnnl::prop_kind::forward_inference, + dnnl::rnn_direction::undef, + input_md, + initial_hidden_md, + initial_cell_md, + W_md, + R_md, + B_md, + output_md, + output_md, + output2_md); + _pd = *prim_desc; + + std::vector prim_cache; + ib >> prim_cache; + _prim = dnnl::primitive(_pd, prim_cache); +#endif + } + + static std::unique_ptr create(const lstm_seq_node& arg, const kernel_impl_params& impl_params) { + auto& engine = impl_params.prog->get_engine(); + auto& config = impl_params.prog->get_config(); + auto attr = impl_params.attrs_onednn; + auto direction = arg.direction(); + auto prim_desc = get_lstm_primitive_descriptor(impl_params, engine, *attr, direction); + return cldnn::make_unique(engine, config, attr, *prim_desc, get_weights_reorder(impl_params, *prim_desc)); + } +}; + +std::unique_ptr LSTMSeqImplementationManager::create_impl(const program_node& node, const kernel_impl_params& params) const { + assert(node.is_type()); + return onednn::lstm_seq_onednn::create(static_cast(node), params); +} + +} // namespace onednn +} // namespace cldnn + +BIND_BINARY_BUFFER_WITH_TYPE(cldnn::onednn::lstm_seq_onednn) diff --git a/src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.hpp b/src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.hpp new file mode 100644 index 00000000000000..6fd16a4dd04acf --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/onednn/lstm_seq_onednn.hpp @@ -0,0 +1,89 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "lstm_seq_inst.h" +#include "reshape_inst.h" +#include "intel_gpu/runtime/utils.hpp" +#include "impls/registry/implementation_manager.hpp" +#include "transformations/utils/utils.hpp" +#include "impls/onednn/utils.hpp" + +#include + + +namespace cldnn { +namespace onednn { + +struct LSTMSeqImplementationManager : public ImplementationManager { + OV_GPU_PRIMITIVE_IMPL("onednn::lstm_seq") + LSTMSeqImplementationManager(shape_types shape_type) : ImplementationManager(impl_types::onednn, shape_type) {} + std::unique_ptr create_impl(const program_node& node, const kernel_impl_params& params) const override; + + bool validate_impl(const program_node& node) const override { + assert(node.is_type()); + const auto& info = node.get_program().get_engine().get_device_info(); + if (info.arch == gpu_arch::unknown) + return false; + + const auto& lstm_seq_node = node.as(); + const auto& in_layout = lstm_seq_node.get_input_layout(0); + const auto& out_layout = lstm_seq_node.get_output_layout(0); + + if (node.get_input_layout(0).format != cldnn::format::bfyx && node.get_input_layout(0).format != cldnn::format::fbyx + && node.get_input_layout(0).format != cldnn::format::ybfx) + return false; + + if (!is_supported_pad(in_layout) || !is_supported_pad(out_layout)) + return false; + + auto in0_dt = node.get_input_layout(0).data_type; + auto in1_dt = node.get_input_layout(1).data_type; + auto in2_dt = node.get_input_layout(2).data_type; + auto in3_dt = node.get_input_layout(3).data_type; + auto in4_dt = node.get_input_layout(4).data_type; + auto in5_dt = node.get_input_layout(5).data_type; + auto out0_dt = node.get_output_layout(0).data_type; + auto out1_dt = node.get_output_layout(1).data_type; + auto out2_dt = node.get_output_layout(2).data_type; + bool cell_state_check = one_of(in2_dt, {data_types::f16, data_types::bf16, data_types::f32}) && + one_of(out2_dt, {data_types::f16, data_types::bf16, data_types::f32}); + bool f16_case = everyone_is(data_types::f16, in0_dt, in1_dt, in3_dt, in4_dt, out0_dt, out1_dt); + bool f32_case = everyone_is(data_types::f32, in0_dt, in1_dt, in3_dt, in4_dt, in5_dt, out0_dt, out1_dt); + bool u8u8u8_case = one_of(out0_dt, {data_types::u8, data_types::f32}) && everyone_is(data_types::i8, in3_dt, in4_dt) && + everyone_is(data_types::u8, in0_dt, in1_dt, out1_dt) && everyone_is(data_types::f32, in2_dt, in5_dt, out2_dt); + bool f32u8f32_case = everyone_is(data_types::u8, in0_dt) && everyone_is(data_types::i8, in3_dt, in4_dt) && + one_of(out0_dt, {data_types::u8, data_types::f32}) && everyone_is(data_types::f32, in1_dt, in5_dt, out1_dt); + bool s8s8s8_case = everyone_is(data_types::i8, in0_dt, in1_dt, out0_dt, out1_dt) && one_of(out0_dt, {data_types::i8, data_types::f32}) && + everyone_is(data_types::f32, in2_dt, in5_dt, out2_dt); + bool f32s8f32_case = everyone_is(data_types::i8, in0_dt, in3_dt, in4_dt) && one_of(out0_dt, {data_types::i8, data_types::f32}) && + everyone_is(data_types::f32, in1_dt, in5_dt, out1_dt); + + if (!cell_state_check) + return false; + return f16_case || f32_case || u8u8u8_case || f32u8f32_case || s8s8s8_case || f32s8f32_case; + } + + in_out_fmts_t query_formats(const program_node& node) const override { + assert(node.is_type()); + std::vector in_fmts(node.get_dependencies().size(), format::any); + std::vector out_fmts(node.get_outputs_count(), format::bfyx); + + size_t out_rank = node.get_output_layout().get_rank(); + for (size_t idx = 0; idx < node.get_dependencies().size(); idx++) { + if (node.get_dependency(idx).is_constant()) + continue; + + auto target_format = format::get_default_format(out_rank); + if (idx == 0) + in_fmts[idx] = format::fbyx; + in_fmts[idx] = target_format; + } + out_fmts[0] = format::ybfx; + + return {in_fmts, out_fmts}; + } +}; + +} // namespace onednn +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp b/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp index a8aa43671ed048..75e087a25fb48f 100644 --- a/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp +++ b/src/plugins/intel_gpu/src/graph/impls/onednn/utils.cpp @@ -270,6 +270,10 @@ dnnl::memory::desc layout_to_memory_desc(cldnn::layout l, dnnl::memory::format_t } else if (target_fmt == dnnl::memory::format_tag::ab) { dims.push_back(l.batch()); dims.push_back(l.get_tensor().count() / l.batch()); + } else if (target_fmt == dnnl::memory::format_tag::abc) { + dims.push_back(l.batch()); + dims.push_back(l.feature()); + dims.push_back(l.spatial(1)); } else if (target_fmt == dnnl::memory::format_tag::ba) { dims.push_back(l.feature()); dims.push_back(l.get_tensor().count() / l.feature()); diff --git a/src/plugins/intel_gpu/src/graph/impls/registry/lstm_cell_impls.cpp b/src/plugins/intel_gpu/src/graph/impls/registry/lstm_cell_impls.cpp new file mode 100644 index 00000000000000..09ba1f670b29d3 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/registry/lstm_cell_impls.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "primitive_inst.h" +#include "registry.hpp" +#include "intel_gpu/primitives/rnn.hpp" + +#if OV_GPU_WITH_OCL + #include "impls/ocl/lstm_cell.hpp" +#endif + +namespace ov { +namespace intel_gpu { + +using namespace cldnn; + +const std::vector>& Registry::get_implementations() { + static const std::vector> impls = { + OV_GPU_CREATE_INSTANCE_OCL(ocl::LSTMCellImplementationManager, shape_types::static_shape) + }; + + return impls; +} + +} // namespace intel_gpu +} // namespace ov diff --git a/src/plugins/intel_gpu/src/graph/impls/registry/lstm_seq_impls.cpp b/src/plugins/intel_gpu/src/graph/impls/registry/lstm_seq_impls.cpp new file mode 100644 index 00000000000000..4b718bd1c74c72 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/impls/registry/lstm_seq_impls.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "primitive_inst.h" +#include "registry.hpp" +#include "intel_gpu/primitives/rnn.hpp" + +#if OV_GPU_WITH_OCL + #include "impls/ocl/rnn_seq.hpp" +#endif + +#if OV_GPU_WITH_ONEDNN + #include "impls/onednn/lstm_seq_onednn.hpp" +#endif + +namespace ov { +namespace intel_gpu { + +using namespace cldnn; + +const std::vector>& Registry::get_implementations() { + static const std::vector> impls = { + OV_GPU_CREATE_INSTANCE_ONEDNN(onednn::LSTMSeqImplementationManager, shape_types::static_shape) + OV_GPU_CREATE_INSTANCE_OCL(ocl::RNNSeqImplementationManager, shape_types::static_shape) + }; + + return impls; +} + +} // namespace intel_gpu +} // namespace ov diff --git a/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp b/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp index 77c4262a7513cc..b7dbbaef6e64f1 100644 --- a/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp +++ b/src/plugins/intel_gpu/src/graph/impls/registry/registry.hpp @@ -130,6 +130,8 @@ REGISTER_IMPLS(fully_connected); REGISTER_IMPLS(gather); REGISTER_IMPLS(gather_nd); REGISTER_IMPLS(gemm); +REGISTER_IMPLS(lstm_cell); +REGISTER_IMPLS(lstm_seq); REGISTER_IMPLS(pooling); REGISTER_IMPLS(reduce); REGISTER_IMPLS(reorder); @@ -171,7 +173,6 @@ REGISTER_DEFAULT_IMPLS(grid_sample, OCL_S); REGISTER_DEFAULT_IMPLS(group_normalization, OCL_S, OCL_D); REGISTER_DEFAULT_IMPLS(kv_cache, OCL_S, OCL_D); REGISTER_DEFAULT_IMPLS(lrn, OCL_S); -REGISTER_DEFAULT_IMPLS(lstm_elt, OCL_S); REGISTER_DEFAULT_IMPLS(multiclass_nms, OCL_S); REGISTER_DEFAULT_IMPLS(multinomial, OCL_S); REGISTER_DEFAULT_IMPLS(mutable_data, OCL_S); diff --git a/src/plugins/intel_gpu/src/graph/include/layout_optimizer.h b/src/plugins/intel_gpu/src/graph/include/layout_optimizer.h index 52abc5f0cf8cb4..e7d5bdc8bdabdf 100644 --- a/src/plugins/intel_gpu/src/graph/include/layout_optimizer.h +++ b/src/plugins/intel_gpu/src/graph/include/layout_optimizer.h @@ -95,8 +95,7 @@ class layout_optimizer { b_fs_zyx_fsv32_network, b_fs_yx_fsv16_network, b_fs_zyx_fsv16_network, - bs_fs_yx_bsv16_fsv16_network, - use_onednn_impls + bs_fs_yx_bsv16_fsv16_network }; struct optimization_attributes { @@ -107,7 +106,7 @@ class layout_optimizer { int32_t b_fs_yx_fsv16_network = 0; int32_t b_fs_zyx_fsv16_network = 0; int32_t bs_fs_yx_bsv16_fsv16_network = 0; - int32_t use_onednn_impls = 0; + std::map onednn_impls = {}; }; private: @@ -190,6 +189,33 @@ class layout_optimizer { void set_optimization_attribute(optimization_attributes_type attribute, int32_t val); optimization_attributes get_optimization_attributes() { return _optimization_attributes; } + template + void enable_onednn_for() { + _optimization_attributes.onednn_impls[PT::type_id()] = true; + } + + template + void disable_onednn_for() { + _optimization_attributes.onednn_impls[PT::type_id()] = false; + } + void add_all_onednn_impls_optimization_attribute(); + bool has_all_enabled_onednn_impls_optimization_attribute(); + template + bool is_enabled_onednn_for() { + auto type_id = PT::type_id(); + auto it = _optimization_attributes.onednn_impls.find(type_id); + if (it == _optimization_attributes.onednn_impls.end()) { + return false; + } + + return it->second; + } + void set_value_onednn(primitive_type_id p_type, bool val); + bool contains_onednn_impls_optimization_attribute(const program_node*); + bool is_empty_onednn_impls_optimization_attribute(); + void clear_onednn_impls_optimization_attribute(); + std::map get_all_onednn_impls_optimization_attribute(); + void set_implementation_forcing(const ov::intel_gpu::ImplForcingMap& map); const std::map>& get_implementation_forcing() const; diff --git a/src/plugins/intel_gpu/src/graph/include/lstm_cell_inst.h b/src/plugins/intel_gpu/src/graph/include/lstm_cell_inst.h new file mode 100644 index 00000000000000..38c4232a500eb9 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/include/lstm_cell_inst.h @@ -0,0 +1,38 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once +#include "intel_gpu/primitives/lstm_cell.hpp" +#include "primitive_inst.h" + +#include + +namespace cldnn { +template <> +struct typed_program_node : public typed_program_node_base { + using parent = typed_program_node_base; + +public: + using parent::parent; +}; + +using lstm_cell_node = typed_program_node; + +template <> +class typed_primitive_inst : public typed_primitive_inst_base { + using parent = typed_primitive_inst_base; + using parent::parent; + +public: + template + static std::vector calc_output_layouts(lstm_cell_node const& node, kernel_impl_params const& impl_param); + static layout calc_output_layout(lstm_cell_node const& node, kernel_impl_params const& impl_param); + static std::string to_string(lstm_cell_node const& node); + +public: + typed_primitive_inst(network& network, lstm_cell_node const& node); +}; + +using lstm_cell_inst = typed_primitive_inst; +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/include/lstm_elt_inst.h b/src/plugins/intel_gpu/src/graph/include/lstm_elt_inst.h deleted file mode 100644 index 1524598c6f3987..00000000000000 --- a/src/plugins/intel_gpu/src/graph/include/lstm_elt_inst.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2018-2024 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#pragma once -#include "intel_gpu/primitives/lstm.hpp" -#include "primitive_inst.h" - -#include - -namespace cldnn { -template <> -struct typed_program_node : public typed_program_node_base { - using parent = typed_program_node_base; - -public: - using parent::parent; - - program_node& input() const { return get_dependency(0); } - program_node& cell() const { return get_dependency(1); } - bool cell_term() const { return !get_primitive()->cell.empty(); } - lstm_weights_order offset_order() const { return get_primitive()->offset_order; } - float clip() const { - float clip_val = get_primitive()->clip; - if (clip_val < 0) - throw std::range_error("Clip value < 0"); - return clip_val; - } - bool input_forget() const { return get_primitive()->input_forget; } - int32_t direction() const { return get_primitive()->direction; } -}; - -using lstm_elt_node = typed_program_node; - -template <> -class typed_primitive_inst : public typed_primitive_inst_base { - using parent = typed_primitive_inst_base; - using parent::parent; - -public: - template - static std::vector calc_output_layouts(lstm_elt_node const& node, kernel_impl_params const& impl_param); - static layout calc_output_layout(lstm_elt_node const& node, kernel_impl_params const& impl_param); - static std::string to_string(lstm_elt_node const& node); - -public: - typed_primitive_inst(network& network, lstm_elt_node const& node); - - memory::ptr cell_memory() const { return dep_memory_ptr(1); } - bool cell_term() const { return !get_typed_desc()->cell.empty(); } - lstm_weights_order offset_order() const { return get_typed_desc()->offset_order; } - float clip() const { - float clip_val = get_typed_desc()->clip; - if (clip_val < 0) - throw std::range_error("Clip value < 0"); - return clip_val; - } - bool input_forget() const { return get_typed_desc()->input_forget; } - uint32_t direction() const { return get_typed_desc()->direction; } -}; - -using lstm_elt_inst = typed_primitive_inst; - -} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/include/lstm_seq_inst.h b/src/plugins/intel_gpu/src/graph/include/lstm_seq_inst.h new file mode 100644 index 00000000000000..33ad7bebac2fbc --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/include/lstm_seq_inst.h @@ -0,0 +1,39 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once +#include "intel_gpu/primitives/rnn.hpp" +#include "primitive_inst.h" + +#include + +namespace cldnn { +template <> +struct typed_program_node : public typed_program_node_base { + using parent = typed_program_node_base; + +public: + using parent::parent; + ov::op::RecurrentSequenceDirection direction() const { return get_primitive()->direction; } +}; + +using lstm_seq_node = typed_program_node; + +template <> +class typed_primitive_inst : public typed_primitive_inst_base { + using parent = typed_primitive_inst_base; + using parent::parent; + +public: + template + static std::vector calc_output_layouts(lstm_seq_node const& node, kernel_impl_params const& impl_param); + static layout calc_output_layout(lstm_seq_node const& node, kernel_impl_params const& impl_param); + static std::string to_string(lstm_seq_node const& node); + +public: + typed_primitive_inst(network& network, lstm_seq_node const& node); +}; + +using lstm_seq_inst = typed_primitive_inst; +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/include/pass_manager.h b/src/plugins/intel_gpu/src/graph/include/pass_manager.h index 281f95a892662c..9850c25a64ec5d 100644 --- a/src/plugins/intel_gpu/src/graph/include/pass_manager.h +++ b/src/plugins/intel_gpu/src/graph/include/pass_manager.h @@ -196,6 +196,11 @@ class post_optimize_weights : public base_pass { weights_bias_offset get_weights_bias_offset(const T& node); template void optimize_weights(T& node, program& p); + void select_implementation(program& p, program_node& node); + void add_lstm_weights_reorder(primitive_id input_id, std::shared_ptr reorder_params, program& p, cldnn::program_node&, \ + cldnn::program_node&, size_t); + void add_lstm_bias_reorder(primitive_id input_id, std::shared_ptr reorder_params, program& p, cldnn::program_node&, \ + cldnn::program_node&); reorder_factory& _rf; }; diff --git a/src/plugins/intel_gpu/src/graph/include/primitive_type_base.h b/src/plugins/intel_gpu/src/graph/include/primitive_type_base.h index eb3f313af00de6..81f614ba9fd43a 100644 --- a/src/plugins/intel_gpu/src/graph/include/primitive_type_base.h +++ b/src/plugins/intel_gpu/src/graph/include/primitive_type_base.h @@ -51,7 +51,7 @@ struct primitive_type_base : primitive_type { if ((node.get_forced_impl_type() & impl_type) != impl_type) continue; - if (impl_type == impl_types::onednn && !node.get_program().get_layout_optimizer().get_optimization_attributes().use_onednn_impls) + if (impl_type == impl_types::onednn && !node.get_program().get_layout_optimizer().contains_onednn_impls_optimization_attribute(&node)) continue; shape_types supported_shape_type = impl->get_shape_type(); @@ -168,7 +168,7 @@ struct primitive_type_base : primitive_type { return true; continue; } else { - if (impl_type == impl_types::onednn && !node.get_program().get_layout_optimizer().get_optimization_attributes().use_onednn_impls) + if (impl_type == impl_types::onednn && !node.get_program().get_layout_optimizer().contains_onednn_impls_optimization_attribute(&node)) continue; if (!impl->validate(node)) diff --git a/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp b/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp index bb4d739b3a07c1..5262e8c4621e72 100644 --- a/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp +++ b/src/plugins/intel_gpu/src/graph/layout_optimizer.cpp @@ -27,7 +27,6 @@ #include "pooling_inst.h" #include "reduce_inst.h" #include "one_hot_inst.h" -#include "permute_inst.h" #include "quantize_inst.h" #include "mvn_inst.h" #include "depth_to_space_inst.h" @@ -37,7 +36,10 @@ #include "gather_inst.h" #include "broadcast_inst.h" #include "loop_inst.h" +#include "concatenation_inst.h" +#include "permute_inst.h" #include "dft_inst.h" +#include "lstm_seq_inst.h" #include "to_string_utils.h" #include #include @@ -114,7 +116,6 @@ bool layout_optimizer::is_format_supported(program_node& node, format::type fmt) node.get_input_layout(0).data_type != data_types::i8 && node.get_input_layout(0).data_type != data_types::u8) return false; - if (node.is_type()) return node.get_output_layout().format == fmt; @@ -132,7 +133,7 @@ bool layout_optimizer::can_fuse_reorder(program_node& prev, program_node& next, auto next_output_layout = next.get_output_layout(); auto prev_dt = prev.get_output_layout().data_type; auto next_dt = next.get_output_layout().data_type; - auto use_onednn_impls = _optimization_attributes.use_onednn_impls; + auto use_onednn_impls = has_all_enabled_onednn_impls_optimization_attribute(); if (prev.is_dynamic() || next.is_dynamic()) return false; @@ -365,7 +366,7 @@ bool layout_optimizer::can_fuse_reorder_to_prev(program_node& prev, reorder_node auto next = node.get_users().front(); auto dt_prev = prev.get_output_layout().data_type; auto dt_next = next->get_output_layout().data_type; - auto use_onednn_impls = _optimization_attributes.use_onednn_impls; + auto use_onednn_impls = contains_onednn_impls_optimization_attribute(&node) && contains_onednn_impls_optimization_attribute(&prev); if (prev.is_type()) return true; @@ -927,7 +928,7 @@ format layout_optimizer::get_expected_format(convolution_node const& node) { } bool onednn_valid_post_ops = get_post_ops_count(node) <= 32; - bool use_onednn_impls = _optimization_attributes.use_onednn_impls && input_layout.data_type != data_types::f32; + bool use_onednn_impls = contains_onednn_impls_optimization_attribute(&node) && input_layout.data_type != data_types::f32; // Use planar bfyx format for dynamic convolutions with explicit padding in clDNN if (node.is_dynamic() && output_layout.get_partial_shape().size() == 4 && node.use_explicit_padding() && !i8_u8_input && @@ -1038,7 +1039,7 @@ format layout_optimizer::get_expected_format(deconvolution_node const& node) { } auto expected_shape = output_layout.get_shape(); - bool use_onednn_impls = _optimization_attributes.use_onednn_impls; + bool use_onednn_impls = contains_onednn_impls_optimization_attribute(&node); auto available = node.get_primitive()->type->get_available_impl_types(node); @@ -1086,7 +1087,7 @@ format layout_optimizer::get_expected_format(quantize_node const& node) { return all_users_gemm; }; - auto use_onednn_impls = _optimization_attributes.use_onednn_impls; + auto use_onednn_impls = has_all_enabled_onednn_impls_optimization_attribute(); if (use_onednn_impls) { expected = format::any; @@ -1126,7 +1127,7 @@ format layout_optimizer::get_expected_format(quantize_node const& node) { bool layout_optimizer::is_primitive_implemented_for_onednn(program_node& node) { if (node.is_type() || node.is_type() || node.is_type() || node.is_type() || node.is_type() || - node.is_type() || node.is_type() || node.is_type()) { + node.is_type() || node.is_type() || node.is_type() || node.is_type()) { return true; } @@ -1219,7 +1220,7 @@ impl_types layout_optimizer::get_preferred_impl_type(program_node& node, format format layout_optimizer::get_preferred_format(program_node& node) { format expected = format::any; auto output_layout = node.get_output_layout(); - bool use_onednn_impls = _optimization_attributes.use_onednn_impls; + bool use_onednn_impls = contains_onednn_impls_optimization_attribute(&node); bool allow_new_shape_infer = node.get_program().is_new_shape_infer(); @@ -1417,14 +1418,55 @@ void layout_optimizer::set_optimization_attribute(optimization_attributes_type a case optimization_attributes_type::bs_fs_yx_bsv16_fsv16_network: _optimization_attributes.bs_fs_yx_bsv16_fsv16_network = val; break; - case optimization_attributes_type::use_onednn_impls: - _optimization_attributes.use_onednn_impls = val; - break; default: throw std::out_of_range("unsupported layout optimization attribute"); } } +void layout_optimizer::add_all_onednn_impls_optimization_attribute() { + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); + enable_onednn_for(); +} + +bool layout_optimizer::has_all_enabled_onednn_impls_optimization_attribute() { + return is_enabled_onednn_for() && is_enabled_onednn_for() && is_enabled_onednn_for() && + is_enabled_onednn_for() && is_enabled_onednn_for() && is_enabled_onednn_for() && + is_enabled_onednn_for() && is_enabled_onednn_for() && is_enabled_onednn_for(); +} + +void layout_optimizer::set_value_onednn(primitive_type_id p_type, bool val) { + _optimization_attributes.onednn_impls[p_type] = val; +} + +bool layout_optimizer::contains_onednn_impls_optimization_attribute(const program_node* node) { + auto type_id = node->type(); + auto it = _optimization_attributes.onednn_impls.find(type_id); + if (it == _optimization_attributes.onednn_impls.end()) { + return false; + } + + return it->second; +} + +bool layout_optimizer::is_empty_onednn_impls_optimization_attribute() { + return _optimization_attributes.onednn_impls.empty(); +} + +void layout_optimizer::clear_onednn_impls_optimization_attribute() { + _optimization_attributes.onednn_impls.clear(); +} + +std::map layout_optimizer::get_all_onednn_impls_optimization_attribute() { + return _optimization_attributes.onednn_impls; +} + bool layout_optimizer::is_format_optimized(const convolution_node& node, const format& format, bool use_weak_restrictions) { auto input_layout = node.get_input_layout(); auto weights_layout = node.weights().get_output_layout(); diff --git a/src/plugins/intel_gpu/src/graph/lstm_cell.cpp b/src/plugins/intel_gpu/src/graph/lstm_cell.cpp new file mode 100644 index 00000000000000..0b300199fb05a3 --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/lstm_cell.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// +#include "lstm_cell_inst.h" +#include "primitive_type_base.h" +#include "json_object.h" +#include + +namespace cldnn { +GPU_DEFINE_PRIMITIVE_TYPE_ID(lstm_cell) + +layout lstm_cell_inst::calc_output_layout(lstm_cell_node const& node, kernel_impl_params const& impl_param) { + const auto& input_layout = impl_param.get_input_layout(0); + const auto& input_pshape = input_layout.get_partial_shape(); + const auto& input_layout_hidden = impl_param.get_input_layout(1); + const auto& input_pshape_hidden = input_layout_hidden.get_partial_shape(); + const auto& lstm_batch_size = input_pshape[0]; + const auto& lstm_hidden_size = input_pshape_hidden[1]; + + return cldnn::layout{ov::PartialShape{lstm_batch_size, lstm_hidden_size}, input_layout.data_type, input_layout.format}; +} + +template +std::vector lstm_cell_inst::calc_output_layouts(lstm_cell_node const& node, kernel_impl_params const& impl_param) { + const auto& input_layout = impl_param.get_input_layout(0); + const auto& input_pshape = input_layout.get_partial_shape(); + const auto& input_layout_hidden = impl_param.get_input_layout(1); + const auto& input_pshape_hidden = input_layout_hidden.get_partial_shape(); + const auto& lstm_batch_size = input_pshape[0]; + const auto& lstm_hidden_size = input_pshape_hidden[1]; + + auto out_layout = cldnn::layout{ShapeType{lstm_batch_size, lstm_hidden_size}, input_layout.data_type, input_layout.format}; + return {out_layout, out_layout}; +} + +template std::vector lstm_cell_inst::calc_output_layouts(lstm_cell_node const& node, const kernel_impl_params& impl_param); + +std::string lstm_cell_inst::to_string(lstm_cell_node const& node) { + auto node_info = node.desc_to_json(); + + std::stringstream primitive_description; + + json_composite lstm_cell_info; + node_info->add("lstm cell info", lstm_cell_info); + node_info->dump(primitive_description); + + return primitive_description.str(); +} + +lstm_cell_inst::typed_primitive_inst(network& network, lstm_cell_node const& node) : parent(network, node) {} +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/lstm_elt.cpp b/src/plugins/intel_gpu/src/graph/lstm_elt.cpp deleted file mode 100644 index 098e89aa45003e..00000000000000 --- a/src/plugins/intel_gpu/src/graph/lstm_elt.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2018-2024 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// -#include "lstm_elt_inst.h" -#include "primitive_type_base.h" -#include "intel_gpu/runtime/error_handler.hpp" -#include "json_object.h" -#include - -namespace cldnn { -GPU_DEFINE_PRIMITIVE_TYPE_ID(lstm_elt) - -layout lstm_elt_inst::calc_output_layout(lstm_elt_node const& node, kernel_impl_params const& impl_param) { - assert(static_cast(impl_param.desc->output_data_types[0]) == false && - "Output data type forcing is not supported for lstm_elt_node!"); - auto input_layout = impl_param.get_input_layout(); - - // tempGEMM{bfyx} = [b: batch, f: direction, x: 1, y: 4 * hidden_size ] input - // cell{bfyx} = [b: batch, f: direction, x: 1, y: hidden_size ] optional - // output{bfyx} = [b: batch, f: 2, x: direction, y: hidden_size ] output - // The output of the lstm_elt node is the concatenation of the intermediate [hidden, cell] tensors. - // A crop/split node is needed to extract each individual tensors - auto result = - layout(input_layout.data_type, - input_layout.format, - tensor(input_layout.batch(), 2, input_layout.spatial(0) / 4, input_layout.feature())); - return result; -} - -template -std::vector lstm_elt_inst::calc_output_layouts(lstm_elt_node const& node, kernel_impl_params const& impl_param) { - std::vector output_layouts; - - // input partial shape [batch, input_size (= hidden_size * 4)] - auto input_layout = impl_param.get_input_layout(); - auto input_pshape = input_layout.get_partial_shape(); - OPENVINO_ASSERT(static_cast(impl_param.desc->output_data_types[0]) == false, "Output data type forcing is not supported for lstm_elt_node!"); - OPENVINO_ASSERT(input_pshape.rank().get_length() == 2, "input_layout rank should be 2 on dynamic shape."); - - int lstm_input_size, lstm_batch_size, lstm_hidden_size; - if (input_pshape[input_pshape.size() - 1].is_static()) { - lstm_input_size = input_pshape[input_pshape.size() - 1].get_length(); - lstm_hidden_size = lstm_input_size / 4; - } else { - lstm_input_size = -1; - lstm_hidden_size = -1; - } - - if (input_pshape[input_pshape.size() - 2].is_static()) { - lstm_batch_size = input_pshape[input_pshape.size() - 2].get_length(); - } else { - lstm_batch_size = -1; - } - - return {cldnn::layout{ov::PartialShape{lstm_batch_size, 2, 1, lstm_hidden_size}, input_layout.data_type, input_layout.format}}; -} - -template std::vector lstm_elt_inst::calc_output_layouts(lstm_elt_node const& node, const kernel_impl_params& impl_param); - -std::string lstm_elt_inst::to_string(lstm_elt_node const& node) { - auto desc = node.get_primitive(); - auto node_info = node.desc_to_json(); - auto cell_id = desc->cell; - - std::stringstream primitive_description; - - json_composite lstm_elt_info; - lstm_elt_info.add("cell id", cell_id); - node_info->add("lstm elt info", lstm_elt_info); - node_info->dump(primitive_description); - - return primitive_description.str(); -} - -lstm_elt_inst::typed_primitive_inst(network& network, lstm_elt_node const& node) : parent(network, node) { - auto input_size = node.get_input_layout(); - CLDNN_ERROR_NOT_PROPER_FORMAT(node.id(), - "input format", - input_size.format.value, - "expected format", - format::bfyx, - format::fyxb); -} -} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/lstm_seq.cpp b/src/plugins/intel_gpu/src/graph/lstm_seq.cpp new file mode 100644 index 00000000000000..55e25672300cfd --- /dev/null +++ b/src/plugins/intel_gpu/src/graph/lstm_seq.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// +#include "lstm_seq_inst.h" +#include "primitive_type_base.h" +#include "json_object.h" +#include + +namespace cldnn { +GPU_DEFINE_PRIMITIVE_TYPE_ID(lstm_seq) + +layout lstm_seq_inst::calc_output_layout(lstm_seq_node const& node, kernel_impl_params const& impl_param) { + const auto& desc = impl_param.typed_desc(); + const auto& input_layout = impl_param.get_input_layout(0); + const auto& input_pshape = input_layout.get_partial_shape(); + const auto& input_layout_hidden = impl_param.get_input_layout(1); + const auto& input_pshape_hidden = input_layout_hidden.get_partial_shape(); + const auto& lstm_batch_size = input_pshape[0]; + const auto& lstm_seq_length = input_pshape[1]; + const auto& lstm_hidden_size = input_pshape_hidden[2]; + + auto first_out_fmt = cldnn::format::bfyx; + if (node.get_preferred_impl_type() == impl_types::onednn && node.get_preferred_output_fmt() != format::any) { + first_out_fmt = node.get_preferred_output_fmt(); + } + + return cldnn::layout{ov::PartialShape{lstm_batch_size, desc->num_directions(), lstm_seq_length, lstm_hidden_size}, input_layout.data_type, first_out_fmt}; +} + +template +std::vector lstm_seq_inst::calc_output_layouts(lstm_seq_node const& node, kernel_impl_params const& impl_param) { + const auto& desc = impl_param.typed_desc(); + const auto& input_layout = impl_param.get_input_layout(0); + const auto& input_pshape = input_layout.get_partial_shape(); + const auto& input_layout_hidden = impl_param.get_input_layout(1); + const auto& input_pshape_hidden = input_layout_hidden.get_partial_shape(); + const auto& lstm_batch_size = input_pshape[0]; + const auto& lstm_seq_length = input_pshape[1]; + const auto& lstm_hidden_size = input_pshape_hidden[2]; + + auto first_out_fmt = cldnn::format::bfyx; + auto second_out_fmt = input_layout.format; + auto third_out_fmt = input_layout.format; + if (node.get_preferred_impl_type() == impl_types::onednn && node.get_preferred_output_fmt() != format::any) { + first_out_fmt = node.get_preferred_output_fmt(); + second_out_fmt = node.get_preferred_output_fmt(1); + third_out_fmt = node.get_preferred_output_fmt(2); + } + auto num_directions = desc->num_directions(); + + return {cldnn::layout{ShapeType{lstm_batch_size, num_directions, lstm_seq_length, lstm_hidden_size}, input_layout.data_type, first_out_fmt}, + cldnn::layout{ShapeType{lstm_batch_size, num_directions, lstm_hidden_size}, input_layout.data_type, second_out_fmt}, + cldnn::layout{ShapeType{lstm_batch_size, num_directions, lstm_hidden_size}, input_layout.data_type, third_out_fmt}}; +} + +template std::vector lstm_seq_inst::calc_output_layouts(lstm_seq_node const& node, const kernel_impl_params& impl_param); + +std::string lstm_seq_inst::to_string(lstm_seq_node const& node) { + auto node_info = node.desc_to_json(); + + std::stringstream primitive_description; + + json_composite lstm_seq_info; + node_info->add("lstm seq info", lstm_seq_info); + node_info->dump(primitive_description); + + return primitive_description.str(); +} + +lstm_seq_inst::typed_primitive_inst(network& network, lstm_seq_node const& node) : parent(network, node) {} +} // namespace cldnn diff --git a/src/plugins/intel_gpu/src/graph/primitive_inst.cpp b/src/plugins/intel_gpu/src/graph/primitive_inst.cpp index dac2c9a3403468..48967498894b41 100644 --- a/src/plugins/intel_gpu/src/graph/primitive_inst.cpp +++ b/src/plugins/intel_gpu/src/graph/primitive_inst.cpp @@ -2561,7 +2561,8 @@ cldnn::network::ptr primitive_inst::get_unfused_subgraph() { ExecutionConfig subgraph_config{ ov::intel_gpu::allow_static_input_reorder(true), ov::intel_gpu::allow_new_shape_infer(true), - ov::enable_profiling(get_network().get_config().get_property(ov::enable_profiling)) + ov::enable_profiling(get_network().get_config().get_property(ov::enable_profiling)), + ov::intel_gpu::use_onednn(get_network().get_config().get_property(ov::intel_gpu::use_onednn)) }; auto prog = program::build_program(get_network().get_engine(), t, diff --git a/src/plugins/intel_gpu/src/graph/program.cpp b/src/plugins/intel_gpu/src/graph/program.cpp index 2bfaac84134387..bdffb9c4980722 100644 --- a/src/plugins/intel_gpu/src/graph/program.cpp +++ b/src/plugins/intel_gpu/src/graph/program.cpp @@ -70,6 +70,9 @@ #include "unique_inst.hpp" #include "condition_inst.h" #include "to_string_utils.h" +#include "intel_gpu/graph/serialization/map_serializer.hpp" + +#include "intel_gpu/primitives/rnn.hpp" // TODO: Remove once we have interface for kernels cache #include "impls/ocl/kernels_cache.hpp" @@ -151,15 +154,14 @@ program::program(engine& engine_ref, is_internal(is_internal), _is_body_program(is_body_program), _compilation_context(compilation_context) { - _config.apply_user_properties(_engine.get_device_info()); init_primitives(); GPU_DEBUG_INFO << "Program config\n" << _config.to_string(); init_program(); prepare_nodes(topology); program_node::reset_unique_id(); - if (no_optimizations) { init_graph(); + _config.apply_user_properties(_engine.get_device_info()); } else { build_program(is_internal); if (_is_body_program) { @@ -494,6 +496,7 @@ void program::set_options() { void program::build_program(bool is_internal) { init_graph(); + _config.apply_user_properties(_engine.get_device_info()); { pre_optimize_graph(is_internal); } run_graph_compilation(); { post_optimize_graph(is_internal); } @@ -523,6 +526,9 @@ void program::init_graph() { for (auto& node : processing_order) { if (!node->is_type()) node->get_output_layouts(); + if (node->is_type()) { + _config.set_property(ov::intel_gpu::use_onednn(true)); + } } // Perform initial shape_of subgraphs markup apply_opt_pass(); @@ -1631,11 +1637,17 @@ void program::set_layout_optimizer_attributes(layout_optimizer& lo) { #ifdef ENABLE_ONEDNN_FOR_GPU bool enable_onednn_for_tests = get_config().get_property(ov::intel_gpu::optimize_data) || is_internal_program(); auto& engine = get_engine(); - if (engine.get_device_info().supports_immad && - engine.get_device_info().vendor_id == INTEL_VENDOR_ID && + if (engine.get_device_info().vendor_id == INTEL_VENDOR_ID && get_config().get_property(ov::intel_gpu::queue_type) == QueueTypes::in_order && - enable_onednn_for_tests) - lo.set_optimization_attribute(layout_optimizer::optimization_attributes_type::use_onednn_impls, 1); + enable_onednn_for_tests) { + if (engine.get_device_info().supports_immad) { + lo.add_all_onednn_impls_optimization_attribute(); + } else { + if (get_config().get_property(ov::intel_gpu::use_onednn)) { + lo.enable_onednn_for(); + } + } + } #endif } @@ -1779,7 +1791,13 @@ void program::save(cldnn::BinaryOutputBuffer& ob) const { ob << _is_body_program; ob << _can_be_optimized; - ob << get_layout_optimizer().get_optimization_attributes().use_onednn_impls; + auto onednn_impls_size = get_layout_optimizer().get_all_onednn_impls_optimization_attribute().size(); + ob << onednn_impls_size; + for (const auto& onednn_impl : get_layout_optimizer().get_all_onednn_impls_optimization_attribute()) { + ob << prim_map_storage::instance().get_type_string(onednn_impl.first); + ob << onednn_impl.second; + } + processing_order.save(ob); { @@ -1903,9 +1921,18 @@ void program::load(cldnn::BinaryInputBuffer& ib) { ib >> _is_body_program; ib >> _can_be_optimized; - int32_t use_onednn_attr = 0; - ib >> use_onednn_attr; - get_layout_optimizer().set_optimization_attribute(layout_optimizer::optimization_attributes_type::use_onednn_impls, use_onednn_attr); + + size_t num_of_onednn_impls; + ib >> num_of_onednn_impls; + for (size_t num = 0; num < num_of_onednn_impls; num++) { + primitive_id p_id{}; + bool enabled; + ib >> p_id; + ib >> enabled; + auto ptype_id = prim_map_storage::instance().get_type_id(p_id); + get_layout_optimizer().set_value_onednn(ptype_id, enabled); + } + _loaded_from_cache = true; processing_order.load(ib, *this); diff --git a/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/lstm_cell_and_seq_bfyx.cl b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/lstm_cell_and_seq_bfyx.cl new file mode 100644 index 00000000000000..f2cf2ca985e855 --- /dev/null +++ b/src/plugins/intel_gpu/src/kernel_selector/cl_kernels/lstm_cell_and_seq_bfyx.cl @@ -0,0 +1,215 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "include/batch_headers/fetch_data.cl" +#include "include/batch_headers/common.cl" + +#define INPUT0_TYPE_VEC MAKE_VECTOR_TYPE(INPUT0_TYPE, VEC_SIZE) +#define INPUT1_TYPE_VEC MAKE_VECTOR_TYPE(INPUT1_TYPE, VEC_SIZE) +#define INPUT3_TYPE_VEC MAKE_VECTOR_TYPE(INPUT3_TYPE, VEC_SIZE) +#define INPUT4_TYPE_VEC MAKE_VECTOR_TYPE(INPUT4_TYPE, VEC_SIZE) +#define OUTPUT_TYPE_VEC MAKE_VECTOR_TYPE(OUTPUT_TYPE, VEC_SIZE) +#define READ_VEC(offset, ptr) CAT(vload, VEC_SIZE)(offset, ptr) + +#ifdef SEQUENCE +#define GET_IN0_IDX(b, f, y) INPUT0_GET_INDEX(b, f, y, 0) + #if DIRECTION == 2 + #define GET_IN1_IDX(b, f, y) INPUT1_GET_INDEX(b, f, y, 0) + #define GET_IN2_IDX(b, f, y) INPUT2_GET_INDEX(b, f, y, 0) + #define GET_IN3_IDX(b, f, y) INPUT3_GET_INDEX(b, f, y, 0) + #define GET_IN4_IDX(b, f, y) INPUT4_GET_INDEX(b, f, y, 0) + #define GET_IN5_IDX(b, f) INPUT5_GET_INDEX(b, f, 0, 0) + #else + #define GET_IN1_IDX(b, f, y) INPUT1_GET_INDEX(b, 0, y, 0) + #define GET_IN2_IDX(b, f, y) INPUT2_GET_INDEX(b, 0, y, 0) + #define GET_IN3_IDX(b, f, y) INPUT3_GET_INDEX(0, f, y, 0) + #define GET_IN4_IDX(b, f, y) INPUT4_GET_INDEX(0, f, y, 0) + #define GET_IN5_IDX(b, f) INPUT5_GET_INDEX(0, f, 0, 0) + #endif +#else +#define GET_IN0_IDX(b, f, y) INPUT0_GET_INDEX(b, y, 0, 0) +#define GET_IN1_IDX(b, f, y) INPUT1_GET_INDEX(b, y, 0, 0) +#define GET_IN2_IDX(b, f, y) INPUT2_GET_INDEX(b, y, 0, 0) +#define GET_IN3_IDX(b, f, y) INPUT3_GET_INDEX(f, y, 0, 0) +#define GET_IN4_IDX(b, f, y) INPUT4_GET_INDEX(f, y, 0, 0) +#define GET_IN5_IDX(b, f) INPUT5_GET_INDEX(f, 0, 0, 0) +#endif + +KERNEL(lstm_cell_and_seq_bfyx)( + const __global INPUT0_TYPE* x, + const __global INPUT1_TYPE* initial_hidden_state, + const __global INPUT2_TYPE* initial_cell_state, + const __global INPUT3_TYPE* W, + const __global INPUT4_TYPE* R, + const __global INPUT5_TYPE* B, +#ifdef SEQUENCE + const __global INPUT6_TYPE* sequence_lengths, + __global OUTPUT_TYPE* hidden_history, + __global OUTPUT1_TYPE* hidden_state, + __global OUTPUT2_TYPE* cell_state +#else + __global OUTPUT_TYPE* hidden_state, + __global OUTPUT1_TYPE* cell_state +#endif +) +{ + const uint b = get_global_id(1); + const uint local_idx = get_local_id(0); + const uint weight_offsets[4] = {GEMM_OFFSET_F, GEMM_OFFSET_I, GEMM_OFFSET_Z, GEMM_OFFSET_O}; + #ifdef SEQUENCE + const uint real_seq_length = sequence_lengths[INPUT6_GET_INDEX(b, 0, 0, 0)]; + #else + const uint real_seq_length = 1; + #endif + #if DIRECTION == 2 + unroll_for(uint dir=0;dir0){ + barrier(CLK_LOCAL_MEM_FENCE); + } + #endif + unroll_for(uint l=0;l= HIDDEN_SIZE) { + continue; + } + ACCUMULATOR_TYPE gate_output[GATE_NUM]; + unroll_for(uint k=0;k0){ + barrier(CLK_LOCAL_MEM_FENCE); + } + #endif + unroll_for(uint l=0;l= HIDDEN_SIZE) { + continue; + } + ACCUMULATOR_TYPE gate_output[GATE_NUM]; + unroll_for(uint k=0;k