Added compatibility check of layout with partial shape (#10144)

* Added compatibility check of layout with partial shape

E.g. layout "NC" in not compatible with PartialShape{1,3,224,224}

Check is added:
- For parameter set_layout
- For parameter set_partial_shape
- For result set_layout
- Checked also compatibility for all results after 'validate_and_infer_types'

* Fix incorrect tests

* Fix of more incorrect tests

* Removed couple of obsoleted error-handling tests - these are catched now on earlier stages

Co-authored-by: Ilya Lavrenov <ilya.lavrenov@intel.com>
This commit is contained in:
Mikhail NosovandIlya Lavrenov authored and GitHub committed 2022-02-07 11:17:28 +03:00
1 parent ae4c727b31
commit 9f9df184c4
11 files changed
+157 -42

No files matched your search

@@ -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]))
@@ -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)
+3 -3
View File
@@ -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;
}
+18
View File
@@ -251,6 +251,7 @@ public:
static std::tuple<PartialShape, Layout, size_t> 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<uint64_t>& dims) {
@@ -517,6 +518,12 @@ std::tuple<PartialShape, Layout, size_t> 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<uint64_t>& dims) {
@@ -541,6 +548,10 @@ std::tuple<PartialShape, Layout, size_t> 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<ov::Node> 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);
}
}
+3
View File
@@ -6,6 +6,7 @@
#include <vector>
#include "openvino/core/layout.hpp"
#include "openvino/core/partial_shape.hpp"
namespace ov {
@@ -33,6 +34,8 @@ std::vector<int64_t> find_permutation(const Layout& src_layout,
const Layout& dst_layout);
Layout apply_permutation(const Layout& src_layout, const std::vector<uint64_t>& dims);
bool is_compatible(const Layout& layout, const PartialShape& shape);
} // namespace utils
} // namespace layout
} // namespace ov
+10
View File
@@ -9,6 +9,7 @@
#include <unordered_map>
#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<shared_ptr<ov::Node>> ov::Model::get_ordered_ops() const {
+13
View File
@@ -7,6 +7,7 @@
#include <sstream>
#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<ParameterVector>);
ov::AttributeAdapter<ParameterVector>::AttributeAdapter(ParameterVector& ref) : m_ref(ref) {}
+89 -4
View File
@@ -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<void()>;
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");
}
@@ -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<ov::Tensor> 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};
@@ -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);
@@ -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)