diff --git a/ngraph/frontend/onnx_import/include/onnx_import/editor/detail/subgraph_extraction.hpp b/ngraph/frontend/onnx_import/include/onnx_import/editor/detail/subgraph_extraction.hpp new file mode 100644 index 00000000000..a005bb3d1c4 --- /dev/null +++ b/ngraph/frontend/onnx_import/include/onnx_import/editor/detail/subgraph_extraction.hpp @@ -0,0 +1,164 @@ +//***************************************************************************** +// Copyright 2017-2021 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +#pragma once + +#include +#include +#include +#include +#include + +namespace ONNX_NAMESPACE +{ + class GraphProto; + class NodeProto; + class ValueInfoProto; +} // namespace ONNX_NAMESPACE + +namespace ngraph +{ + enum class EdgeType + { + INPUT, + OUTPUT + }; + + template + struct Edge + { + Edge() = delete; + Edge(const int node_idx, std::string tensor_name) + : m_node_idx{node_idx} + , m_tensor_name{std::move(tensor_name)} + { + } + + const int m_node_idx; + const std::string m_tensor_name; + }; + namespace onnx_import + { + /// \brief Defines an edge connected to an input of any node in the graph. + /// It consists of a node index in the processed ONNX model and the input name. + /// The index should point to a node in the topological sort of the underlying graph + /// which means it has to be in range: 0 <= node_idx < graph.node_size() + /// + /// For a node number 5, with 3 inputs: + /// + /// ----(in_A)----> +--------+ + /// ----(in_B)----> | node 5 | ----(out)----> + /// ----(in_C)----> +--------+ + /// + /// there are 3 possible valid instances of this struct: + /// InputEdge(5, "in_A") + /// InputEdge(5, "in_B") + /// InputEdge(5, "in_C") + using InputEdge = Edge; + + /// \brief Defines an edge connected to an output of any node in the graph. + /// It consists of a node index in the processed ONNX model and the output name. + /// + /// For a node number 5, with 2 outputs: + /// + /// +--------+ ----(out1)----> + /// ----(in_A)----> | node 5 | + /// +--------+ ----(out2)----> + /// + /// there are 2 possible valid instances of this struct: + /// OutputEdge(5, "out1") + /// OutputEdge(5, "out2") + using OutputEdge = Edge; + + /// \brief Subgraph extraction helper structure + struct SubgraphExtractor + { + SubgraphExtractor(ONNX_NAMESPACE::GraphProto& graph); + + /// \brief Adds new inputs to the graph and connects them to the nodes indicated by + /// the provided input edges. + void add_new_inputs(const std::vector& new_inputs); + + /// \brief Adds new outputs to the graph with the same name as the nodes pointed to + /// by the input edges "new_outputs". + void add_new_outputs(const std::vector& new_outputs); + + /// \brief Extracts the final subgraph by traversing the original model bottom-up + /// starting at each of the provided output edges. The extracted subgraph + /// contains all previously added inputs and potentially a subset of original + /// model's inputs that contribute to the value calculated in the output tensors. + /// In the end the underlying GraphProto is modified and obsolete elements + /// are discarded after this method call has finished. + /// + /// \param subgraph_outputs A list of expected outputs of the extracted subgraph. + void extract_subgraph(std::vector subgraph_outputs); + + /// \brief Represents a subgraph of an ONNX model by holding a subset of nodes, inputs, + /// outputs and initializers of the original graph. Objects of this struct can be + /// merged into other instances using the += operator to build a subgraph from + /// smaller clusters. + struct SubgraphComponents + { + SubgraphComponents() = default; + SubgraphComponents(const SubgraphComponents&) = delete; + SubgraphComponents(SubgraphComponents&&) = default; + SubgraphComponents& operator=(const SubgraphComponents&) = delete; + SubgraphComponents& operator=(SubgraphComponents&&) = default; + + std::set nodes; + std::set inputs; + std::set initializers; + std::set outputs; + + SubgraphComponents& operator+=(SubgraphComponents&& other) + { + nodes.insert(other.nodes.begin(), other.nodes.end()); + inputs.insert(other.inputs.begin(), other.inputs.end()); + initializers.insert(other.initializers.begin(), other.initializers.end()); + outputs.insert(other.outputs.begin(), other.outputs.end()); + return *this; + } + }; + + private: + ONNX_NAMESPACE::GraphProto& m_onnx_graph; + + // Graph traversal helper: node index -> node inputs (one-to-many) + std::unordered_multimap m_node_inputs; + // Number of consumers of all tensors in the graph + std::map m_tensor_consumers; + + /// \brief Replaces the old input edge with a new one in the helper struct. + /// This is used by the output contributors discovery. + void replace_input_edge(const InputEdge& old_edge, const InputEdge& new_edge); + + /// \brief Returns a list of edges of each outputs of the graph "m_onnx_graph" + std::vector all_output_edges() const; + + /// \brief Traverses the graph bottom-up and collects all nodes, inputs and initializers + /// that contribute to an output designated by the provided output edge. + /// A sum of such SubgraphComponents objects forms a target extracted subgraph. + SubgraphComponents + discover_output_contributors(const OutputEdge& output_edge, + const SubgraphComponents& already_collected) const; + + /// \brief Modifies the underlying GraphProto object and discards all obsolete elements. + /// + /// \param subgraph An object describing the subgraph to be extracted (elems to be kept) + void extract_subgraph_from_onnx_model(const SubgraphComponents& subgraph); + }; + } // namespace onnx_import +} // namespace ngraph diff --git a/ngraph/frontend/onnx_import/include/onnx_import/editor/editor.hpp b/ngraph/frontend/onnx_import/include/onnx_import/editor/editor.hpp index abd58697a08..e0981b1973c 100644 --- a/ngraph/frontend/onnx_import/include/onnx_import/editor/editor.hpp +++ b/ngraph/frontend/onnx_import/include/onnx_import/editor/editor.hpp @@ -23,6 +23,7 @@ #include "ngraph/op/constant.hpp" #include "ngraph/partial_shape.hpp" #include "ngraph/type/element_type.hpp" +#include "onnx_import/editor/detail/subgraph_extraction.hpp" #include "onnx_import/utils/onnx_importer_visibility.hpp" namespace ONNX_NAMESPACE @@ -53,7 +54,7 @@ namespace ngraph /// \param model_path Path to the file containing the model. ONNXModelEditor(const std::string& model_path); - /// \brief Modifies the in-memory representation of the model (m_model_proto) by setting + /// \brief Modifies the in-memory representation of the model by setting /// custom input types for all inputs specified in the provided map. /// /// \param input_types A collection of pairs {input_name: new_input_type} that should be @@ -62,7 +63,7 @@ namespace ngraph /// the inputs specified in its parameter. void set_input_types(const std::map& input_types); - /// \brief Modifies the in-memory representation of the model (m_model_proto) by setting + /// \brief Modifies the in-memory representation of the model by setting /// custom input shapes for all inputs specified in the provided map. /// /// \param input_shapes A collection of pairs {input_name: new_input_shape} that should @@ -71,6 +72,18 @@ namespace ngraph /// the inputs specified in its parameter. void set_input_shapes(const std::map& input_shapes); + /// \brief Extracts a subgraph constrained by input edges and output edges. In the end + /// the underlying ModelProto is modified - obsolete inputs, initializers, nodes + /// and outputs are removed from the in-memory model. + /// + /// \node Please look at the declaration of InputEdge and OutputEdge for explanation + /// how those objects can be created. If the outputs parameter is empty + /// this method keeps all of the original outputs of the model. + /// + /// \param inputs A collection of input edges which become new inputs to the graph + /// \param outputs A collection of output edges which become new outputs of the graph + void cut_graph_fragment(const std::vector& inputs, + const std::vector& outputs); /// \brief Modifies the in-memory representation of the model by setting custom input /// values for inputs specified in the provided map. /// @@ -91,11 +104,20 @@ namespace ngraph /// \return A reference to ONNX ModelProto object containing the in-memory model ONNX_NAMESPACE::ModelProto& model() const; + /// \brief Returns a serialized ONNX model, possibly modified by the editor. + std::string model_string() const; + + /// \brief Returns a list of all inputs of the in-memory model, including initializers. + /// The returned value might depend on the previous operations executed on an + /// instance of the model editor, in particular the subgraph extraction which + /// can discard some inputs and initializers from the original graph. + std::vector model_inputs() const; + /// \brief Returns the path to the original model file const std::string& model_path() const; - /// \brief Saves the possibly model held by this class to a file. Serializes in binary - /// mode. + /// \brief Saves the possibly modified model held by this class to a file. + /// Serializes in binary mode. /// /// \param out_file_path A path to the file where the modified model should be dumped. void serialize(const std::string& out_file_path) const; @@ -103,7 +125,7 @@ namespace ngraph private: const std::string m_model_path; - class Impl; + struct Impl; std::unique_ptr m_pimpl; }; } // namespace onnx_import diff --git a/ngraph/frontend/onnx_import/src/editor/detail/subgraph_extraction.cpp b/ngraph/frontend/onnx_import/src/editor/detail/subgraph_extraction.cpp new file mode 100644 index 00000000000..92407172e9c --- /dev/null +++ b/ngraph/frontend/onnx_import/src/editor/detail/subgraph_extraction.cpp @@ -0,0 +1,498 @@ +//***************************************************************************** +// Copyright 2017-2021 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +#include +#include +#include + +#include "ngraph/check.hpp" +#include "onnx_import/editor/detail/subgraph_extraction.hpp" + +using namespace ngraph::onnx_import; + +namespace +{ + void validate_node_index(const ONNX_NAMESPACE::GraphProto& graph, const int node_idx) + { + NGRAPH_CHECK( + node_idx >= 0 && node_idx < graph.node_size(), + "The specified node index is out of range of nodes in the original model(idx: ", + std::to_string(node_idx), + "; nodes count in the model: ", + std::to_string(graph.node_size()), + ")"); + } + + template + std::function name_equals(const std::string& name) + { + return [&name](const T& onnx_object) -> bool { return onnx_object.name() == name; }; + } + + const auto is_equal_to = + +[](const std::string& other) { return [&](const std::string& s) { return s == other; }; }; + + /// \brief Checks if an item with name equal to "name" already exists in the specified + /// container. A container item is expected to have a name() method. + template + bool already_exists(const Container& items, const std::string& name) + { + using std::begin; + using std::end; + return std::any_of( + begin(items), end(items), name_equals(name)); + } + + /// \brief Checks if a tensor with name "name" is produced by an input of the graph + bool is_graph_input(const ONNX_NAMESPACE::GraphProto& graph, const std::string& name) + { + return already_exists(graph.input(), name); + } + + /// \brief Checks if a tensor with name "name" is produced by an initializer of the graph + bool is_graph_initializer(const ONNX_NAMESPACE::GraphProto& graph, const std::string& name) + { + return already_exists(graph.initializer(), name); + } + + /// \brief Looks up the index of a node that produces a tensor "input_name". Used to traverse + /// the graph bottom-up. Starts from a node index "current_node_idx" because it operates + /// on a topologically sorted graph. + int find_source_node_idx(const ONNX_NAMESPACE::GraphProto& graph, + const int current_node_idx, + const std::string& input_name) + { + for (int i = current_node_idx - 1; i >= 0; --i) + { + const auto& outputs = graph.node(i).output(); + const auto output_found = + std::any_of(std::begin(outputs), std::end(outputs), is_equal_to(input_name)); + + if (output_found) + { + return i; + } + } + + throw ngraph::ngraph_error{"Source node not found in the graph for node: " + + std::to_string(current_node_idx) + " and input name: " + + input_name}; + } + + /// \brief Looks up a descriptor for a given tensor name. This descriptor contains inferred + /// shape information which is required to create new inputs and outputs in the graph. + const ONNX_NAMESPACE::ValueInfoProto& + find_tensor_descriptor(const ONNX_NAMESPACE::GraphProto& graph, + const std::string& tensor_name) + { + const auto it = std::find_if(std::begin(graph.value_info()), + std::end(graph.value_info()), + name_equals(tensor_name)); + + NGRAPH_CHECK(it != std::end(graph.value_info()), + "Could not find a tensor descriptor for tensor '", + tensor_name, + "'. It's not possible to add a new input to the graph without the type and " + "shape information of the intermediate tensor."); + + return *it; + } + + /// \brief Inserts a new input to the graph and removes an initializer that produced a tensor + /// specified by an input edge passed to this function. + void replace_initializer_with_new_input(ONNX_NAMESPACE::GraphProto& graph, + const InputEdge& edge) + { + const auto it = std::find_if(std::begin(graph.initializer()), + std::end(graph.initializer()), + name_equals(edge.m_tensor_name)); + + NGRAPH_CHECK(it != std::end(graph.initializer()), + "Could not find an initializer in the graph: '", + edge.m_tensor_name); + + if (!already_exists(graph.input(), edge.m_tensor_name)) + { + const auto& initializer = *it; + auto& new_input = *(graph.add_input()); + + auto& new_input_tensor_type = *(new_input.mutable_type()->mutable_tensor_type()); + new_input_tensor_type.set_elem_type(initializer.data_type()); + + auto& new_input_shape = *(new_input_tensor_type.mutable_shape()); + for (const auto initializer_dim : initializer.dims()) + { + auto& new_dim = *(new_input_shape.add_dim()); + new_dim.set_dim_value(initializer_dim); + } + + *(new_input.mutable_name()) = edge.m_tensor_name; + } + + graph.mutable_initializer()->erase(it); + } + + /// \brief Inserts a new input to the graph and connects it to the node designated by an input + /// edge passed to this function. + /// \return A new input edge (along with "true") if a new input was added to the graph, + /// false + the original edge otherwise. + std::pair append_new_graph_input(ONNX_NAMESPACE::GraphProto& graph, + const InputEdge& edge) + { + if (already_exists(graph.input(), edge.m_tensor_name) && + !is_graph_initializer(graph, edge.m_tensor_name)) + { + // no need to append a new input if an edge points to an existing one in the model + return {false, edge}; + } + + auto& target_node = *(graph.mutable_node(edge.m_node_idx)); + auto& node_inputs = *(target_node.mutable_input()); + auto target_input = + std::find(std::begin(node_inputs), std::end(node_inputs), edge.m_tensor_name); + + NGRAPH_CHECK(target_input != std::end(node_inputs), + "Input '", + edge.m_tensor_name, + "' not found in the inputs of node ", + edge.m_node_idx, + ". Cannot append a new graph input to this node."); + + const std::string new_input_name = target_node.output(0) + ":" + edge.m_tensor_name; + + // if an edge is connected to an initializer, the initializer is removed and substituted + // with an input + if (is_graph_initializer(graph, edge.m_tensor_name)) + { + replace_initializer_with_new_input(graph, edge); + return {false, edge}; + } + else + { + auto& new_input = *(graph.add_input()); + // copy the intermediate tensor properties to the newly created input + new_input.MergeFrom(find_tensor_descriptor(graph, edge.m_tensor_name)); + *(new_input.mutable_name()) = new_input_name; + // attach the new graph input to the target node's input + *target_input = new_input_name; + return {true, InputEdge{edge.m_node_idx, new_input_name}}; + } + } + + /// \brief Replaces a node or initializer (consumed by multiple nodes) with a new input + /// \return Returns an index of a removed node or -1 if an initializer was removed + int replace_source_with_new_input(ONNX_NAMESPACE::GraphProto& graph, const InputEdge& edge) + { + if (already_exists(graph.input(), edge.m_tensor_name) && + !is_graph_initializer(graph, edge.m_tensor_name)) + { + // happens when a user specifies multiple input edges pointing to the same tensor name + return -1; + } + + if (is_graph_initializer(graph, edge.m_tensor_name)) + { + replace_initializer_with_new_input(graph, edge); + } + else + { + auto& new_input = *(graph.add_input()); + // copy the intermediate tensor properties to the newly created input + new_input.MergeFrom(find_tensor_descriptor(graph, edge.m_tensor_name)); + + const auto source_node_idx = + find_source_node_idx(graph, edge.m_node_idx, edge.m_tensor_name); + auto& source_node = *(graph.mutable_node(source_node_idx)); + auto& node_outputs = *source_node.mutable_output(); + auto target_output = + std::find(std::begin(node_outputs), std::end(node_outputs), edge.m_tensor_name); + + NGRAPH_CHECK(target_output != std::end(node_outputs), + "Output '", + edge.m_tensor_name, + "' not found in the outputs of node ", + source_node_idx, + ". Cannot remove the output from this node."); + + // stop produsing tensor "edge.m_tensor_name" by the source node of the processed edge + *target_output = ""; + + return source_node_idx; + } + + return -1; + } + + /// \brief Adds new outputs to the ONNX graph for an edge specified by a user + /// The shape for this output is taken from a previously executed shape inference of the + /// original model. + void append_new_graph_output(ONNX_NAMESPACE::GraphProto& graph, const OutputEdge& edge) + { + if (already_exists(graph.output(), edge.m_tensor_name)) + { + return; + } + + auto& target_node = *(graph.mutable_node(edge.m_node_idx)); + const auto& node_outputs = target_node.output(); + const auto target_output = + std::find(std::begin(node_outputs), std::end(node_outputs), edge.m_tensor_name); + + NGRAPH_CHECK(target_output != std::end(node_outputs), + "Output '", + edge.m_tensor_name, + "' not found in the outputs of node ", + edge.m_node_idx, + ". Cannot append a new graph output to this node."); + + auto& new_output = *(graph.add_output()); + // copy the intermediate tensor's properties to the newly created + new_output.MergeFrom(find_tensor_descriptor(graph, edge.m_tensor_name)); + *(new_output.mutable_name()) = edge.m_tensor_name; + } + + /// \brief Removes all items from a container except the ones whose names are in items_to_keep + /// It's intended to work with ONNX graph inputs, outputs and initializers only. + template + void discard_by_name(Container& all_items, const std::set& items_to_keep) + { + static_assert( + std::is_same::value || + std::is_same::value, + "Unsupported value type of the container"); + + // The tested item can be discarded if its name is not found in the items_to_keep set + const auto can_be_discarded = [&items_to_keep](const typename Container::value_type& item) { + return items_to_keep.count(item.name()) == 0; + }; + + using std::begin; + using std::end; + + // move the elements-to-discard to the end of the container + const auto new_end = std::remove_if(begin(all_items), end(all_items), can_be_discarded); + // erase all of the discarded elements past the new end of the container + all_items.erase(new_end, end(all_items)); + } + + /// \brief Removes all nodes from a container keeping the ones whose index is in nodes_to_keep + template + void discard_nodes(Container& all_nodes, const std::set& nodes_to_keep) + { + static_assert( + std::is_same::value, + "Unsupported value type of the container"); + + int idx = 0; + const auto discard_node = [&idx, &nodes_to_keep](const typename Container::value_type&) { + return nodes_to_keep.count(idx++) == 0; + }; + + using std::begin; + using std::end; + + const auto new_end = std::remove_if(begin(all_nodes), end(all_nodes), discard_node); + all_nodes.erase(new_end, end(all_nodes)); + } +} // namespace + +/* -----------------------------------------------------------------------------------------------*/ + +SubgraphExtractor::SubgraphExtractor(ONNX_NAMESPACE::GraphProto& graph) + : m_onnx_graph(graph) +{ + // gathers information about the graph - input edges of every node and number of "consumers" + // of all tensors in the graph + for (int i = 0; i < graph.node_size(); ++i) + { + for (const auto& node_input : graph.node(i).input()) + { + m_node_inputs.insert({i, node_input}); + m_tensor_consumers[node_input] += 1; + } + } +} + +void SubgraphExtractor::add_new_inputs(const std::vector& new_inputs) +{ + for (const auto& input_edge : new_inputs) + { + validate_node_index(m_onnx_graph, input_edge.m_node_idx); + + // if a tensor has multiple consumers, its producer(source) should be replaced with a new + // input - this way all consumers of this tensor will now be connected to a new graph input + if (m_tensor_consumers[input_edge.m_tensor_name] > 1) + { + // remove a node or initializer from a model and insert a new input instead + int idx = replace_source_with_new_input(m_onnx_graph, input_edge); + if (idx != -1) + { + // if a node was replaced with an input, remove input edges from a helper multimap + // for this node because it won't end up in the target subgraph + // m_node_inputs stores information about existing edges in the graph, + // when a node is removed/replaced, information about its edges should also + // be removed (this way this node will be discarded from the original graph) + m_node_inputs.erase(idx); + } + } + else + { + // in case an edge is connected to a single node, a new graph input should be added + // and connected to that node; the new edge is an edge between the node and new input + const auto& new_edge = append_new_graph_input(m_onnx_graph, input_edge); + if (new_edge.first) + { + // the original edge should be replaced with a new one in the helper multimap + // this information will later be used during the subgraph extraction stage + replace_input_edge(input_edge, new_edge.second); + } + } + } +} + +void SubgraphExtractor::add_new_outputs(const std::vector& new_outputs) +{ + for (const auto& output_edge : new_outputs) + { + validate_node_index(m_onnx_graph, output_edge.m_node_idx); + + append_new_graph_output(m_onnx_graph, output_edge); + } +} + +void SubgraphExtractor::replace_input_edge(const InputEdge& old_edge, const InputEdge& new_edge) +{ + // old_edge = {5, "x"}; new_edge = {5, "y"} + // for a given node index "N", find all of its inputs in the helper multimap (pair of iterators) + // using those iterators find the name of an input tensor that needs to be replaced + const auto node_inputs = m_node_inputs.equal_range(old_edge.m_node_idx); + auto old_input_name = node_inputs.first; + + // find an iterator pointing to an input name that should + while (old_input_name->second != old_edge.m_tensor_name && old_input_name != node_inputs.second) + { + ++old_input_name; + } + + // finally remove the old edge from the helper map and insert a new edge + m_node_inputs.erase(old_input_name); + m_node_inputs.insert({new_edge.m_node_idx, new_edge.m_tensor_name}); +} + +void SubgraphExtractor::extract_subgraph(std::vector subgraph_outputs) +{ + // when the user doesn't specify any outputs, all outputs of the original graph should be kept + if (subgraph_outputs.empty()) + { + subgraph_outputs = all_output_edges(); + } + + SubgraphComponents subgraph; + + for (const auto& output_edge : subgraph_outputs) + { + // for each output edge find the nodes, inputs and initializers that contribute to the value + // produced by this output - "output contributors" + // a sum of all contributors of all outputs is the target subgraph + subgraph += discover_output_contributors(output_edge, subgraph); + } + + // using the subgraph components collected above, modify the underlying GraphProto + extract_subgraph_from_onnx_model(subgraph); +} + +SubgraphExtractor::SubgraphComponents SubgraphExtractor::discover_output_contributors( + const OutputEdge& output_edge, const SubgraphComponents& already_collected) const +{ + const auto already_visited = [&already_collected](const int node_index) { + return already_collected.nodes.count(node_index) > 0; + }; + + SubgraphComponents output_contributors; + output_contributors.outputs.insert(output_edge.m_tensor_name); + + // reverse DFS graph traversal + std::stack nodes_to_visit; + nodes_to_visit.push(output_edge.m_node_idx); + + while (!nodes_to_visit.empty()) + { + const auto n = nodes_to_visit.top(); + nodes_to_visit.pop(); + + // if a node has already been visited, return early because it's already marked as + // a node to keep in the final extracted subgraph + if (already_visited(n)) + { + continue; + } + + output_contributors.nodes.insert(n); + + // check if the visitor reached any of the graph inputs + // and/or keep looking for more contributors further up in the graph + + // when an input or initializer is reached, the visitor stops the lookup + const auto n_inputs = m_node_inputs.equal_range(n); + for (auto input_name = n_inputs.first; input_name != n_inputs.second; ++input_name) + { + if (is_graph_input(m_onnx_graph, input_name->second)) + { + output_contributors.inputs.insert(input_name->second); + // when an initializer has a matching graph input + if (is_graph_initializer(m_onnx_graph, input_name->second)) + { + output_contributors.initializers.insert(input_name->second); + } + } + else if (is_graph_initializer(m_onnx_graph, input_name->second)) + { + // when an initializer doesn't have a corresponding input + output_contributors.initializers.insert(input_name->second); + } + else + { + // if an edge points to another node (source node) it should be visited + // in one of the future iterations + nodes_to_visit.push(find_source_node_idx(m_onnx_graph, n, input_name->second)); + } + } + } + + return output_contributors; +} + +void SubgraphExtractor::extract_subgraph_from_onnx_model(const SubgraphComponents& subgraph) +{ + discard_by_name(*(m_onnx_graph.mutable_input()), subgraph.inputs); + discard_by_name(*(m_onnx_graph.mutable_initializer()), subgraph.initializers); + discard_by_name(*(m_onnx_graph.mutable_output()), subgraph.outputs); + discard_nodes(*(m_onnx_graph.mutable_node()), subgraph.nodes); +} + +std::vector SubgraphExtractor::all_output_edges() const +{ + std::vector all_outputs; + + for (const auto& graph_output : m_onnx_graph.output()) + { + all_outputs.emplace_back( + find_source_node_idx(m_onnx_graph, m_onnx_graph.node_size(), graph_output.name()), + graph_output.name()); + } + + return all_outputs; +} diff --git a/ngraph/frontend/onnx_import/src/editor/editor.cpp b/ngraph/frontend/onnx_import/src/editor/editor.cpp index d23cabc6b35..e5f0c04018b 100644 --- a/ngraph/frontend/onnx_import/src/editor/editor.cpp +++ b/ngraph/frontend/onnx_import/src/editor/editor.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "ngraph/log.hpp" #include "onnx_import/editor/editor.hpp" @@ -157,6 +158,12 @@ namespace } } + template + std::string extract_name(const T& input_or_initializer) + { + return input_or_initializer.name(); + }; + void modify_initializer(TensorProto& initializer, const std::string& name, const std::shared_ptr values, @@ -211,6 +218,9 @@ struct onnx_import::ONNXModelEditor::Impl : m_model_proto{std::move(parse_from_file(model_path))} { } + + void infer_shapes() { ONNX_NAMESPACE::shape_inference::InferShapes(m_model_proto); } + void remove_shape_inference_info() { m_model_proto.mutable_graph()->clear_value_info(); } }; onnx_import::ONNXModelEditor::ONNXModelEditor(const std::string& model_path) @@ -289,6 +299,49 @@ void onnx_import::ONNXModelEditor::set_input_shapes( } } +void onnx_import::ONNXModelEditor::cut_graph_fragment(const std::vector& inputs, + const std::vector& outputs) +{ + if (inputs.empty() && outputs.empty()) + { + return; + } + + m_pimpl->infer_shapes(); + + SubgraphExtractor editor{*(m_pimpl->m_model_proto.mutable_graph())}; + editor.add_new_inputs(inputs); + editor.add_new_outputs(outputs); + editor.extract_subgraph(outputs); + + m_pimpl->remove_shape_inference_info(); +} + +std::vector onnx_import::ONNXModelEditor::model_inputs() const +{ + const auto& graph = m_pimpl->m_model_proto.graph(); + + std::vector inputs_and_initializers; + inputs_and_initializers.reserve(graph.input_size() + graph.initializer_size()); + + std::transform(graph.input().begin(), + graph.input().end(), + std::back_inserter(inputs_and_initializers), + extract_name); + + std::transform(graph.initializer().begin(), + graph.initializer().end(), + std::back_inserter(inputs_and_initializers), + extract_name); + + return inputs_and_initializers; +} + +std::string onnx_import::ONNXModelEditor::model_string() const +{ + return m_pimpl->m_model_proto.SerializeAsString(); +} + void onnx_import::ONNXModelEditor::set_input_values( const std::map>& input_values) { diff --git a/ngraph/frontend/onnx_import/src/onnx.cpp b/ngraph/frontend/onnx_import/src/onnx.cpp index 1af5bcf7a67..be6c35a6301 100644 --- a/ngraph/frontend/onnx_import/src/onnx.cpp +++ b/ngraph/frontend/onnx_import/src/onnx.cpp @@ -15,8 +15,6 @@ //***************************************************************************** #include -#include -#include #include #include "core/graph.hpp" @@ -82,8 +80,6 @@ namespace ngraph std::shared_ptr import_onnx_model(const ONNXModelEditor& model_editor) { - // this overload of the import_onnx_model is friended with the ONNXModelEditor - // and thus can access its private members return detail::import_onnx_model(model_editor.model(), model_editor.model_path()); } diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__existing_inputs_and_outputs_based_extraction.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__existing_inputs_and_outputs_based_extraction.onnx new file mode 100644 index 00000000000..3cff162f375 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__existing_inputs_and_outputs_based_extraction.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__existing_inputs_and_outputs_based_extraction.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__existing_inputs_and_outputs_based_extraction.prototxt new file mode 100644 index 00000000000..535ab6a83b4 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__existing_inputs_and_outputs_based_extraction.prototxt @@ -0,0 +1,132 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "relu1" + input: "in2" + output: "add1" + op_type: "Add" + } + node { + input: "in3" + input: "in4" + output: "conv1" + op_type: "Conv" + } + node { + input: "add1" + input: "conv1" + output: "mul2" + op_type: "Mul" + } + name: "subgraph_extraction_testing" + initializer { + dims: 1 + dims: 1 + dims: 1 + dims: 1 + data_type: 1 + float_data: 1 + name: "in4" + } + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in3" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in4" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "mul2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_to_input_replacement.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_to_input_replacement.onnx new file mode 100644 index 00000000000..4046c6ee23b Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_to_input_replacement.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_to_input_replacement.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_to_input_replacement.prototxt new file mode 100644 index 00000000000..64d29267fb6 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_to_input_replacement.prototxt @@ -0,0 +1,121 @@ +ir_version: 3 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_b_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + + output { + name: "conv1/7x7_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } +} + +opset_import { + version: 8 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_without_matching_input_tail_cut.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_without_matching_input_tail_cut.onnx new file mode 100644 index 00000000000..94374ba41ab Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_without_matching_input_tail_cut.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_without_matching_input_tail_cut.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_without_matching_input_tail_cut.prototxt new file mode 100644 index 00000000000..a26c205ebc8 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__initializer_without_matching_input_tail_cut.prototxt @@ -0,0 +1,120 @@ +ir_version: 3 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + node { + input: "conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + initializer { + dims: 1 + data_type: 1 + name: "conv1/7x7_s2_b_0" + float_data: 3.141592 + } + + output { + name: "conv1/7x7_s2_2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } +} + +opset_import { + version: 8 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers.onnx new file mode 100644 index 00000000000..8c581788d0b Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers.prototxt new file mode 100644 index 00000000000..ade2123bafd --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers.prototxt @@ -0,0 +1,166 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "relu1" + input: "in2" + output: "add1" + op_type: "Add" + } + node { + input: "in3" + input: "in4" + output: "conv1" + op_type: "Conv" + } + node { + input: "relu1" + input: "add1" + output: "add2" + op_type: "Add" + } + node { + input: "add1" + input: "conv1" + output: "mul2" + op_type: "Mul" + } + node { + input: "add2" + output: "split1" + output: "split2" + op_type: "Split" + attribute { + name: "axis" + i: 1 + type: INT + } + } + node { + input: "relu1" + input: "split1" + output: "mul1" + op_type: "Mul" + } + name: "subgraph_extraction_testing" + initializer { + dims: 1 + dims: 1 + dims: 1 + dims: 1 + data_type: 1 + float_data: 1 + name: "in4" + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in3" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in4" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "relu1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "mul1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "mul2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_2.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_2.onnx new file mode 100644 index 00000000000..5ce30259ad4 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_2.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_2.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_2.prototxt new file mode 100644 index 00000000000..5d62d03bb61 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_2.prototxt @@ -0,0 +1,149 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in3" + input: "in4" + output: "conv1" + op_type: "Conv" + } + node { + input: "relu1" + input: "add1" + output: "add2" + op_type: "Add" + } + node { + input: "add1" + input: "conv1" + output: "mul2" + op_type: "Mul" + } + name: "subgraph_extraction_testing" + initializer { + dims: 1 + dims: 1 + dims: 1 + dims: 1 + data_type: 1 + float_data: 1 + name: "in4" + } + input { + name: "in3" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in4" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "relu1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "add1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "mul2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "add2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_3.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_3.onnx new file mode 100644 index 00000000000..93af27af95f Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_3.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_3.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_3.prototxt new file mode 100644 index 00000000000..ea70f359331 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_3.prototxt @@ -0,0 +1,95 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "relu1" + input: "in2" + output: "add1" + op_type: "Add" + } + node { + input: "relu1" + input: "add1" + output: "add2" + op_type: "Add" + } + node { + input: "add2" + output: "split1" + output: "split2" + op_type: "Split" + attribute { + name: "axis" + i: 1 + type: INT + } + } + node { + input: "relu1" + input: "split1" + output: "mul1" + op_type: "Mul" + } + name: "subgraph_extraction_testing" + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "relu1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "mul1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "split2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 1 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.onnx new file mode 100644 index 00000000000..8d4f832795a Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.prototxt new file mode 100644 index 00000000000..6c718ff0b39 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.prototxt @@ -0,0 +1,82 @@ +ir_version: 7 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "pool1/3x3_s2_1:conv1/7x7_s2_2" + output: "pool1/3x3_s2_1" + name: "" + op_type: "MaxPool" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 3 + ints: 3 + type: INTS + } + } + + input { + name: "pool1/3x3_s2_1:conv1/7x7_s2_2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } + + output { + name: "pool1/3x3_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 55 + } + dim { + dim_value: 55 + } + } + } + } + } +} + +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.onnx new file mode 100644 index 00000000000..fed4a096f76 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.prototxt new file mode 100644 index 00000000000..dd42e019cfb --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.prototxt @@ -0,0 +1,121 @@ +ir_version: 7 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_b_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + } + } + } + } + + output { + name: "conv1/7x7_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } +} + +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_head_cut.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_head_cut.onnx new file mode 100644 index 00000000000..75a4087c4aa Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_head_cut.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_head_cut.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_head_cut.prototxt new file mode 100644 index 00000000000..46c5056cb63 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_head_cut.prototxt @@ -0,0 +1,88 @@ +ir_version: 7 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "conv1/7x7_s2_2:conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + node { + input: "conv1/7x7_s2_2" + output: "pool1/3x3_s2_1" + name: "" + op_type: "MaxPool" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 3 + ints: 3 + type: INTS + } + } + + input { + name: "conv1/7x7_s2_2:conv1/7x7_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } + + output { + name: "pool1/3x3_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 55 + } + dim { + dim_value: 55 + } + } + } + } + } +} + +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_tail_cut.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_tail_cut.onnx new file mode 100644 index 00000000000..0ce0abd31bc Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_tail_cut.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_tail_cut.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_tail_cut.prototxt new file mode 100644 index 00000000000..59efc1294d5 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_tail_cut.prototxt @@ -0,0 +1,127 @@ +ir_version: 7 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + node { + input: "conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_b_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + } + } + } + } + + output { + name: "conv1/7x7_s2_2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } +} + +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.onnx new file mode 100644 index 00000000000..62c7ad443f8 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.prototxt new file mode 100644 index 00000000000..ad845070c83 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.prototxt @@ -0,0 +1,134 @@ +ir_version: 3 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + node { + input: "conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_b_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + + initializer { + dims: 1 + data_type: 1 + name: "conv1/7x7_s2_b_0" + float_data: 3.141592 + } + + output { + name: "conv1/7x7_s2_2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 112 + } + dim { + dim_value: 112 + } + } + } + } + } +} + +opset_import { + version: 8 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiout_op_output_edge.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiout_op_output_edge.onnx new file mode 100644 index 00000000000..537bebf1c54 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiout_op_output_edge.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiout_op_output_edge.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiout_op_output_edge.prototxt new file mode 100644 index 00000000000..ccd953073ce --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiout_op_output_edge.prototxt @@ -0,0 +1,78 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "relu1" + input: "in2" + output: "add1" + op_type: "Add" + } + node { + input: "relu1" + input: "add1" + output: "add2" + op_type: "Add" + } + node { + input: "add2" + output: "split1" + output: "split2" + op_type: "Split" + attribute { + name: "axis" + i: 1 + type: INT + } + } + name: "subgraph_extraction_testing" + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "split2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 1 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer.onnx new file mode 100644 index 00000000000..c2c3c98a366 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer.prototxt new file mode 100644 index 00000000000..9f70d286c5c --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer.prototxt @@ -0,0 +1,90 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "in1" + output: "relu2" + op_type: "Relu" + } + node { + input: "in2" + output: "relu3" + op_type: "Relu" + } + node { + input: "in2" + output: "relu4" + op_type: "Relu" + } + node { + input: "relu1" + input: "relu2" + output: "add1" + op_type: "Add" + } + node { + input: "relu2" + input: "relu3" + output: "add2" + op_type: "Add" + } + name: "subgraph_extraction_testing" + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "relu4" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.onnx new file mode 100644 index 00000000000..a6f7856183d Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.prototxt new file mode 100644 index 00000000000..186d8f4f01c --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.prototxt @@ -0,0 +1,95 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "in2" + output: "relu3" + op_type: "Relu" + } + node { + input: "in2" + output: "relu4" + op_type: "Relu" + } + node { + input: "relu1" + input: "relu2" + output: "add1" + op_type: "Add" + } + node { + input: "relu2" + input: "relu3" + output: "add2" + op_type: "Add" + } + name: "subgraph_extraction_testing" + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "relu2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "relu4" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_input_relu2.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_input_relu2.onnx new file mode 100644 index 00000000000..b20056d9c24 Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_input_relu2.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_input_relu2.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_input_relu2.prototxt new file mode 100644 index 00000000000..c8556c7d366 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__multiple_consumers_of_graph_input_relu2.prototxt @@ -0,0 +1,100 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "in2" + output: "relu3" + op_type: "Relu" + } + node { + input: "in2" + output: "relu4" + op_type: "Relu" + } + node { + input: "relu1" + input: "relu2" + output: "add1" + op_type: "Add" + } + node { + input: "relu2" + input: "relu3" + output: "add2" + op_type: "Add" + } + name: "subgraph_extraction_testing" + initializer { + data_type: 1 + float_data: 1 + name: "in2" + } + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "relu2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "relu4" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__no_input_params.onnx b/ngraph/test/models/onnx/model_editor/reference/subgraph__no_input_params.onnx new file mode 100644 index 00000000000..8ab4d10364e Binary files /dev/null and b/ngraph/test/models/onnx/model_editor/reference/subgraph__no_input_params.onnx differ diff --git a/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt b/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt new file mode 100644 index 00000000000..f41a21428ab --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt @@ -0,0 +1,153 @@ +ir_version: 7 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + node { + input: "conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + node { + input: "conv1/7x7_s2_2" + output: "pool1/3x3_s2_1" + name: "" + op_type: "MaxPool" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 3 + ints: 3 + type: INTS + } + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_b_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + } + } + } + } + + output { + name: "pool1/3x3_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 55 + } + dim { + dim_value: 55 + } + } + } + } + } +} + +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/subgraph__inception_head_with_initializer.prototxt b/ngraph/test/models/onnx/model_editor/subgraph__inception_head_with_initializer.prototxt new file mode 100644 index 00000000000..ca779cb9fff --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/subgraph__inception_head_with_initializer.prototxt @@ -0,0 +1,160 @@ +ir_version: 3 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + node { + input: "conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + node { + input: "conv1/7x7_s2_2" + output: "pool1/3x3_s2_1" + name: "" + op_type: "MaxPool" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 3 + ints: 3 + type: INTS + } + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_b_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + + initializer { + dims: 1 + data_type: 1 + name: "conv1/7x7_s2_b_0" + float_data: 3.141592 + } + + output { + name: "pool1/3x3_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 55 + } + dim { + dim_value: 55 + } + } + } + } + } +} + +opset_import { + version: 8 +} diff --git a/ngraph/test/models/onnx/model_editor/subgraph__initializer_without_matching_input.prototxt b/ngraph/test/models/onnx/model_editor/subgraph__initializer_without_matching_input.prototxt new file mode 100644 index 00000000000..06fd4c315b3 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/subgraph__initializer_without_matching_input.prototxt @@ -0,0 +1,146 @@ +ir_version: 3 +producer_name: "test_data_generator" +doc_string: "This model contains the first few nodes of the ONNX Inception V1 model" +graph { + name: "Inception V1 fragment" + node { + input: "data_0" + input: "conv1/7x7_s2_w_0" + input: "conv1/7x7_s2_b_0" + output: "conv1/7x7_s2_1" + name: "" + op_type: "Conv" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 3 + ints: 3 + ints: 3 + ints: 3 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 7 + ints: 7 + type: INTS + } + } + node { + input: "conv1/7x7_s2_1" + output: "conv1/7x7_s2_2" + name: "" + op_type: "Relu" + } + node { + input: "conv1/7x7_s2_2" + output: "pool1/3x3_s2_1" + name: "" + op_type: "MaxPool" + attribute { + name: "strides" + ints: 2 + ints: 2 + type: INTS + } + attribute { + name: "pads" + ints: 0 + ints: 0 + ints: 0 + ints: 0 + type: INTS + } + attribute { + name: "kernel_shape" + ints: 3 + ints: 3 + type: INTS + } + } + + input { + name: "data_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 3 + } + dim { + dim_value: 224 + } + dim { + dim_value: 224 + } + } + } + } + } + + input { + name: "conv1/7x7_s2_w_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 64 + } + dim { + dim_value: 3 + } + dim { + dim_value: 7 + } + dim { + dim_value: 7 + } + } + } + } + } + + initializer { + dims: 1 + data_type: 1 + name: "conv1/7x7_s2_b_0" + float_data: 3.141592 + } + + output { + name: "pool1/3x3_s2_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 64 + } + dim { + dim_value: 55 + } + dim { + dim_value: 55 + } + } + } + } + } +} + +opset_import { + version: 8 +} diff --git a/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt new file mode 100644 index 00000000000..9e79acc9cf9 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt @@ -0,0 +1,187 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "relu1" + input: "in2" + output: "add1" + op_type: "Add" + } + node { + input: "in3" + input: "in4" + output: "conv1" + op_type: "Conv" + } + node { + input: "relu1" + input: "add1" + output: "add2" + op_type: "Add" + } + node { + input: "add1" + input: "conv1" + output: "mul2" + op_type: "Mul" + } + node { + input: "add2" + output: "split1" + output: "split2" + op_type: "Split" + attribute { + name: "axis" + i: 1 + type: INT + } + } + node { + input: "relu1" + input: "split1" + output: "mul1" + op_type: "Mul" + } + name: "subgraph_extraction_testing" + initializer { + dims: 1 + dims: 1 + dims: 1 + dims: 1 + data_type: 1 + float_data: 1 + name: "in4" + } + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in3" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "in4" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "mul1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + output { + name: "split2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "mul2" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + dim { + dim_value: 1 + } + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests_2.prototxt b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests_2.prototxt new file mode 100644 index 00000000000..76d8587d9e8 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests_2.prototxt @@ -0,0 +1,95 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + op_type: "Relu" + } + node { + input: "in1" + output: "relu2" + op_type: "Relu" + } + node { + input: "in2" + output: "relu3" + op_type: "Relu" + } + node { + input: "in2" + output: "relu4" + op_type: "Relu" + } + node { + input: "relu1" + input: "relu2" + output: "add1" + op_type: "Add" + } + node { + input: "relu2" + input: "relu3" + output: "add2" + op_type: "Add" + } + name: "subgraph_extraction_testing" + initializer { + data_type: 1 + float_data: 1 + name: "in2" + } + input { + name: "in1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + input { + name: "in2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add1" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "add2" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } + output { + name: "relu4" + type { + tensor_type { + elem_type: 1 + shape { + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/onnx/onnx_editor.cpp b/ngraph/test/onnx/onnx_editor.cpp index 91498a49623..5a821681b0d 100644 --- a/ngraph/test/onnx/onnx_editor.cpp +++ b/ngraph/test/onnx/onnx_editor.cpp @@ -15,20 +15,24 @@ //***************************************************************************** #include + #include "gtest/gtest.h" #include "default_opset.hpp" #include "ngraph/file_util.hpp" #include "ngraph/op/util/op_types.hpp" +#include "ngraph/opsets/opset1.hpp" #include "onnx_import/editor/editor.hpp" #include "onnx_import/onnx.hpp" #include "util/engine/interpreter_engine.hpp" #include "util/test_case.hpp" #include "util/test_control.hpp" +// #include "utils/onnx_test_util.hpp" NGRAPH_SUPPRESS_DEPRECATED_START using namespace ngraph; +using namespace ngraph::onnx_import; static std::string s_manifest = "${MANIFEST}"; @@ -54,6 +58,23 @@ namespace return *input_pos; } + + std::string read_binary_file(const std::string& path) + { + std::ifstream inputs_fs{path, std::ios::in | std::ios::binary}; + if (!inputs_fs) + { + throw std::runtime_error("Failed to open the file: " + path); + } + + std::vector file_content; + inputs_fs.seekg(0, std::ios::end); + const auto size = inputs_fs.tellg(); + inputs_fs.seekg(0, std::ios::beg); + file_content.resize(size); + inputs_fs.read(reinterpret_cast(file_content.data()), size); + return std::string(file_content.begin(), file_content.end()); + } } // namespace NGRAPH_TEST(onnx_editor, types__single_input_type_substitution) @@ -287,6 +308,411 @@ NGRAPH_TEST(onnx_editor, shapes__static_to_dynamic_rank_substitution) } } +NGRAPH_TEST(onnx_editor, subgraph__linear_model_head_cut) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + editor.cut_graph_fragment({{InputEdge(1, "conv1/7x7_s2_1")}}, {}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_head_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__linear_model_head_cut_ins_and_outs) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + editor.cut_graph_fragment({{InputEdge(1, "conv1/7x7_s2_1")}}, + {{OutputEdge(2, "pool1/3x3_s2_1")}}); + + // expected to behave the same way as subgraph__linear_model_head_cut + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_head_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__linear_model_deeper_head_cut) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + editor.cut_graph_fragment({{InputEdge(2, "conv1/7x7_s2_2")}}, {}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_deeper_head_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__linear_model_tail_cut) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + editor.cut_graph_fragment({}, {{OutputEdge{1, "conv1/7x7_s2_2"}}}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_tail_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__linear_model_tail_cut_ins_and_outs) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{0, "data_0"}}}, {{OutputEdge{1, "conv1/7x7_s2_2"}}}); + + // expected to behave the same way as subgraph__linear_model_tail_cut + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_tail_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__linear_model_with_initializer_tail_cut) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head_with_initializer.prototxt")}; + + editor.cut_graph_fragment({}, {{OutputEdge{1, "conv1/7x7_s2_2"}}}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, + "onnx/model_editor/reference/subgraph__linear_model_with_initializer_tail_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__initializer_without_matching_input_tail_cut) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__initializer_without_matching_input.prototxt")}; + + editor.cut_graph_fragment({}, {{OutputEdge{1, "conv1/7x7_s2_2"}}}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__initializer_without_matching_input_tail_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__linear_model_deeper_tail_cut) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + editor.cut_graph_fragment({}, {{OutputEdge{0, "conv1/7x7_s2_1"}}}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_deeper_tail_cut.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__no_input_params) +{ + const auto model_path = + file_util::path_join(SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt"); + + onnx_import::ONNXModelEditor editor{model_path}; + + editor.cut_graph_fragment({}, {}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__no_input_params.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), model_path); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__initializer_to_input_replacement) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head_with_initializer.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{0, "conv1/7x7_s2_b_0"}}}, + {{OutputEdge{0, "conv1/7x7_s2_1"}}}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, + "onnx/model_editor/reference/subgraph__initializer_to_input_replacement.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__initializer_to_input_replacement_2) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__initializer_without_matching_input.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{0, "conv1/7x7_s2_b_0"}}}, + {{OutputEdge{0, "conv1/7x7_s2_1"}}}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, + "onnx/model_editor/reference/subgraph__initializer_to_input_replacement.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__multiout_op_output_edge) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({}, {{OutputEdge{5, "split2"}}}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__multiout_op_output_edge.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__existing_inputs_and_outputs_based_extraction) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{1, "in2"}, InputEdge{2, "in3"}}}, + {{OutputEdge{4, "mul2"}}}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__existing_inputs_and_outputs_based_extraction.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumers) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{1, "relu1"}, InputEdge{6, "relu1"}}}, + {{OutputEdge{6, "mul1"}, OutputEdge{4, "mul2"}}}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__input_edge_from_tensor_with_multiple_consumers.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumers_2) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{3, "relu1"}, InputEdge{3, "add1"}}}, + {{OutputEdge{3, "add2"}, OutputEdge{4, "mul2"}}}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__input_edge_from_tensor_with_multiple_consumers_2.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumers_3) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{3, "relu1"}, InputEdge{6, "relu1"}}}, + {{OutputEdge{6, "mul1"}, OutputEdge{5, "split2"}}}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__input_edge_from_tensor_with_multiple_consumers_3.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumers_4) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{3, "relu1"}}}, + {{OutputEdge{6, "mul1"}, OutputEdge{5, "split2"}}}); + + // expected to behave the same way as the test above + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__input_edge_from_tensor_with_multiple_consumers_3.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_input_relu2) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{4, "relu2"}}}, {}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__multiple_consumers_of_graph_input_relu2.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_initializer) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{2, "in2"}}}, {}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__multiple_consumers_of_graph_initializer.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_initializer_2) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{2, "in2"}, InputEdge{3, "in2"}}}, {}); + + // same as above + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__multiple_consumers_of_graph_initializer.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_initializer_relu2_and_init) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + editor.cut_graph_fragment({{InputEdge{5, "relu2"}, InputEdge{3, "in2"}}}, {}); + + const auto ref_model = file_util::path_join( + SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__multiple_consumers_of_graph_initializer_relu2_and_init.onnx"); + + EXPECT_EQ(editor.model_string(), read_binary_file(ref_model)); + + // const auto result = compare_onnx_models(editor.model_string(), ref_model); + // EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, subgraph__invalid_edge_idx) +{ + const auto model_path = + file_util::path_join(SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt"); + + onnx_import::ONNXModelEditor editor{model_path}; + EXPECT_THROW(editor.cut_graph_fragment({{InputEdge{15, "x"}}}, {}), ngraph::ngraph_error); +} + +NGRAPH_TEST(onnx_editor, subgraph__invalid_edge_name) +{ + const auto model_path = + file_util::path_join(SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt"); + + onnx_import::ONNXModelEditor editor{model_path}; + + EXPECT_THROW(editor.cut_graph_fragment({{InputEdge{0, "x"}}}, {}), ngraph::ngraph_error); +} + +NGRAPH_TEST(onnx_editor, subgraph__inputs_getter) +{ + onnx_import::ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + EXPECT_EQ(editor.model_inputs(), + (std::vector{"data_0", "conv1/7x7_s2_w_0", "conv1/7x7_s2_b_0"})); + + editor.cut_graph_fragment({{InputEdge(1, "conv1/7x7_s2_1")}}, {}); + + EXPECT_EQ(editor.model_inputs(), (std::vector{"conv1/7x7_s2_2:conv1/7x7_s2_1"})); +} + using TestEngine = test::INTERPRETER_Engine; NGRAPH_TEST(onnx_editor, values__append_one_initializer) diff --git a/ngraph/test/util/onnx_test_util.cpp b/ngraph/test/util/onnx_test_util.cpp new file mode 100644 index 00000000000..32e87765603 --- /dev/null +++ b/ngraph/test/util/onnx_test_util.cpp @@ -0,0 +1,325 @@ +//***************************************************************************** +// Copyright 2017-2021 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +#include +#include +#include +#include +#include +#include + +#include "ngraph/except.hpp" +#include "onnx_test_util.hpp" + +using namespace ngraph; +using namespace ngraph::test; + +namespace +{ + void parse_from_istream(std::istream& model_stream, ONNX_NAMESPACE::ModelProto& model_proto) + { + if (!model_stream.good()) + { + model_stream.clear(); + model_stream.seekg(0); + if (!model_stream.good()) + { + throw ngraph_error("Provided input stream has incorrect state."); + } + } + + if (!model_proto.ParseFromIstream(&model_stream)) + { +#ifdef NGRAPH_USE_PROTOBUF_LITE + throw ngraph_error( + "Error during import of ONNX model provided as input stream " + " with binary protobuf message."); +#else + // Rewind to the beginning and clear stream state. + model_stream.clear(); + model_stream.seekg(0); + google::protobuf::io::IstreamInputStream iistream(&model_stream); + // Try parsing input as a prototxt message + if (!google::protobuf::TextFormat::Parse(&iistream, &model_proto)) + { + throw ngraph_error( + "Error during import of ONNX model provided as input stream with prototxt " + "protobuf message."); + } +#endif + } + } + + void parse_from_file(const std::string& file_path, ONNX_NAMESPACE::ModelProto& model_proto) + { + std::ifstream file_stream{file_path, std::ios::in | std::ios::binary}; + + if (!file_stream.is_open()) + { + throw ngraph_error("Could not open the file: " + file_path); + }; + + parse_from_istream(file_stream, model_proto); + } + + ComparisonResult compare_nodes(const ONNX_NAMESPACE::GraphProto& graph, + const ONNX_NAMESPACE::GraphProto& ref_graph) + { + if (graph.node_size() != ref_graph.node_size()) + { + return ComparisonResult::fail("The number of nodes in compared models doesn't match"); + } + else + { + for (int i = 0; i < graph.node_size(); ++i) + { + const auto& lhs = graph.node(i); + const auto& rhs = ref_graph.node(i); + + if (lhs.op_type() != rhs.op_type()) + { + return ComparisonResult::fail("Operation types are different at index " + + std::to_string(i) + ": " + lhs.op_type() + + " vs " + rhs.op_type()); + } + + for (int j = 0; j < lhs.input_size(); ++j) + { + if (lhs.input(j) != rhs.input(j)) + { + return ComparisonResult::fail( + "Input names don't match for nodes at index " + std::to_string(i) + + ": " + lhs.input(j) + " vs " + rhs.input(j)); + } + } + + for (int j = 0; j < lhs.output_size(); ++j) + { + if (lhs.output(j) != rhs.output(j)) + { + return ComparisonResult::fail( + "Output names don't match for nodes at index " + std::to_string(i) + + ": " + lhs.output(j) + " vs " + rhs.output(j)); + } + } + } + } + + return ComparisonResult::pass(); + } + + ComparisonResult compare_value_info(const ONNX_NAMESPACE::ValueInfoProto& lhs, + const ONNX_NAMESPACE::ValueInfoProto& rhs, + const std::string& item_type) + { + if (lhs.name() != rhs.name()) + { + return ComparisonResult::fail(item_type + " names in the graph don't match: " + + lhs.name() + " vs " + rhs.name()); + } + + const auto& lhs_tensor = lhs.type().tensor_type(); + const auto& rhs_tensor = rhs.type().tensor_type(); + if (lhs_tensor.elem_type() != rhs_tensor.elem_type()) + { + return ComparisonResult::fail("Element types don't match for " + item_type + " " + + lhs.name() + ": " + + std::to_string(lhs_tensor.elem_type()) + " vs " + + std::to_string(rhs_tensor.elem_type())); + } + + const auto& lhs_shape = lhs_tensor.shape(); + const auto& rhs_shape = rhs_tensor.shape(); + if (lhs_shape.dim_size() != rhs_shape.dim_size()) + { + return ComparisonResult::fail("Tensor ranks don't match for " + item_type + " " + + lhs.name() + ": " + std::to_string(lhs_shape.dim_size()) + + " vs " + std::to_string(rhs_shape.dim_size())); + } + else + { + for (int j = 0; j < lhs_shape.dim_size(); ++j) + { + const auto& lhs_dim = lhs_shape.dim(j); + const auto& rhs_dim = rhs_shape.dim(j); + if ((lhs_dim.has_dim_value() && rhs_dim.has_dim_param()) || + (rhs_dim.has_dim_value() && lhs_dim.has_dim_param())) + { + return ComparisonResult::fail("Dynamic vs static dimension mismatch for " + + item_type + " " + lhs.name() + " at index: " + + std::to_string(j)); + } + else if (lhs_dim.has_dim_value() && lhs_dim.dim_value() != rhs_dim.dim_value()) + { + return ComparisonResult::fail("Shape dimensions don't match for " + item_type + + " " + lhs.name() + " at index: " + + std::to_string(j) + ". " + + std::to_string(lhs_dim.dim_value()) + " vs " + + std::to_string(rhs_dim.dim_value())); + } + } + } + + return ComparisonResult::pass(); + } + + ComparisonResult compare_inputs(const ONNX_NAMESPACE::GraphProto& graph, + const ONNX_NAMESPACE::GraphProto& ref_graph) + { + if (graph.input_size() != ref_graph.input_size()) + { + return ComparisonResult::fail( + "The number of inputs in compared models doesn't match: " + + std::to_string(graph.input_size()) + " vs " + + std::to_string(ref_graph.input_size())); + } + else + { + for (int i = 0; i < graph.input_size(); ++i) + { + const auto& lhs = graph.input(i); + const auto& rhs = ref_graph.input(i); + + const auto res = compare_value_info(lhs, rhs, "input"); + if (!res.is_ok) + { + return res; + } + } + + return ComparisonResult::pass(); + } + } + + ComparisonResult compare_outputs(const ONNX_NAMESPACE::GraphProto& graph, + const ONNX_NAMESPACE::GraphProto& ref_graph) + { + if (graph.output_size() != ref_graph.output_size()) + { + return ComparisonResult::fail("The number of outputs in compared models doesn't match" + + std::to_string(graph.output_size()) + " vs " + + std::to_string(ref_graph.output_size())); + } + else + { + for (int i = 0; i < graph.output_size(); ++i) + { + const auto& lhs = graph.output(i); + const auto& rhs = ref_graph.output(i); + + const auto res = compare_value_info(lhs, rhs, "output"); + if (!res.is_ok) + { + return res; + } + } + + return ComparisonResult::pass(); + } + } + + ComparisonResult compare_initializers(const ONNX_NAMESPACE::GraphProto& graph, + const ONNX_NAMESPACE::GraphProto& ref_graph) + { + if (graph.initializer_size() != ref_graph.initializer_size()) + { + return ComparisonResult::fail( + "The number of initializers in compared models doesn't match" + + std::to_string(graph.initializer_size()) + " vs " + + std::to_string(ref_graph.initializer_size())); + } + else + { + for (int i = 0; i < graph.initializer_size(); ++i) + { + const auto& lhs = graph.initializer(i); + const auto& rhs = ref_graph.initializer(i); + + if (lhs.name() != rhs.name()) + { + return ComparisonResult::fail("Initializer names in the graph don't match: " + + lhs.name() + " vs " + rhs.name()); + } + else if (lhs.data_type() != rhs.data_type()) + { + return ComparisonResult::fail( + "Initializer data types in the graph don't match: " + + std::to_string(lhs.data_type()) + " vs " + std::to_string(rhs.data_type())); + } + else if (lhs.dims_size() != rhs.dims_size()) + { + return ComparisonResult::fail("Initializer ranks in the graph don't match: " + + std::to_string(lhs.dims_size()) + " vs " + + std::to_string(rhs.dims_size())); + } + else + { + for (int j = 0; j < lhs.dims_size(); ++j) + { + if (lhs.dims(j) != rhs.dims(j)) + { + return ComparisonResult::fail( + "Shape dimensions don't match for initializer " + lhs.name() + + " at index: " + std::to_string(j) + ". " + + std::to_string(lhs.dims(j)) + " vs " + std::to_string(rhs.dims(j))); + } + } + } + } + + return ComparisonResult::pass(); + } + } + + ComparisonResult compare_onnx_graphs(const ONNX_NAMESPACE::GraphProto& graph, + const ONNX_NAMESPACE::GraphProto& ref_graph) + { + ComparisonResult comparison = compare_inputs(graph, ref_graph); + if (!comparison.is_ok) + { + return comparison; + } + + comparison = compare_outputs(graph, ref_graph); + if (!comparison.is_ok) + { + return comparison; + } + + comparison = compare_initializers(graph, ref_graph); + if (!comparison.is_ok) + { + return comparison; + } + + return compare_nodes(graph, ref_graph); + } +} // namespace +namespace ngraph +{ + namespace test + { + ComparisonResult compare_onnx_models(const std::string& model, + const std::string& reference_model_path) + { + std::stringstream model_stream{model}; + ONNX_NAMESPACE::ModelProto model_proto, ref_model; + parse_from_istream(model_stream, model_proto); + parse_from_file(reference_model_path, ref_model); + return compare_onnx_graphs(model_proto.graph(), ref_model.graph()); + } + } // namespace test +} // namespace ngraph diff --git a/ngraph/test/util/onnx_test_util.hpp b/ngraph/test/util/onnx_test_util.hpp new file mode 100644 index 00000000000..fa8447c8c1c --- /dev/null +++ b/ngraph/test/util/onnx_test_util.hpp @@ -0,0 +1,52 @@ +//***************************************************************************** +// Copyright 2017-2021 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +#pragma once + +#include + +namespace ngraph +{ + namespace test + { + struct ComparisonResult + { + ComparisonResult() = default; + ComparisonResult(std::string error) + : is_ok{false} + , error_message{std::move(error)} + { + } + ComparisonResult(ComparisonResult&&) = default; + ComparisonResult(const ComparisonResult&) = default; + ComparisonResult& operator=(ComparisonResult&&) = default; + ComparisonResult& operator=(const ComparisonResult&) = default; + + bool is_ok = true; + std::string error_message; + + static ComparisonResult pass() { return {}; } + static ComparisonResult fail(std::string error) + { + return ComparisonResult{std::move(error)}; + } + }; + + ComparisonResult compare_onnx_models(const std::string& model, + const std::string& reference_model_path); + + } // namespace onnx_import +} // namespace ngraph