diff --git a/src/frontends/onnx/frontend/include/openvino/frontend/onnx/frontend.hpp b/src/frontends/onnx/frontend/include/openvino/frontend/onnx/frontend.hpp index fd952d9f7a9..5e4c44b00f3 100644 --- a/src/frontends/onnx/frontend/include/openvino/frontend/onnx/frontend.hpp +++ b/src/frontends/onnx/frontend/include/openvino/frontend/onnx/frontend.hpp @@ -20,10 +20,12 @@ public: using Ptr = std::shared_ptr; std::shared_ptr convert(const InputModel::Ptr& model) const override; void convert(const std::shared_ptr& partially_converted) const override; + std::shared_ptr convert_partially(const InputModel::Ptr& model) const override; std::shared_ptr decode(const InputModel::Ptr& model) const override; std::string get_name() const override; bool supported_impl(const std::vector& variants) const override; void add_extension(const std::shared_ptr& extension) override; + void normalize(const std::shared_ptr& model) const override; protected: InputModel::Ptr load_impl(const std::vector& params) const override; diff --git a/src/frontends/onnx/frontend/src/core/graph.cpp b/src/frontends/onnx/frontend/src/core/graph.cpp index 5d0eb68dfed..5fde1ee645f 100644 --- a/src/frontends/onnx/frontend/src/core/graph.cpp +++ b/src/frontends/onnx/frontend/src/core/graph.cpp @@ -22,36 +22,12 @@ #include "openvino/frontend/onnx/node_context.hpp" #include "openvino/op/util/op_types.hpp" #include "openvino/util/log.hpp" -#include "ops_bridge.hpp" #include "utils/common.hpp" #include "utils/legacy_conversion_extension.hpp" namespace ngraph { namespace onnx_import { namespace detail { -std::string to_string(const std::map>& map) { - std::string result; - for (auto it = std::begin(map); it != std::end(map); ++it) { - result += (it != std::begin(map) ? ", " : "") + it->first; - } - return result; -} - -/// \brief Gets the operator represented by provided node unique identificator. -/// -/// \param[in] node_proto The node protobuf representation object. -/// -/// \note The operator is uniquely identified by the tuple (domain, op_type, -/// since_version). The first two elements are stored in NodeProto object, -/// thus we use only them. -/// -/// \return The unique identificator. -/// -static std::string get_op_domain_and_name(const ONNX_NAMESPACE::NodeProto& node_proto) { - std::string domain = get_node_domain(node_proto); - return (domain.empty() ? "" : domain + ".") + node_proto.op_type(); -} - bool common_node_for_all_outputs(const OutputVector& outputs) { const auto first_out_node = outputs.at(0).get_node(); bool ret = std::all_of(std::next(std::begin(outputs)), @@ -157,9 +133,9 @@ Graph::Graph(const std::string& model_dir, : m_cache{std::move(cache)}, m_extensions{std::move(extensions)}, m_model_dir{model_dir}, - m_enable_mmap{enable_mmap} { - const auto ops_bridge = detail::init_ops_bridge(m_extensions.conversions); - m_model = common::make_unique(model_proto, detail::build_model_opset(*model_proto, ops_bridge)); + m_enable_mmap{enable_mmap}, + m_ops_bridge{detail::init_ops_bridge(m_extensions.conversions)} { + m_model = common::make_unique(model_proto, detail::build_model_opset(*model_proto, m_ops_bridge)); transform::expand_onnx_functions(*model_proto); @@ -197,48 +173,22 @@ Graph::Graph(const std::string& model_dir, auto ng_node = value_info.get_ng_node(m_parameters, initializers); m_cache->emplace_node(input.name(), std::move(ng_node)); } - - // Verify that ONNX graph contains only nodes of available operator types - std::map> unknown_operators; - std::map op_statistics; - for (const auto& node_proto : m_model->get_graph().node()) { - if (m_extensions.telemetry) { - op_statistics[node_proto.op_type()]++; - } - if (!m_model->is_operator_available(node_proto)) { - unknown_operators.emplace(detail::get_op_domain_and_name(node_proto), node_proto); - // If a node from an unregistered domain is detected, try registering that domain - m_model->enable_opset_domain(get_node_domain(node_proto), ops_bridge); - } - } - - if (m_extensions.telemetry) { - for (const auto& op : op_statistics) { - m_extensions.telemetry->send_event("op_count", "onnx_" + op.first, static_cast(op.second)); - } - } - - // Reverify wheter we still have any unavailable operators. - auto it = std::begin(unknown_operators); - while (it != std::end(unknown_operators)) { - if (m_model->is_operator_available(it->second)) { - it = unknown_operators.erase(it); - } else { - it++; - } - } - - NGRAPH_CHECK(unknown_operators.empty(), - "OpenVINO does not support the following ONNX operations: ", - detail::to_string(unknown_operators)); } void Graph::convert_to_ngraph_nodes() { const float total = static_cast(m_model->get_graph().node().size()); unsigned int completed = 0u; + std::map op_statistics; // Process ONNX graph nodes, convert to nGraph nodes for (const auto& node_proto : m_model->get_graph().node()) { + if (m_extensions.telemetry) { + op_statistics[node_proto.op_type()]++; + } const Node node{node_proto, this}; + if (!m_model->is_operator_available(node.op_type(), node.domain())) { + // If a node from an unregistered domain is detected, try registering that domain + m_model->enable_opset_domain(node.domain(), m_ops_bridge); + } if (node.has_subgraphs()) { const auto& subgraphs = node.get_subgraphs(); for (auto& kv : subgraphs) { @@ -250,6 +200,11 @@ void Graph::convert_to_ngraph_nodes() { ++completed; m_extensions.progress_reporter->report_progress(completed / total, static_cast(total), completed); } + if (m_extensions.telemetry) { + for (const auto& op : op_statistics) { + m_extensions.telemetry->send_event("op_count", "onnx_" + op.first, static_cast(op.second)); + } + } } void Graph::remove_dangling_parameters() { @@ -325,8 +280,12 @@ OutputVector Graph::make_framework_nodes(const Node& onnx_node) { void Graph::decode_to_framework_nodes() { const float total = static_cast(m_model->get_graph().node().size()); unsigned int completed = 0u; + std::map op_statistics; // Process ONNX graph nodes, convert to nGraph nodes for (const auto& node_proto : m_model->get_graph().node()) { + if (m_extensions.telemetry) { + op_statistics[node_proto.op_type()]++; + } const Node node{node_proto, this}; OutputVector ng_nodes{make_framework_nodes(node)}; set_friendly_names(node, ng_nodes); @@ -339,6 +298,11 @@ void Graph::decode_to_framework_nodes() { ++completed; m_extensions.progress_reporter->report_progress(completed / total, static_cast(total), completed); } + if (m_extensions.telemetry) { + for (const auto& op : op_statistics) { + m_extensions.telemetry->send_event("op_count", "onnx_" + op.first, static_cast(op.second)); + } + } } std::shared_ptr Graph::create_function() { @@ -383,23 +347,31 @@ OutputVector Graph::get_ng_outputs() { } OutputVector Graph::make_ng_nodes(const Node& onnx_node) { - const auto ng_node_factory = m_model->get_operator(onnx_node.op_type(), onnx_node.domain()); - // contains outputs of nG subgraph implementing a particular ONNX node (possibly a single output of a single node) OutputVector ng_subgraph_outputs; - try { - ng_subgraph_outputs = ng_node_factory(onnx_node); - } catch (const ::ngraph::onnx_import::error::OnnxNodeValidationFailure&) { - // Do nothing OnnxNodeValidationFailure exception already has ONNX node information. - throw; - } catch (const std::exception& exc) { - std::string msg_prefix = error::detail::get_error_msg_prefix(onnx_node); - OPENVINO_THROW(msg_prefix + ":\n" + std::string(exc.what())); - } catch (...) { - std::string msg_prefix = error::detail::get_error_msg_prefix(onnx_node); - // Since we do not know anything about current exception data type we can only - // notify user in this way. - OPENVINO_ERR << msg_prefix + "Unhandled exception type. \n"; - std::rethrow_exception(std::current_exception()); + std::string error_message; + if (m_model->is_operator_available(onnx_node.op_type(), onnx_node.domain())) { + const auto ng_node_factory = m_model->get_operator(onnx_node.op_type(), onnx_node.domain()); + try { + ng_subgraph_outputs = ng_node_factory(onnx_node); + } catch (const ::ngraph::onnx_import::error::OnnxNodeValidationFailure& e) { + error_message = e.what(); + } catch (const std::exception& exc) { + error_message = error::detail::get_error_msg_prefix(onnx_node); + error_message += ":\n" + std::string{exc.what()}; + } catch (...) { + error_message = error::detail::get_error_msg_prefix(onnx_node); + // Since we do not know anything about current exception data type we can only + // notify user in this way. + error_message += "Unhandled exception type. \n"; + } + } + if (ng_subgraph_outputs.empty()) { // translation not possible (not supported op or exception during processing) + const auto not_supported_node = std::make_shared(onnx_node.get_ng_inputs(), + onnx_node.get_outputs_size(), + onnx_node.domain(), + onnx_node.op_type(), + error_message); + ng_subgraph_outputs = not_supported_node->outputs(); } const size_t outputs_size = std::accumulate(std::begin(ng_subgraph_outputs), diff --git a/src/frontends/onnx/frontend/src/core/graph.hpp b/src/frontends/onnx/frontend/src/core/graph.hpp index ba251b6f5dd..600aa6f5726 100644 --- a/src/frontends/onnx/frontend/src/core/graph.hpp +++ b/src/frontends/onnx/frontend/src/core/graph.hpp @@ -16,6 +16,7 @@ #include "ngraph/op/parameter.hpp" #include "onnx_import/core/operator_set.hpp" #include "openvino/frontend/extension/holder.hpp" +#include "ops_bridge.hpp" namespace ngraph { namespace onnx_import { @@ -83,6 +84,7 @@ private: std::vector m_nodes; std::string m_model_dir; bool m_enable_mmap; + OperatorsBridge m_ops_bridge; }; /// \brief Representation of ONNX subgraph. It is used for example by ONNX Loop op. diff --git a/src/frontends/onnx/frontend/src/core/model.cpp b/src/frontends/onnx/frontend/src/core/model.cpp index b41c0be0a47..1a10c1e2308 100644 --- a/src/frontends/onnx/frontend/src/core/model.cpp +++ b/src/frontends/onnx/frontend/src/core/model.cpp @@ -52,12 +52,12 @@ const Operator& Model::get_operator(const std::string& name, const std::string& return op->second; } -bool Model::is_operator_available(const ONNX_NAMESPACE::NodeProto& node_proto) const { - const auto dm = m_opset.find(get_node_domain(node_proto)); +bool Model::is_operator_available(const std::string& name, const std::string& domain) const { + const auto dm = m_opset.find(domain); if (dm == std::end(m_opset)) { return false; } - const auto op = dm->second.find(node_proto.op_type()); + const auto op = dm->second.find(name); return (op != std::end(dm->second)); } diff --git a/src/frontends/onnx/frontend/src/core/model.hpp b/src/frontends/onnx/frontend/src/core/model.hpp index 311db16b1ca..148c9f867b8 100644 --- a/src/frontends/onnx/frontend/src/core/model.hpp +++ b/src/frontends/onnx/frontend/src/core/model.hpp @@ -77,8 +77,10 @@ public: const Operator& get_operator(const std::string& name, const std::string& domain) const; /// \brief Check availability of operator base on NodeProto. + /// \param name Type name of the operator. + /// \param domain Domain name of the operator. /// \return `true` if the operator is available, otherwise it returns `false`. - bool is_operator_available(const ONNX_NAMESPACE::NodeProto& node_proto) const; + bool is_operator_available(const std::string& name, const std::string& domain) const; /// \brief Enable operators from provided domain to use by this model. /// diff --git a/src/frontends/onnx/frontend/src/frontend.cpp b/src/frontends/onnx/frontend/src/frontend.cpp index dbc7f1c6036..3ebbf861900 100644 --- a/src/frontends/onnx/frontend/src/frontend.cpp +++ b/src/frontends/onnx/frontend/src/frontend.cpp @@ -29,6 +29,7 @@ #include "openvino/core/so_extension.hpp" #include "openvino/frontend/extension/telemetry.hpp" #include "ops_bridge.hpp" +#include "utils/common.hpp" using namespace ov; using namespace ov::frontend::onnx; @@ -86,7 +87,7 @@ InputModel::Ptr FrontEnd::load_impl(const std::vector& variants) const return nullptr; } -std::shared_ptr FrontEnd::convert(const InputModel::Ptr& model) const { +std::shared_ptr FrontEnd::convert_partially(const InputModel::Ptr& model) const { auto model_onnx = std::dynamic_pointer_cast(model); NGRAPH_CHECK(model_onnx != nullptr, "Invalid input model"); @@ -105,6 +106,24 @@ std::shared_ptr FrontEnd::convert(const InputModel::Ptr& model return model_onnx->convert(); } +void FrontEnd::normalize(const std::shared_ptr& model) const { + // Here, you can register transformations as a second step of importing process + // In particular, you can operate on not supported ops (it allows to N:N ONNX->OV mapping). + // ov::pass::Manager manager; + // manager.run_passes(model); +} + +std::shared_ptr FrontEnd::convert(const InputModel::Ptr& model) const { + const auto partially_converted = convert_partially(model); + + const auto error_message = ngraph::onnx_import::common::collect_translation_exceptions(partially_converted); + NGRAPH_CHECK(error_message.empty(), error_message); + + normalize(partially_converted); + + return partially_converted; +} + void FrontEnd::convert(const std::shared_ptr& partially_converted) const { ngraph::onnx_import::detail::convert_decoded_function(partially_converted); } diff --git a/src/frontends/onnx/frontend/src/onnx.cpp b/src/frontends/onnx/frontend/src/onnx.cpp index 68ed2b4424b..a073441cf41 100644 --- a/src/frontends/onnx/frontend/src/onnx.cpp +++ b/src/frontends/onnx/frontend/src/onnx.cpp @@ -13,6 +13,7 @@ #include "onnx_common/parser.hpp" #include "onnx_import/onnx_utils.hpp" #include "ops_bridge.hpp" +#include "utils/common.hpp" #include "utils/legacy_conversion_extension.hpp" #include "utils/onnx_internal.hpp" @@ -28,7 +29,10 @@ std::shared_ptr import_onnx_model(std::istream& stream, const auto model_proto = std::make_shared(onnx_common::parse_from_istream(stream)); ov::frontend::ExtensionHolder extensions; extensions.conversions.push_back(legacy_conversion_extension); - return detail::import_onnx_model(model_proto, model_path, enable_mmap, std::move(extensions)); + const auto model = detail::import_onnx_model(model_proto, model_path, enable_mmap, std::move(extensions)); + const auto error_message = common::collect_translation_exceptions(model); + NGRAPH_CHECK(error_message.empty(), error_message); + return model; } std::shared_ptr import_onnx_model(const std::string& file_path, const bool enable_mmap) { @@ -39,7 +43,10 @@ std::shared_ptr import_onnx_model(const std::string& file_path, const ". Could not open the file."); }; - return import_onnx_model(model_stream, file_path, enable_mmap); + const auto model = import_onnx_model(model_stream, file_path, enable_mmap); + const auto error_message = common::collect_translation_exceptions(model); + NGRAPH_CHECK(error_message.empty(), error_message); + return model; } std::set get_supported_operators(std::int64_t version, const std::string& domain) { diff --git a/src/frontends/onnx/frontend/src/onnx_framework_node.cpp b/src/frontends/onnx/frontend/src/onnx_framework_node.cpp index e1f4754c84c..6cf880d760d 100644 --- a/src/frontends/onnx/frontend/src/onnx_framework_node.cpp +++ b/src/frontends/onnx/frontend/src/onnx_framework_node.cpp @@ -14,7 +14,7 @@ // limitations under the License. //***************************************************************************** -#include +#include "onnx_framework_node.hpp" namespace ngraph { namespace frontend { @@ -26,5 +26,24 @@ std::shared_ptr ONNXSubgraphFrameworkNode::clone_with_new_inputs(const Out return std::make_shared(m_node, m_functions, inputs); } +std::shared_ptr NotSupportedONNXNode::clone_with_new_inputs(const OutputVector& inputs) const { + const auto& attrs = get_attrs(); + std::string error_message = attrs.at(failed_conversion_key); + return std::make_shared(inputs, + get_output_size(), + attrs.get_opset_name(), + attrs.get_type_name(), + error_message); +} + +bool NotSupportedONNXNode::visit_attributes(AttributeVisitor& visitor) { + const auto& attrs = get_attrs(); + auto domain = attrs.get_opset_name(); + auto op_type = attrs.get_type_name(); + visitor.on_attribute("ONNX_META_domain", domain); + visitor.on_attribute("ONNX_META_type", op_type); + return true; +} + } // namespace frontend } // namespace ngraph diff --git a/src/frontends/onnx/frontend/src/onnx_framework_node.hpp b/src/frontends/onnx/frontend/src/onnx_framework_node.hpp index 10807e37afb..5db79a1cfb3 100644 --- a/src/frontends/onnx/frontend/src/onnx_framework_node.hpp +++ b/src/frontends/onnx/frontend/src/onnx_framework_node.hpp @@ -36,15 +36,18 @@ class Model; namespace frontend { class ONNXFrameworkNode : public ov::op::util::FrameworkNode { public: - OPENVINO_RTTI("ONNXFrameworkNode", "0"); + OPENVINO_OP("ONNXFrameworkNode", "util", ov::op::util::FrameworkNode); - ONNXFrameworkNode(const onnx_import::Node& node) - : ov::op::util::FrameworkNode(node.get_ng_inputs(), node.get_outputs_size()), - m_node(node) {} + ONNXFrameworkNode(const onnx_import::Node& node) : ONNXFrameworkNode(node, node.get_ng_inputs()) {} ONNXFrameworkNode(const onnx_import::Node& node, const OutputVector& inputs) : ov::op::util::FrameworkNode(inputs, node.get_outputs_size()), - m_node(node) {} + m_node(node) { + ov::op::util::FrameworkNodeAttrs attrs; + attrs.set_type_name(node.op_type()); + attrs.set_opset_name(node.domain()); + set_attrs(attrs); + } OutputVector get_ng_nodes(const std::shared_ptr& graph) const { OutputVector ng_nodes{graph->make_ng_nodes(m_node)}; @@ -71,7 +74,7 @@ protected: class ONNXSubgraphFrameworkNode : public ONNXFrameworkNode { public: - OPENVINO_RTTI("ONNXSubgraphFrameworkNode", "0"); + OPENVINO_OP("ONNXSubgraphFrameworkNode", "util", ONNXFrameworkNode); ONNXSubgraphFrameworkNode(const onnx_import::Node& node, const std::vector>& functions, @@ -94,5 +97,35 @@ private: std::vector> m_functions; }; +// Be careful with using protobuf references (also onnx_import::Node) inside NotSupportedONNXNode +// which are inserted into ov::Model due to different lifetime and problematic sharing between dynamic libs. +class NotSupportedONNXNode : public ov::op::util::FrameworkNode { + static constexpr const char* failed_conversion_key = "onnx::NotSupportedONNXNode::failed_conversion_key"; + +public: + OPENVINO_OP("NotSupportedONNXNode", "util", ov::op::util::FrameworkNode); + + NotSupportedONNXNode(const OutputVector& inputs, + const size_t output_size, + const std::string& domain, + const std::string& op_type, + const std::string& additional_error_message) + : ov::op::util::FrameworkNode(inputs, output_size) { + ov::op::util::FrameworkNodeAttrs attrs; + attrs.set_opset_name(domain); + attrs.set_type_name(op_type); + attrs[failed_conversion_key] = additional_error_message; + set_attrs(attrs); + } + + std::string additional_error_message() const { + auto attrs = get_attrs(); + return attrs[failed_conversion_key]; + } + + virtual std::shared_ptr clone_with_new_inputs(const OutputVector& inputs) const override; + virtual bool visit_attributes(AttributeVisitor& visitor) override; +}; + } // namespace frontend } // namespace ngraph diff --git a/src/frontends/onnx/frontend/src/utils/common.cpp b/src/frontends/onnx/frontend/src/utils/common.cpp index 94fc2d687da..811a7b2e3f7 100644 --- a/src/frontends/onnx/frontend/src/utils/common.cpp +++ b/src/frontends/onnx/frontend/src/utils/common.cpp @@ -8,6 +8,7 @@ #include "default_opset.hpp" #include "ngraph/graph_util.hpp" +#include "onnx_framework_node.hpp" namespace ngraph { namespace onnx_import { @@ -141,6 +142,44 @@ bool is_optimized_out(const Output& node_output) { return rt_info.find(OPTIMIZED_OUT_NODE) != rt_info.end(); } +std::string collect_translation_exceptions(const std::shared_ptr& partially_converted) { + std::string fully_unsupported_ops = "OpenVINO does not support the following ONNX operations: "; + std::string additional_error_message = "Errors during ONNX translation: \n"; + const std::string sep = ", "; + + bool unsupported_found = false; + bool additional_error_found = false; + for (const auto& op : partially_converted->get_ops()) { + if (const auto unsupported = std::dynamic_pointer_cast(op)) { + if (unsupported->additional_error_message().empty()) { + fully_unsupported_ops += (unsupported->get_attrs().get_opset_name().empty() + ? "" + : unsupported->get_attrs().get_opset_name() + ".") + + unsupported->get_attrs().get_type_name() + sep; + unsupported_found = true; + } else { + additional_error_message += unsupported->additional_error_message(); + additional_error_found = true; + } + } + } + fully_unsupported_ops = fully_unsupported_ops.substr(0, fully_unsupported_ops.size() - sep.size()); + // remove redundant new line + additional_error_message = + (additional_error_message.empty() || additional_error_message[additional_error_message.length() - 1] != '\n') + ? additional_error_message + : additional_error_message.erase(additional_error_message.length() - 1); + if (unsupported_found && additional_error_found) { + return fully_unsupported_ops + "\n" + additional_error_message; + } else if (unsupported_found) { + return fully_unsupported_ops; + } else if (additional_error_found) { + return additional_error_message; + } else { + return ""; + } +} + } // namespace common } // namespace onnx_import } // namespace ngraph diff --git a/src/frontends/onnx/frontend/src/utils/common.hpp b/src/frontends/onnx/frontend/src/utils/common.hpp index 017a7299f94..bc2636b17ba 100644 --- a/src/frontends/onnx/frontend/src/utils/common.hpp +++ b/src/frontends/onnx/frontend/src/utils/common.hpp @@ -150,6 +150,9 @@ void mark_as_optimized_out(Output& node_output); /// \brief Checks if a given output was marked as optimized out byt the function above. bool is_optimized_out(const Output& node_output); + +/// \brief Collect unsupported operators after convert_partially and all exceptions from translation process. +std::string collect_translation_exceptions(const std::shared_ptr& partially_converted); } // namespace common } // namespace onnx_import } // namespace ngraph diff --git a/src/frontends/onnx/tests/CMakeLists.txt b/src/frontends/onnx/tests/CMakeLists.txt index 080876ab103..0a17e3bfb73 100644 --- a/src/frontends/onnx/tests/CMakeLists.txt +++ b/src/frontends/onnx/tests/CMakeLists.txt @@ -73,6 +73,8 @@ set(MULTI_TEST_SRC onnx_import_with_editor.in.cpp) set(SRC conversion.cpp + convert_tests.cpp + convert_partially_tests.cpp library_extension.cpp load_from.cpp onnx_editor.cpp diff --git a/src/frontends/onnx/tests/convert_partially_tests.cpp b/src/frontends/onnx/tests/convert_partially_tests.cpp new file mode 100644 index 00000000000..62525a2d9b5 --- /dev/null +++ b/src/frontends/onnx/tests/convert_partially_tests.cpp @@ -0,0 +1,109 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include + +#include "common_test_utils/file_utils.hpp" +#include "common_test_utils/ngraph_test_utils.hpp" +#include "openvino/frontend/manager.hpp" +#include "openvino/op/util/framework_node.hpp" +#include "openvino/opsets/opset11.hpp" + +namespace { +std::shared_ptr convert_partially(const std::string& path) { + ov::frontend::FrontEndManager manager; + const auto frontend = manager.load_by_framework("onnx"); + const auto in_model = frontend->load(path); + return frontend->convert_partially(in_model); +} + +std::shared_ptr get_framework_node_with_out_name(const std::shared_ptr& model, + const std::string& out_name) { + for (const auto& op : model->get_ops()) { + if (auto framework_node = std::dynamic_pointer_cast(op)) { + for (const auto& out : op->outputs()) { + if (out.get_any_name() == out_name) { + return framework_node; + } + } + } + } + return nullptr; +} +} // namespace + +TEST(ONNXFeConvertPartially, insert_framework_node_if_unsupported) { + const auto path = CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + + "unsupported_ops/add_unsupported.onnx"); + std::shared_ptr model; + ASSERT_NO_THROW(model = convert_partially(path)); + ASSERT_TRUE(model); + + EXPECT_EQ(count_ops_of_type(model), 1); + const auto unsupported_add = get_framework_node_with_out_name(model, "Y"); + ASSERT_TRUE(unsupported_add); + EXPECT_EQ(unsupported_add->get_attrs().get_type_name(), "UnsupportedAdd"); + EXPECT_EQ(unsupported_add->get_attrs().get_opset_name(), "test_domain"); +} + +TEST(ONNXFeConvertPartially, insert_more_framework_nodes_if_unsupported) { + const auto path = CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + + "unsupported_ops/two_unsupported_nodes.onnx"); + std::shared_ptr model; + ASSERT_NO_THROW(model = convert_partially(path)); + ASSERT_TRUE(model); + + EXPECT_EQ(count_ops_of_type(model), 1); + const auto unsupported_add = get_framework_node_with_out_name(model, "X"); + ASSERT_TRUE(unsupported_add); + EXPECT_EQ(unsupported_add->get_attrs().get_type_name(), "UnsupportedAdd"); + + const auto unsupported_abs = get_framework_node_with_out_name(model, "Y_out"); + ASSERT_TRUE(unsupported_abs); + EXPECT_EQ(unsupported_abs->get_attrs().get_type_name(), "UnsupportedAbs"); +} + +// validation error - onnx/instance_norm_bad_scale_type.onnx +TEST(ONNXFeConvertPartially, insert_framework_node_if_onnx_validation_exception) { + const auto path = + CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + "instance_norm_bad_scale_type.onnx"); + std::shared_ptr model; + ASSERT_NO_THROW(model = convert_partially(path)); + ASSERT_TRUE(model); + + const auto incorrect_instance_norm = get_framework_node_with_out_name(model, "y"); + ASSERT_TRUE(incorrect_instance_norm); + EXPECT_EQ(incorrect_instance_norm->get_attrs().get_type_name(), "InstanceNormalization"); +} + +TEST(ONNXFeConvertPartially, insert_framework_node_if_other_translation_exception) { + const auto path = + CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + "depth_to_space_bad_mode.onnx"); + std::shared_ptr model; + ASSERT_NO_THROW(model = convert_partially(path)); + ASSERT_TRUE(model); + + const auto incorrect_dts = get_framework_node_with_out_name(model, "B"); + ASSERT_TRUE(incorrect_dts); + EXPECT_EQ(incorrect_dts->get_attrs().get_type_name(), "DepthToSpace"); +} + +TEST(ONNXFeConvertPartially, insert_framework_nodes_if_both_unsupported_and_other_translation_exception) { + const auto path = CommonTestUtils::getModelFromTestModelZoo( + std::string(ONNX_TEST_MODELS) + "unsupported_ops/unsupported_add_and_incorrect_dts.onnx"); + std::shared_ptr model; + ASSERT_NO_THROW(model = convert_partially(path)); + ASSERT_TRUE(model); + + EXPECT_EQ(count_ops_of_type(model), 1); + const auto incorrect_dts = get_framework_node_with_out_name(model, "B"); + ASSERT_TRUE(incorrect_dts); + EXPECT_EQ(incorrect_dts->get_attrs().get_type_name(), "DepthToSpace"); + + const auto unsupported_add = get_framework_node_with_out_name(model, "Y"); + ASSERT_TRUE(unsupported_add); + EXPECT_EQ(unsupported_add->get_attrs().get_type_name(), "UnsupportedAdd"); +} diff --git a/src/frontends/onnx/tests/convert_tests.cpp b/src/frontends/onnx/tests/convert_tests.cpp new file mode 100644 index 00000000000..22bc01c795e --- /dev/null +++ b/src/frontends/onnx/tests/convert_tests.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include + +#include "common_test_utils/file_utils.hpp" +#include "common_test_utils/test_assertions.hpp" +#include "openvino/frontend/manager.hpp" + +namespace { +void convert(const std::string& path) { + ov::frontend::FrontEndManager manager; + const auto frontend = manager.load_by_framework("onnx"); + const auto in_model = frontend->load(path); + frontend->convert(in_model); +} +} // namespace + +TEST(ONNXFeConvertException, exception_if_node_unsupported) { + const auto path = CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + + "unsupported_ops/add_unsupported.onnx"); + + OV_EXPECT_THROW( + convert(path), + ov::AssertFailure, + testing::EndsWith("OpenVINO does not support the following ONNX operations: test_domain.UnsupportedAdd\n")); +} + +TEST(ONNXFeConvertException, exception_if_more_nodes_unsupported) { + const auto path = CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + + "unsupported_ops/two_unsupported_nodes.onnx"); + + OV_EXPECT_THROW( + convert(path), + ov::AssertFailure, + testing::EndsWith("OpenVINO does not support the following ONNX operations: UnsupportedAbs, UnsupportedAdd\n")); +} + +TEST(ONNXFeConvertException, exception_if_onnx_validation_exception) { + const auto path = + CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + "instance_norm_bad_scale_type.onnx"); + + OV_EXPECT_THROW(convert(path), + ov::AssertFailure, + testing::EndsWith("Element types for data and scale input do not match (data element type: f32, " + "scale element type: u16).\n")); +} + +TEST(ONNXFeConvertException, exception_if_other_translation_exception) { + const auto path = + CommonTestUtils::getModelFromTestModelZoo(std::string(ONNX_TEST_MODELS) + "depth_to_space_bad_mode.onnx"); + + OV_EXPECT_THROW(convert(path), ov::AssertFailure, testing::EndsWith("only 'DCR' and 'CRD' modes are supported\n")); +} + +TEST(ONNXFeConvertException, exception_if_both_unsupported_and_other_translation_exception) { + const auto path = CommonTestUtils::getModelFromTestModelZoo( + std::string(ONNX_TEST_MODELS) + "unsupported_ops/unsupported_add_and_incorrect_dts.onnx"); + + OV_EXPECT_THROW( + convert(path), + ov::AssertFailure, + testing::HasSubstr("OpenVINO does not support the following ONNX operations: test_domain.UnsupportedAdd")); + OV_EXPECT_THROW(convert(path), ov::AssertFailure, testing::EndsWith("only 'DCR' and 'CRD' modes are supported\n")); +} + +TEST(ONNXFeConvertException, exception_if_both_unsupported_onnx_validation_exception_and_other_exception) { + const auto path = CommonTestUtils::getModelFromTestModelZoo( + std::string(ONNX_TEST_MODELS) + "unsupported_ops/unsupported_add_incorrect_dts_and_inst_norm_bad_scale.onnx"); + + OV_EXPECT_THROW( + convert(path), + ov::AssertFailure, + testing::HasSubstr("OpenVINO does not support the following ONNX operations: test_domain.UnsupportedAdd")); + OV_EXPECT_THROW(convert(path), ov::AssertFailure, testing::HasSubstr("'stop' input is not a scalar")); + OV_EXPECT_THROW(convert(path), ov::AssertFailure, testing::EndsWith("only 'DCR' and 'CRD' modes are supported\n")); +} diff --git a/src/frontends/onnx/tests/models/unsupported_ops/add_unsupported.prototxt b/src/frontends/onnx/tests/models/unsupported_ops/add_unsupported.prototxt new file mode 100644 index 00000000000..898670a5214 --- /dev/null +++ b/src/frontends/onnx/tests/models/unsupported_ops/add_unsupported.prototxt @@ -0,0 +1,76 @@ +ir_version: 3 +producer_name: "OV ONNX FE" +graph { + node { + input: "A" + input: "B" + output: "X" + name: "add_node1" + op_type: "Add" + } + node { + input: "X" + input: "C" + output: "Y" + op_type: "UnsupportedAdd" + domain: "test_domain" + } + name: "test_graph" + input { + name: "A" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "B" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "C" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "Y" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } +} +opset_import { + domain: "test_domain" + version: 4 + +} diff --git a/src/frontends/onnx/tests/models/unsupported_ops/two_unsupported_nodes.prototxt b/src/frontends/onnx/tests/models/unsupported_ops/two_unsupported_nodes.prototxt new file mode 100644 index 00000000000..21027514861 --- /dev/null +++ b/src/frontends/onnx/tests/models/unsupported_ops/two_unsupported_nodes.prototxt @@ -0,0 +1,77 @@ +ir_version: 3 +producer_name: "OV ONNX FE" +graph { + node { + input: "A" + input: "B" + output: "X" + op_type: "UnsupportedAdd" + } + node { + input: "X" + input: "C" + output: "Y" + op_type: "Add" + } + node { + input: "Y" + output: "Y_out" + op_type: "UnsupportedAbs" + } + name: "test_graph" + input { + name: "A" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "B" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "C" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "Y_out" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } +} +opset_import { + version: 4 +} diff --git a/src/frontends/onnx/tests/models/unsupported_ops/unsupported_add_and_incorrect_dts.prototxt b/src/frontends/onnx/tests/models/unsupported_ops/unsupported_add_and_incorrect_dts.prototxt new file mode 100644 index 00000000000..37f3ff6c370 --- /dev/null +++ b/src/frontends/onnx/tests/models/unsupported_ops/unsupported_add_and_incorrect_dts.prototxt @@ -0,0 +1,79 @@ +ir_version: 3 +producer_name: "nGraph ONNX Importer" +graph { + node { + input: "A" + output: "B" + op_type: "DepthToSpace" + attribute { + name: "blocksize" + i: 2 + type: INT + } + attribute { + name: "mode" + s: "blocks_first" + type: STRING + } + } + node { + input: "B" + output: "D" + op_type: "Abs" + } + node { + input: "B" + input: "D" + output: "Y" + op_type: "UnsupportedAdd" + domain: "test_domain" + } + name: "compute_graph" + input { + name: "A" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 8 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "Y" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 4 + } + dim { + dim_value: 4 + } + } + } + } + } +} +opset_import { + version: 11 +} diff --git a/src/frontends/onnx/tests/models/unsupported_ops/unsupported_add_incorrect_dts_and_inst_norm_bad_scale.prototxt b/src/frontends/onnx/tests/models/unsupported_ops/unsupported_add_incorrect_dts_and_inst_norm_bad_scale.prototxt new file mode 100644 index 00000000000..cdeb2551edf --- /dev/null +++ b/src/frontends/onnx/tests/models/unsupported_ops/unsupported_add_incorrect_dts_and_inst_norm_bad_scale.prototxt @@ -0,0 +1,105 @@ +ir_version: 3 +producer_name: "nGraph ONNX Importer" +graph { + node { + input: "A" + output: "B" + op_type: "DepthToSpace" + attribute { + name: "blocksize" + i: 2 + type: INT + } + attribute { + name: "mode" + s: "blocks_first" + type: STRING + } + } + node { + input: "B" + output: "D" + op_type: "Abs" + } + node { + input: "B" + input: "D" + output: "Y" + op_type: "UnsupportedAdd" + domain: "test_domain" + } + node { + input: "Y" + input: "scale" + input: "bias" + output: "Z" + op_type: "InstanceNormalization" + attribute { + name: "epsilon" + f: 0.01 + type: FLOAT + } + } + name: "compute_graph" + input { + name: "A" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 8 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "scale" + type { + tensor_type { + elem_type: 4 + shape { + dim { + dim_value: 2 + } + + } + } + } + } + input { + name: "bias" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + + } + } + } + } + output { + name: "Z" + type { + tensor_type { + elem_type: 1 + } + } + } +} +opset_import { + version: 11 +} diff --git a/src/frontends/onnx/tests/onnx_import.in.cpp b/src/frontends/onnx/tests/onnx_import.in.cpp index df1caca387a..5466a8afb56 100644 --- a/src/frontends/onnx/tests/onnx_import.in.cpp +++ b/src/frontends/onnx/tests/onnx_import.in.cpp @@ -281,7 +281,7 @@ NGRAPH_TEST(${BACKEND_NAME}, onnx_model_custom_op_register_unregister) { FAIL() << "Expected ngraph::ngraph_error"; } catch (ngraph::ngraph_error const& err) { std::string what{err.what()}; - EXPECT_NE(what.find("Check 'unknown_operators.empty()' failed"), std::string::npos); + EXPECT_NE(what.find("OpenVINO does not support the following ONNX operations:"), std::string::npos); } catch (...) { FAIL() << "Expected ngraph::ngraph_error"; }