diff --git a/src/bindings/python/tests/test_ngraph/test_basic.py b/src/bindings/python/tests/test_ngraph/test_basic.py index 3185a0a37e9..2adae148073 100644 --- a/src/bindings/python/tests/test_ngraph/test_basic.py +++ b/src/bindings/python/tests/test_ngraph/test_basic.py @@ -30,8 +30,8 @@ def test_ngraph_function_api(): assert parameter_a.element_type == Type.f32 assert parameter_a.partial_shape == PartialShape([2, 2]) - parameter_a.layout = ov.Layout("NCWH") - assert parameter_a.layout == ov.Layout("NCWH") + parameter_a.layout = ov.Layout("NC") + assert parameter_a.layout == ov.Layout("NC") function = Model(model, [parameter_a, parameter_b, parameter_c], "TestFunction") function.get_parameters()[1].set_partial_shape(PartialShape([3, 4, 5])) diff --git a/src/bindings/python/tests/test_ngraph/test_preprocess.py b/src/bindings/python/tests/test_ngraph/test_preprocess.py index 888a2eea53f..bf0741cbfdb 100644 --- a/src/bindings/python/tests/test_ngraph/test_preprocess.py +++ b/src/bindings/python/tests/test_ngraph/test_preprocess.py @@ -39,7 +39,7 @@ def test_ngraph_preprocess_mean_vector(): parameter_a = ops.parameter(shape, dtype=np.float32, name="A") model = parameter_a function = Model(model, [parameter_a], "TestFunction") - layout = ov.Layout("NCHW") + layout = ov.Layout("NC") p = PrePostProcessor(function) p.input().tensor().set_layout(layout) @@ -60,7 +60,7 @@ def test_ngraph_preprocess_scale_vector(): parameter_a = ops.parameter(shape, dtype=np.float32, name="A") model = parameter_a function = Model(model, [parameter_a], "TestFunction") - layout = ov.Layout("NCHW") + layout = ov.Layout("NC") p = PrePostProcessor(function) inp = p.input() @@ -142,13 +142,13 @@ def test_ngraph_preprocess_input_output_by_name(): def test_ngraph_preprocess_output_postprocess(): - shape = [2, 2] + shape = [2, 3] parameter_a = ops.parameter(shape, dtype=np.int32, name="A") model = parameter_a function = Model(model, [parameter_a], "TestFunction") - layout1 = ov.Layout("NCHW") - layout2 = ov.Layout("NHWC") - layout3 = [0, 1] + layout1 = ov.Layout("NC") + layout2 = ov.Layout("CN") + layout3 = [1, 0] @custom_preprocess_function def custom_postprocess(output: Output): @@ -157,15 +157,16 @@ def test_ngraph_preprocess_output_postprocess(): p = PrePostProcessor(function) inp = p.input() inp.tensor().set_layout(layout1) - inp.preprocess().convert_element_type(Type.f32).mean([1., 2.]) + inp.preprocess().convert_element_type(Type.f32).mean([1., 2., 3.]) out = p.output() + out.model().set_layout(layout1) out.postprocess().convert_element_type(Type.f32) \ .convert_layout(layout2) \ .convert_layout(layout3).custom(custom_postprocess) function = p.build() - input_data = np.array([[-1, -2], [-3, -4]]).astype(np.int32) - expected_output = np.array([[2, 4], [4, 6]]).astype(np.float32) + input_data = np.array([[-1, -2, -3], [-4, -5, -6]]).astype(np.int32) + expected_output = np.array([[2, 4, 6], [5, 7, 9]]).astype(np.float32) runtime = get_runtime() computation = runtime.computation(function) diff --git a/src/core/include/openvino/op/parameter.hpp b/src/core/include/openvino/op/parameter.hpp index 3dac3acc300..0a5bf1e180b 100644 --- a/src/core/include/openvino/op/parameter.hpp +++ b/src/core/include/openvino/op/parameter.hpp @@ -42,9 +42,9 @@ public: PartialShape& get_partial_shape() { return m_partial_shape; } - void set_partial_shape(const PartialShape& partial_shape) { - m_partial_shape = partial_shape; - } + + void set_partial_shape(const PartialShape& partial_shape); + const element::Type& get_element_type() const { return m_element_type; } diff --git a/src/core/src/layout.cpp b/src/core/src/layout.cpp index e7b72556359..aea7a0f7b7a 100644 --- a/src/core/src/layout.cpp +++ b/src/core/src/layout.cpp @@ -251,6 +251,7 @@ public: static std::tuple find_unsqueeze(const Layout& src_layout, const PartialShape& src_shape, const Layout& dst_layout); + static bool is_compatible(const Layout& layout, const PartialShape& shape); }; Layout LayoutUtils::apply_permutation(const Layout& src_layout, const std::vector& dims) { @@ -517,6 +518,12 @@ std::tuple LayoutUtils::find_unsqueeze(const Layou } } +bool LayoutUtils::is_compatible(const Layout& layout, const PartialShape& shape) { + auto layout_min_rank = layout.m_left_size + layout.m_right_size; + int64_t layout_max_rank = layout.m_dynamic ? -1 : layout_min_rank; + return shape.rank().compatible(Dimension(layout_min_rank, layout_max_rank)); +} + namespace layout { namespace utils { Layout apply_permutation(const Layout& src_layout, const std::vector& dims) { @@ -541,6 +548,10 @@ std::tuple find_unsqueeze(const Layout& src_layout return LayoutUtils::find_unsqueeze(src_layout, src_shape, dst_layout); } +bool is_compatible(const Layout& layout, const PartialShape& shape) { + return LayoutUtils::is_compatible(layout, shape); +} + } // namespace utils // Helper functions @@ -603,6 +614,13 @@ void set_layout(ov::Output output, const ov::Layout& layout) { if (layout.empty()) { output.get_rt_info().erase(ov::LayoutAttribute::get_type_info_static()); } else { + OPENVINO_ASSERT(ov::layout::utils::is_compatible(layout, output.get_partial_shape()), + "Can't set layout for Parameter/Result ", + output, + ": layout ", + layout.to_string(), + " is not compatible with shape ", + output.get_partial_shape()); output.get_rt_info()[ov::LayoutAttribute::get_type_info_static()] = ov::LayoutAttribute(layout); } } diff --git a/src/core/src/layout_utils.hpp b/src/core/src/layout_utils.hpp index 4d0737be026..85da289578d 100644 --- a/src/core/src/layout_utils.hpp +++ b/src/core/src/layout_utils.hpp @@ -6,6 +6,7 @@ #include +#include "openvino/core/layout.hpp" #include "openvino/core/partial_shape.hpp" namespace ov { @@ -33,6 +34,8 @@ std::vector find_permutation(const Layout& src_layout, const Layout& dst_layout); Layout apply_permutation(const Layout& src_layout, const std::vector& dims); +bool is_compatible(const Layout& layout, const PartialShape& shape); + } // namespace utils } // namespace layout } // namespace ov diff --git a/src/core/src/model.cpp b/src/core/src/model.cpp index 0741b839680..c759847c417 100644 --- a/src/core/src/model.cpp +++ b/src/core/src/model.cpp @@ -9,6 +9,7 @@ #include #include "itt.hpp" +#include "layout_utils.hpp" #include "ngraph/evaluator.hpp" #include "ngraph/function.hpp" #include "ngraph/graph_util.hpp" @@ -253,6 +254,15 @@ void ov::Model::validate_nodes_and_infer_types() const { if (!only_pairs) throw ov::Exception("Model is incorrect. Assign and ReadValue operations must be in pairs on the " "network."); + for (const auto& output : outputs()) { + OPENVINO_ASSERT(ov::layout::utils::is_compatible(ov::layout::get_layout(output), output.get_partial_shape()), + "Result '", + output, + "' with shape ", + output.get_partial_shape(), + " is incompatible with layout ", + ov::layout::get_layout(output).to_string()); + } } std::vector> ov::Model::get_ordered_ops() const { diff --git a/src/core/src/op/parameter.cpp b/src/core/src/op/parameter.cpp index 75a9a1a978e..e5feebad2a6 100644 --- a/src/core/src/op/parameter.cpp +++ b/src/core/src/op/parameter.cpp @@ -7,6 +7,7 @@ #include #include "itt.hpp" +#include "layout_utils.hpp" #include "ngraph/attribute_visitor.hpp" using namespace std; @@ -56,6 +57,18 @@ void op::Parameter::set_layout(const ov::Layout& layout) { ov::layout::set_layout(output(0), layout); } +void op::Parameter::set_partial_shape(const PartialShape& partial_shape) { + OPENVINO_ASSERT(ov::layout::utils::is_compatible(get_layout(), partial_shape), + "Can't set partial shape ", + partial_shape, + " for Parameter ", + *this, + " with layout ", + get_layout().to_string(), + ". Layout is not compatible with shape"); + m_partial_shape = partial_shape; +} + BWDCMP_RTTI_DEFINITION(ov::AttributeAdapter); ov::AttributeAdapter::AttributeAdapter(ParameterVector& ref) : m_ref(ref) {} diff --git a/src/core/tests/model.cpp b/src/core/tests/model.cpp index 4ba12760840..6a15d31f21f 100644 --- a/src/core/tests/model.cpp +++ b/src/core/tests/model.cpp @@ -1438,7 +1438,7 @@ TEST(model, get_batch_size) { TEST(model, get_batch_size_with_conflict) { auto f = bs_utils::create_n_inputs(ov::element::f32, {ov::PartialShape::dynamic(), {5, 6}, {1, 3, 224, 224}, {3, 1}}, - {"NCHW", "D...", "NCHW", "N???"}); + {"NCHW", "D...", "NCHW", "N?"}); // TODO: gtest v.10 limitation. Replace with EXPECT_THAT for gtest >= v1.11 try { @@ -1447,7 +1447,7 @@ TEST(model, get_batch_size_with_conflict) { } catch (const ov::Exception& err) { // Verify error message contains conflicting layouts EXPECT_TRUE(std::string(err.what()).find(ov::Layout("NCHW").to_string()) != std::string::npos) << err.what(); - EXPECT_TRUE(std::string(err.what()).find(ov::Layout("N???").to_string()) != std::string::npos) << err.what(); + EXPECT_TRUE(std::string(err.what()).find(ov::Layout("N?").to_string()) != std::string::npos) << err.what(); // Verify error message doesn't contain non-conflicting layouts EXPECT_TRUE(std::string(err.what()).find(ov::Layout("D...").to_string()) == std::string::npos) << err.what(); EXPECT_TRUE(std::string(err.what()).find("tensor_input_0") == std::string::npos) << err.what(); @@ -1525,7 +1525,7 @@ TEST(model, set_batch_size_dynamic_layout) { TEST(model, set_batch_size_with_conflict) { auto f = bs_utils::create_n_inputs(ov::element::f32, {ov::PartialShape::dynamic(), {5, 6}, {1, 3, 224, 224}, {3, 1}}, - {"NCHW", "D...", "NCHW", "N???"}); + {"NCHW", "D...", "NCHW", "N?"}); // TODO: gtest v.10 limitation. Replace with EXPECT_THAT for gtest >= v1.11 try { @@ -1534,7 +1534,7 @@ TEST(model, set_batch_size_with_conflict) { } catch (const ov::Exception& err) { // Verify error message contains conflicting layouts EXPECT_TRUE(std::string(err.what()).find(ov::Layout("NCHW").to_string()) != std::string::npos) << err.what(); - EXPECT_TRUE(std::string(err.what()).find(ov::Layout("N???").to_string()) != std::string::npos) << err.what(); + EXPECT_TRUE(std::string(err.what()).find(ov::Layout("N?").to_string()) != std::string::npos) << err.what(); // Verify error message doesn't contain non-conflicting layouts EXPECT_TRUE(std::string(err.what()).find(ov::Layout("D...").to_string()) == std::string::npos) << err.what(); EXPECT_TRUE(std::string(err.what()).find("tensor_input_0") == std::string::npos) << err.what(); @@ -1577,3 +1577,88 @@ TEST(model, set_batch_size_validation_throw) { FAIL() << "Expected ov::Exception"; } } + +TEST(model, incompatible_layout) { + auto f = bs_utils::create_n_inputs(ov::element::f32, {{1, 3, 224, 224}}, {"NCHW"}); + using callback = std::function; + auto verify_ex = [&](const callback& cb, const std::string& msg) { + try { + cb(); + FAIL() << "set_layout shall throw"; + } catch (const ov::Exception& err) { + // Verify error message contains conflicting layouts + EXPECT_TRUE(std::string(err.what()).find(msg) != std::string::npos) << err.what(); + } catch (...) { + FAIL() << "Expected ov::Exception"; + } + }; + auto verify_ex_set_layout = [&](const ov::Layout& layout) { + auto msg = layout.to_string(); + verify_ex( + [&]() { + ov::layout::set_layout(f->input(), layout); + }, + msg); + }; + verify_ex_set_layout("HWC"); + verify_ex_set_layout("NDCHW"); + verify_ex_set_layout("ND...CHW"); + EXPECT_NO_THROW(ov::layout::set_layout(f->input(), "H...WC")); + EXPECT_NO_THROW(ov::layout::set_layout(f->input(), "...NCHW")); + EXPECT_NO_THROW(f->get_parameters()[0]->set_layout("NCHW...")); + EXPECT_NO_THROW(f->get_parameters()[0]->set_layout("NCHW")); + + auto verify_ex_set_layout_param = [&](const ov::Layout& layout) { + auto msg = layout.to_string(); + verify_ex( + [&]() { + f->get_parameters()[0]->set_layout(layout); + }, + msg); + }; + verify_ex_set_layout_param("HWC"); + verify_ex_set_layout_param("NDCHW"); + verify_ex_set_layout_param("ND...CHW"); + + auto verify_ex_set_partial_shape = [&](const ov::PartialShape& shape) { + std::stringstream msgStr; + msgStr << shape; + auto msg = msgStr.str(); + verify_ex( + [&]() { + f->get_parameters()[0]->set_partial_shape(shape); + }, + msg); + }; + verify_ex_set_partial_shape({1, 2, 3, 4, 5}); + verify_ex_set_partial_shape({1, 2, 3}); + EXPECT_NO_THROW(f->get_parameters()[0]->set_partial_shape(ov::PartialShape::dynamic())); + EXPECT_NO_THROW(f->get_parameters()[0]->set_partial_shape(ov::PartialShape{1, 3, 224, 224})); + + auto verify_ex_set_layout_result = [&](const ov::Layout& layout) { + auto msg = layout.to_string(); + verify_ex( + [&]() { + ov::layout::set_layout(f->output(), layout); + }, + msg); + }; + verify_ex_set_layout_result("HWC"); + verify_ex_set_layout_result("NDCHW"); + verify_ex_set_layout_result("ND...CHW"); + + auto verify_ex_set_layout_result_validate = [&](const ov::PartialShape& param_shape, const ov::Layout& layout) { + auto msg = layout.to_string(); + f = bs_utils::create_n_inputs(ov::element::f32, {ov::PartialShape::dynamic()}, {"..."}); + verify_ex( + [&]() { + f->get_parameters()[0]->set_partial_shape(param_shape); + ov::layout::set_layout(f->output(), layout); + f->validate_nodes_and_infer_types(); + }, + msg); + }; + verify_ex_set_layout_result_validate({1, 2, 3, 4}, "HWC"); + verify_ex_set_layout_result_validate({1, 2, 3, 4}, "NDHWC"); + verify_ex_set_layout_result_validate({1, 2, 3, 4}, "ND...HWC"); +} diff --git a/src/tests/functional/plugin/shared/src/behavior/ov_infer_request/batched_tensors.cpp b/src/tests/functional/plugin/shared/src/behavior/ov_infer_request/batched_tensors.cpp index f1d827f7cd8..6d7af6a653c 100644 --- a/src/tests/functional/plugin/shared/src/behavior/ov_infer_request/batched_tensors.cpp +++ b/src/tests/functional/plugin/shared/src/behavior/ov_infer_request/batched_tensors.cpp @@ -317,19 +317,6 @@ TEST_P(OVInferRequestBatchedTests, SetTensors_Batch1) { } } -TEST_P(OVInferRequestBatchedTests, SetInputTensors_Batch_Incorrect) { - size_t batch = 3; - auto one_shape = Shape{1, 3, 3, 3}; - auto batch_shape = Shape{batch, 3, 3, 3}; - auto model = OVInferRequestBatchedTests::create_n_inputs(1, element::f32, batch_shape, "DCHWN"); - const std::string tensor_name = "tensor_input0"; - auto execNet = ie->compile_model(model, targetDevice); - ov::InferRequest req; - req = execNet.create_infer_request(); - std::vector tensors(batch, ov::Tensor(element::f32, one_shape)); - ASSERT_THROW(req.set_tensors(tensor_name, tensors), ov::Exception); -} - TEST_P(OVInferRequestBatchedTests, SetInputTensors_Get_Tensor_Not_Allowed) { size_t batch = 3; auto one_shape = Shape{1, 3, 3, 3}; diff --git a/src/tests/unit/inference_engine/ie_compilation_context_test.cpp b/src/tests/unit/inference_engine/ie_compilation_context_test.cpp index 6d9497f78cf..4c9b79047c3 100644 --- a/src/tests/unit/inference_engine/ie_compilation_context_test.cpp +++ b/src/tests/unit/inference_engine/ie_compilation_context_test.cpp @@ -309,15 +309,15 @@ TEST(NetworkContext_CNNNetwork, HashWithFutureRt_int64) { } TEST(NetworkContext_CNNNetwork, HashWithLayout) { - auto net1 = createNetworkWithLayout("NCHW"); - auto net2 = createNetworkWithLayout("nchw"); - auto net3 = createNetworkWithLayout("?CHW"); - auto net3_1 = createNetworkWithLayout("?CH?"); + auto net1 = createNetworkWithLayout("NCH"); + auto net2 = createNetworkWithLayout("nch"); + auto net3 = createNetworkWithLayout("?CH"); + auto net3_1 = createNetworkWithLayout("?C?"); auto net4 = createNetworkWithLayout(""); auto fun5 = create_simple_function(); - fun5->get_parameters()[0]->set_layout("NCHW"); + fun5->get_parameters()[0]->set_layout("NCH"); fun5->get_parameters()[0]->set_layout(""); - fun5->get_results()[0]->set_layout("NHCW"); + fun5->get_results()[0]->set_layout("NHC"); fun5->get_results()[0]->set_layout(ov::Layout()); auto net5 = CNNNetwork(fun5); diff --git a/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py b/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py index c535b01b7f1..877f4a1ba6e 100644 --- a/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py +++ b/tools/mo/unit_tests/mo/back/moc_preprocessing_test_actual.py @@ -551,12 +551,10 @@ class TestPreprocessingMOC(UnitTestWithMockedTelemetry): with self.assertRaisesRegex(Error, '.*2.*inputs.*input1.*input2.*'): process_function(ov_function=function, argv=argv) - def test_reverse_channels_bad_layout(self): - argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None) + def test_incompatible_layout(self): function = create_function2(shape1=[1, 224, 224, 3], shape2=[1, 4, 224, 224]) - function.get_parameters()[0].layout = Layout("NDHWC") - with self.assertRaisesRegex(Error, '.*input1.*'): - process_function(ov_function=function, argv=argv) + with self.assertRaisesRegex(Exception, '.*input1.*'): + function.get_parameters()[0].layout = Layout("NDHWC") def test_guess_layout_reverse_channels_dont_apply_to_4(self): argv = Namespace(reverse_input_channels=True, mean_scale_values=None, scale=None)