-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Unify unconverted ops reporting in frontends #33710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| // Copyright (C) 2018-2026 Intel Corporation | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <functional> | ||
| #include <map> | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <set> | ||
| #include <string> | ||
|
|
||
| #include "openvino/core/model.hpp" | ||
| #include "openvino/frontend/extension/telemetry.hpp" | ||
| #include "openvino/op/util/framework_node.hpp" | ||
| #include "openvino/op/util/multi_subgraph_base.hpp" | ||
|
|
||
| namespace ov { | ||
| namespace frontend { | ||
|
|
||
| /// \brief Structure containing information about unconverted operations | ||
| /// Map of operation types with no conversion rule (op_type -> empty string) | ||
| /// or operations that failed during conversion (op_type -> error message) | ||
| struct UnconvertedOpsReport { | ||
| std::map<std::string, std::string> unconverted_ops; | ||
|
|
||
| bool has_issues() const; | ||
|
|
||
| /// \brief Add an unconverted operation if not already present | ||
| /// \param op_type Operation type name | ||
| /// \param error_message Error message (empty if no converter found, non-empty if conversion failed) | ||
| void add(const std::string& op_type, const std::string& error_message = {}); | ||
|
|
||
| /// \brief Merge another report into this one | ||
| void merge(const UnconvertedOpsReport& other); | ||
| }; | ||
|
|
||
| /// \brief Callback type for extracting unconverted operation info from framework-specific nodes | ||
| /// \param node The node to check | ||
| /// \return Optional pair of (op_type, error_message) if this is an unconverted framework node | ||
| using FrameworkNodeExtractor = | ||
| std::function<std::optional<std::pair<std::string, std::string>>(const std::shared_ptr<ov::Node>&)>; | ||
|
|
||
| /// \brief Collect unconverted operations from a model | ||
| /// \param model The model to scan | ||
| /// \param extractor Callback to extract info from framework-specific nodes | ||
| /// \return Report containing all unconverted operations found | ||
| UnconvertedOpsReport collect_unconverted_ops(const std::shared_ptr<ov::Model>& model, | ||
| const FrameworkNodeExtractor& extractor); | ||
|
|
||
| /// \brief Callback type for adding additional error information | ||
| /// \param unsupported_ops Set of unsupported operation types (those without error messages) | ||
| /// \return Additional message to append to the error report | ||
| using AdditionalErrorCallback = std::function<std::string(const std::set<std::string>&)>; | ||
|
|
||
| /// \brief Check conversion result, send telemetry, and optionally throw if there are unconverted operations | ||
| /// \param report The unconverted operations report | ||
| /// \param telemetry Telemetry extension (can be nullptr) | ||
| /// \param telemetry_prefix Frontend name prefix for telemetry events (e.g., "pytorch", "tf", "onnx", "jax") | ||
| /// \param error_message_prefix Prefix for filtering error messages in telemetry (e.g., "[PyTorch Frontend] ") | ||
| /// If non-empty, error_info telemetry events will be sent | ||
| /// \param additional_error Additional error message (e.g., from normalize step) | ||
| /// \param additional_callback Optional callback for frontend-specific additional error messages | ||
| /// \param throw_on_issues If true (default), throws when there are unconverted ops or additional_error is non-empty | ||
| /// \throws ov::frontend::OpConversionFailure if throw_on_issues is true and there are issues | ||
| void check_unconverted_ops(const UnconvertedOpsReport& report, | ||
| const std::shared_ptr<TelemetryExtension>& telemetry, | ||
| const std::string& telemetry_prefix, | ||
| const std::string& error_message_prefix = {}, | ||
| const std::string& additional_error = {}, | ||
| const AdditionalErrorCallback& additional_callback = nullptr, | ||
| bool throw_on_issues = true); | ||
|
|
||
| } // namespace frontend | ||
| } // namespace ov | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,147 @@ | ||||||
| // Copyright (C) 2018-2026 Intel Corporation | ||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||
| // | ||||||
|
|
||||||
| #include "unconverted_ops_report.hpp" | ||||||
|
|
||||||
| #include <sstream> | ||||||
|
|
||||||
| #include "openvino/frontend/exception.hpp" | ||||||
| #include "openvino/util/common_util.hpp" | ||||||
|
|
||||||
| namespace ov { | ||||||
| namespace frontend { | ||||||
|
|
||||||
| bool UnconvertedOpsReport::has_issues() const { | ||||||
| return !unconverted_ops.empty(); | ||||||
| } | ||||||
|
|
||||||
| void UnconvertedOpsReport::add(const std::string& op_type, const std::string& error_message) { | ||||||
| if (unconverted_ops.find(op_type) == unconverted_ops.end()) { | ||||||
| unconverted_ops[op_type] = error_message; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| void UnconvertedOpsReport::merge(const UnconvertedOpsReport& other) { | ||||||
| for (const auto& [op_type, msg] : other.unconverted_ops) { | ||||||
| add(op_type, msg); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| UnconvertedOpsReport collect_unconverted_ops(const std::shared_ptr<ov::Model>& model, | ||||||
| const FrameworkNodeExtractor& extractor) { | ||||||
| UnconvertedOpsReport report; | ||||||
| if (!model) { | ||||||
| return report; | ||||||
| } | ||||||
|
|
||||||
| for (const auto& node : model->get_ordered_ops()) { | ||||||
| // Try framework-specific extractor first | ||||||
| if (auto result = extractor(node)) { | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| report.add(result->first, result->second); | ||||||
| } | ||||||
|
|
||||||
| // Handle MultiSubGraphOp (parent of Loop, If, etc.) - common for all frontends | ||||||
| if (const auto& subgraph_op = ov::as_type_ptr<ov::op::util::MultiSubGraphOp>(node)) { | ||||||
| for (size_t i = 0; i < subgraph_op->get_internal_subgraphs_size(); ++i) { | ||||||
| report.merge(collect_unconverted_ops(subgraph_op->get_function(i), extractor)); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| return report; | ||||||
| } | ||||||
|
|
||||||
| namespace { | ||||||
|
|
||||||
| std::string format_unconverted_ops_report(const UnconvertedOpsReport& report, | ||||||
| const std::string& additional_error, | ||||||
| const AdditionalErrorCallback& additional_callback) { | ||||||
| std::stringstream error_msg; | ||||||
| std::stringstream unconverted_ops_msg; | ||||||
| std::stringstream failed_ops_msg; | ||||||
| std::stringstream failed_ops_short; | ||||||
|
|
||||||
| error_msg << "Model wasn't fully converted."; | ||||||
| unconverted_ops_msg << "-- No conversion rule found for operations: "; | ||||||
| failed_ops_msg << " Failed operations detailed log:"; | ||||||
| failed_ops_short << "-- Conversion is failed for: "; | ||||||
|
Comment on lines
+64
to
+67
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we impact telemetry mechanism that collect fails and separate into FEs by [FW_NAME]?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, that message is not processed by telemetry. The telemetry logic is preserved |
||||||
|
|
||||||
| bool has_unsupported = false; | ||||||
| bool has_failed = false; | ||||||
| std::set<std::string> unsupported_ops_set; | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| for (const auto& [op_type, error] : report.unconverted_ops) { | ||||||
| if (error.empty()) { | ||||||
| // No conversion rule found | ||||||
| if (has_unsupported) { | ||||||
| unconverted_ops_msg << ", "; | ||||||
| } | ||||||
| unconverted_ops_msg << op_type; | ||||||
| unsupported_ops_set.insert(op_type); | ||||||
| has_unsupported = true; | ||||||
| } else { | ||||||
| // Conversion failed with error | ||||||
| if (has_failed) { | ||||||
| failed_ops_short << ", "; | ||||||
| } | ||||||
| failed_ops_short << op_type; | ||||||
| failed_ops_msg << "\n-- " << op_type << " with a message:\n" << error; | ||||||
| has_failed = true; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (has_failed) { | ||||||
| error_msg << failed_ops_msg.str(); | ||||||
| } | ||||||
|
|
||||||
| error_msg << "\nSummary:" << additional_error; | ||||||
|
|
||||||
| if (has_unsupported) { | ||||||
| error_msg << '\n' << unconverted_ops_msg.str(); | ||||||
| } | ||||||
|
|
||||||
| if (has_failed) { | ||||||
| error_msg << '\n' << failed_ops_short.str(); | ||||||
| } | ||||||
|
|
||||||
| // Add additional callback-provided information | ||||||
| if (additional_callback && has_unsupported) { | ||||||
| if (auto additional_info = additional_callback(unsupported_ops_set); !additional_info.empty()) { | ||||||
| error_msg << '\n' << additional_info; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return error_msg.str(); | ||||||
| } | ||||||
|
|
||||||
| } // namespace | ||||||
|
|
||||||
| void check_unconverted_ops(const UnconvertedOpsReport& report, | ||||||
| const std::shared_ptr<TelemetryExtension>& telemetry, | ||||||
| const std::string& telemetry_prefix, | ||||||
| const std::string& error_message_prefix, | ||||||
| const std::string& additional_error, | ||||||
| const AdditionalErrorCallback& additional_callback, | ||||||
| bool throw_on_issues) { | ||||||
| // Send telemetry for all unconverted operations | ||||||
| if (telemetry) { | ||||||
| const bool send_error_info = !error_message_prefix.empty(); | ||||||
| for (const auto& [op_type, error] : report.unconverted_ops) { | ||||||
| telemetry->send_event("error_cause", telemetry_prefix + "_" + op_type); | ||||||
| if (send_error_info && !error.empty()) { | ||||||
| auto cropped_message = ov::util::filter_lines_by_prefix(error, error_message_prefix); | ||||||
| if (!cropped_message.empty()) { | ||||||
| telemetry->send_event("error_info", cropped_message); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (throw_on_issues && (report.has_issues() || !additional_error.empty())) { | ||||||
| FRONT_END_OP_CONVERSION_CHECK(false, | ||||||
| format_unconverted_ops_report(report, additional_error, additional_callback)); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| } // namespace frontend | ||||||
| } // namespace ov | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.