diff --git a/CMakeLists.txt b/CMakeLists.txt index dfce1809cf1b52..5d985709d76a96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ function(build_ngraph) set(SDL_cmake_included ON) add_subdirectory(ngraph) set(NGRAPH_LIBRARIES ngraph PARENT_SCOPE) + set(FRONTEND_LIBRARIES frontend_manager PARENT_SCOPE) set(NGRAPH_REF_LIBRARIES ngraph_reference PARENT_SCOPE) endfunction() diff --git a/inference-engine/samples/benchmark_app/CMakeLists.txt b/inference-engine/samples/benchmark_app/CMakeLists.txt index 97172ee7a496fb..cef62a7c323b3e 100644 --- a/inference-engine/samples/benchmark_app/CMakeLists.txt +++ b/inference-engine/samples/benchmark_app/CMakeLists.txt @@ -8,6 +8,6 @@ file (GLOB HDR ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) ie_add_sample(NAME benchmark_app SOURCES ${SRC} HEADERS ${HDR} - DEPENDENCIES format_reader ie_samples_utils - OPENCV_DEPENDENCIES core) + DEPENDENCIES format_reader ie_samples_utils frontend_manager + OPENCV_DEPENDENCIES imgcodecs) diff --git a/inference-engine/samples/benchmark_app/main.cpp b/inference-engine/samples/benchmark_app/main.cpp index 5a34e558cd33b1..3afa3958330123 100644 --- a/inference-engine/samples/benchmark_app/main.cpp +++ b/inference-engine/samples/benchmark_app/main.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include "benchmark_app.hpp" #include "infer_request_wrap.hpp" #include "inputs_filling.hpp" @@ -338,7 +340,79 @@ int main(int argc, char* argv[]) { slog::info << "Loading network files" << slog::endl; auto startTime = Time::now(); - CNNNetwork cnnNetwork = ie.ReadNetwork(FLAGS_m); + + ////////////////////////////////////////////////////////////// + + //CNNNetwork cnnNetwork = ie.ReadNetwork(FLAGS_m); + + ngraph::frontend::FrontEndManager manager; + auto FE = manager.loadByFramework("onnx"); + auto inputModel = FE->loadFromFile(FLAGS_m); + + int test_scenario = 2; + + // The next test cases are applicable for resnet50-v1-7.onnx model from the official repository + + switch (test_scenario) { + case 0: { + inputModel->setPartialShape(inputModel->getInputs()[0], ngraph::PartialShape({10, 3, 224, 224})); + break; + } + case 1: { + auto new_input = inputModel->getPlaceByTensorName("resnetv17_conv0_fwd"); + inputModel->overrideAllInputs({inputModel->getPlaceByTensorName("resnetv17_conv0_fwd")}); + break; + } + case 2: { + auto new_input = inputModel->getPlaceByTensorName("resnetv17_conv0_fwd"); + inputModel->overrideAllInputs({new_input}); + inputModel->setPartialShape(new_input, ngraph::PartialShape({2, 64, 112, 112})); + break; + } + case 3: { + auto new_input = inputModel->getPlaceByTensorName("resnetv17_conv0_fwd"); + inputModel->setPartialShape(new_input, ngraph::PartialShape({2, 64, 112, 112})); + inputModel->overrideAllInputs({new_input}); + break; + } + case 4: { + auto new_input = inputModel->getPlaceByTensorName("resnetv17_conv0_fwd"); + inputModel->setPartialShape(new_input, ngraph::PartialShape({2, 64, 112, 112})); + inputModel->overrideAllInputs({new_input}); + break; + } + case 5: { + // Multiple consumers for a tensor, should be converted to a single Parameter + auto new_input = inputModel->getPlaceByTensorName("resnetv17_pool0_fwd"); + inputModel->overrideAllInputs({new_input}); + break; + } + case 6: { + // Split usages of a tensor to two Parameters; using ports instead of tensors + auto new_input = inputModel->getPlaceByTensorName("resnetv17_pool0_fwd"); + auto ports = new_input->getConsumingPorts(); + inputModel->overrideAllInputs({ports[0], ports[1]}); // it is known there are two ports + break; + } + case 7: { + auto new_output_1 = inputModel->getPlaceByTensorName("resnetv17_stage1_conv2_fwd"); + auto new_output_2 = inputModel->getPlaceByTensorName("resnetv17_stage1_conv3_fwd"); + inputModel->overrideAllOutputs({new_output_1, new_output_2}); + break; + } + } + +#if 1 // Complete convert + auto ngFunc = FE->convert(inputModel); +#else // Decode first, than finalize conversion -- should give identical to simple `convert` result + auto ngFunc = FE->decode(inputModel); + FE->convert(ngFunc); +#endif + CNNNetwork cnnNetwork(ngFunc); + cnnNetwork.serialize("benchmark_app_loaded_network.xml"); + + ///////////////////////////////////////////////////////////// + auto duration_ms = double_to_string(get_total_ms_time(startTime)); slog::info << "Read network took " << duration_ms << " ms" << slog::endl; if (statistics) diff --git a/inference-engine/samples/hello_classification/CMakeLists.txt b/inference-engine/samples/hello_classification/CMakeLists.txt index 9b0509bab82c23..bf6b0389a1eb46 100644 --- a/inference-engine/samples/hello_classification/CMakeLists.txt +++ b/inference-engine/samples/hello_classification/CMakeLists.txt @@ -4,6 +4,6 @@ ie_add_sample(NAME hello_classification SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" - DEPENDENCIES ie_samples_utils + DEPENDENCIES ie_samples_utils frontend_manager OPENCV_DEPENDENCIES core imgcodecs) diff --git a/inference-engine/samples/hello_classification/main.cpp b/inference-engine/samples/hello_classification/main.cpp index 858a87ead5b09b..f0750cff846682 100644 --- a/inference-engine/samples/hello_classification/main.cpp +++ b/inference-engine/samples/hello_classification/main.cpp @@ -130,7 +130,6 @@ int main(int argc, char* argv[]) { DataPtr output_info = network.getOutputsInfo().begin()->second; std::string output_name = network.getOutputsInfo().begin()->first; - output_info->setPrecision(Precision::FP32); // ----------------------------------------------------------------------------------------------------- // --------------------------- Step 4. Loading a model to the device diff --git a/inference-engine/src/CMakeLists.txt b/inference-engine/src/CMakeLists.txt index db8d27ada7a23d..bfd718f556042d 100644 --- a/inference-engine/src/CMakeLists.txt +++ b/inference-engine/src/CMakeLists.txt @@ -51,5 +51,5 @@ add_custom_target(ie_libraries ALL inference_engine_lp_transformations inference_engine_snippets) if(NGRAPH_ONNX_IMPORT_ENABLE) - add_dependencies(ie_libraries inference_engine_onnx_reader) + add_dependencies(ie_libraries inference_engine_onnx_reader frontend_manager) endif() diff --git a/inference-engine/src/inference_engine/CMakeLists.txt b/inference-engine/src/inference_engine/CMakeLists.txt index 66a43ff315d5dc..97b55434d3b65d 100644 --- a/inference-engine/src/inference_engine/CMakeLists.txt +++ b/inference-engine/src/inference_engine/CMakeLists.txt @@ -125,6 +125,7 @@ target_compile_definitions(${TARGET_NAME}_obj PRIVATE IMPLEMENT_INFERENCE_ENGINE $) target_include_directories(${TARGET_NAME}_obj SYSTEM PRIVATE $ + $ $ $) @@ -162,7 +163,7 @@ if (TBBBIND_2_4_FOUND) endif() target_link_libraries(${TARGET_NAME} PRIVATE pugixml openvino::itt ${CMAKE_DL_LIBS} Threads::Threads - ${NGRAPH_LIBRARIES} inference_engine_transformations) + ${NGRAPH_LIBRARIES} ${FRONTEND_LIBRARIES} inference_engine_transformations) target_include_directories(${TARGET_NAME} INTERFACE ${PUBLIC_HEADERS_DIR} PRIVATE $ diff --git a/inference-engine/src/inference_engine/ie_core.cpp b/inference-engine/src/inference_engine/ie_core.cpp index 1bc038ff07136a..8a4aaf89c8cbc9 100644 --- a/inference-engine/src/inference_engine/ie_core.cpp +++ b/inference-engine/src/inference_engine/ie_core.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "compilation_context.hpp" #include "ie_plugin_cpp.hpp" @@ -207,6 +208,7 @@ class Core::Impl : public ICore { std::unordered_set opsetNames; std::vector extensions; + //ngraph::frontend::FrontEndManager frontEndManager; std::map pluginRegistry; mutable std::mutex pluginsMutex; // to lock parallel access to pluginRegistry and plugins diff --git a/inference-engine/src/inference_engine/xml_parse_utils.cpp b/inference-engine/src/inference_engine/xml_parse_utils.cpp index a71adf1b54e5a2..7a829d9a77d90a 100644 --- a/inference-engine/src/inference_engine/xml_parse_utils.cpp +++ b/inference-engine/src/inference_engine/xml_parse_utils.cpp @@ -12,6 +12,23 @@ #include "ie_precision.hpp" +template +bool is_bool_value(const std::string & str, T & val) { + std::string tmp; + for (const auto ch : str) { + tmp += std::tolower(ch); + } + if (tmp == "true") { + val = 1; + return true; + } + if (tmp == "false") { + val = 0; + return true; + } + return false; +} + int XMLParseUtils::GetIntAttr(const pugi::xml_node& node, const char* str) { auto attr = node.attribute(str); if (attr.empty()) @@ -20,10 +37,11 @@ int XMLParseUtils::GetIntAttr(const pugi::xml_node& node, const char* str) { std::string str_value = std::string(attr.value()); std::size_t idx = 0; int int_value = std::stoi(str_value, &idx, 10); - if (idx != str_value.length()) - IE_THROW() << "node <" << node.name() << "> has attribute \"" << str << "\" = \"" << str_value - << "\" which is not an integer" - << " at offset " << node.offset_debug(); +//mnosov: TODO - commented out according to rebase +// if (idx != str_value.length()) +// IE_THROW() << "node <" << node.name() << "> has attribute \"" << str << "\" = \"" << str_value +// << "\" which is not an integer" +// << " at offset " << node.offset_debug(); return int_value; } @@ -65,10 +83,11 @@ unsigned int XMLParseUtils::GetUIntAttr(const pugi::xml_node& node, const char* std::string str_value = std::string(attr.value()); std::size_t idx = 0; long long int_value = std::stoll(str_value, &idx, 10); - if (idx != str_value.length() || int_value < 0 || int_value > (std::numeric_limits::max)()) - IE_THROW() << "node <" << node.name() << "> has attribute \"" << str << "\" = \"" << str_value - << "\" which is not an unsigned integer" - << " at offset " << node.offset_debug(); +// TODO: mnosov: commented out according to rebase +// if (idx != str_value.length() || int_value < 0 || int_value > (std::numeric_limits::max)()) +// IE_THROW() << "node <" << node.name() << "> has attribute \"" << str << "\" = \"" << str_value +// << "\" which is not an unsigned integer" +// << " at offset " << node.offset_debug(); return static_cast(int_value); } diff --git a/inference-engine/src/plugin_api/ie_ngraph_utils.hpp b/inference-engine/src/plugin_api/ie_ngraph_utils.hpp index 40904bb07215ca..48a9a026daceab 100644 --- a/inference-engine/src/plugin_api/ie_ngraph_utils.hpp +++ b/inference-engine/src/plugin_api/ie_ngraph_utils.hpp @@ -134,6 +134,8 @@ inline Precision convertPrecision(const ::ngraph::element::Type& precision) { return Precision(Precision::BIN); case ::ngraph::element::Type_t::boolean: return Precision(Precision::BOOL); + case ::ngraph::element::Type_t::dynamic: + return Precision(Precision::UNSPECIFIED); default: IE_THROW() << "Incorrect precision " << precision.get_type_name() << "!"; return{}; } diff --git a/inference-engine/src/transformations/src/transformations/serialize.cpp b/inference-engine/src/transformations/src/transformations/serialize.cpp index 475d04c6d55267..f4482d20f5545c 100644 --- a/inference-engine/src/transformations/src/transformations/serialize.cpp +++ b/inference-engine/src/transformations/src/transformations/serialize.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "ngraph/ops.hpp" #include "ngraph/opsets/opset.hpp" #include "ngraph/opsets/opset1.hpp" @@ -502,6 +503,7 @@ std::string get_opset_name( std::string get_precision_name(const ngraph::element::Type & elem_type) { switch (elem_type) { case ::ngraph::element::Type_t::undefined: + case ::ngraph::element::Type_t::dynamic: return "UNSPECIFIED"; case ::ngraph::element::Type_t::f16: return "FP16"; @@ -542,17 +544,6 @@ std::string get_precision_name(const ngraph::element::Type & elem_type) { } } -std::string escape_delim(const std::string& name, const char delim = ',') { - std::string result_name = name; - const std::string escaped_delim = std::string("\\") + delim; - size_t index = result_name.find(delim, 0); - while (index != std::string::npos) { - result_name.replace(index, 1, escaped_delim); - index = result_name.find(delim, index + 2); - } - return result_name; -} - std::string generate_unique_name( const std::unordered_set& unique_names, std::string base_name, int suffix) { @@ -565,6 +556,17 @@ std::string generate_unique_name( } } +std::string escape_delim(const std::string& name, const char delim = ',') { + std::string result_name = name; + const std::string escaped_delim = std::string("\\") + delim; + size_t index = result_name.find(delim, 0); + while (index != std::string::npos) { + result_name.replace(index, 1, escaped_delim); + index = result_name.find(delim, index + 2); + } + return result_name; +} + // TODO: remove when CNNNetwork will be supporting not-unique names std::string get_node_unique_name(std::unordered_set& unique_names, const ngraph::Node* n) { @@ -721,9 +723,6 @@ void ngfunction_2_irv10(pugi::xml_node& netXml, if (node->get_input_size() > 0) { pugi::xml_node input = layer.append_child("input"); for (const auto & i : node->inputs()) { - NGRAPH_CHECK(i.get_partial_shape().is_static(), - "Unsupported dynamic input shape in ", node); - // WA for LSTMCellv0, peephole input shall not be serialized if (i.get_index() == 6 && dynamic_cast(node)) { port_id++; @@ -734,10 +733,10 @@ void ngfunction_2_irv10(pugi::xml_node& netXml, port.append_attribute("id").set_value(port_id++); port.append_attribute("precision") .set_value(get_precision_name(i.get_element_type()).c_str()); - for (auto d : i.get_shape()) { + for (auto d : std::vector(i.get_partial_shape())) { pugi::xml_node dim = port.append_child("dim"); dim.append_child(pugi::xml_node_type::node_pcdata) - .set_value(std::to_string(d).c_str()); + .set_value(std::to_string(d.get_max_length()).c_str()); } } @@ -765,10 +764,10 @@ void ngfunction_2_irv10(pugi::xml_node& netXml, if (!names.empty()) { port.append_attribute("names").set_value(names.c_str()); } - for (auto d : o.get_shape()) { + for (auto d : std::vector(o.get_partial_shape())) { pugi::xml_node dim = port.append_child("dim"); dim.append_child(pugi::xml_node_type::node_pcdata) - .set_value(std::to_string(d).c_str()); + .set_value(std::to_string(d.get_max_length()).c_str()); } } if (node_type_name == "TensorIterator" || node_type_name == "Loop") { diff --git a/model-optimizer/extensions/front/user_data_repack.py b/model-optimizer/extensions/front/user_data_repack.py index 80a2aff557c079..556010a19838d7 100644 --- a/model-optimizer/extensions/front/user_data_repack.py +++ b/model-optimizer/extensions/front/user_data_repack.py @@ -30,3 +30,10 @@ def find_and_replace_pattern(self, graph: Graph): inputs = list(packed_user_shapes.keys()) \ if packed_user_shapes is not None and isinstance(packed_user_shapes, dict) else None graph.graph['inputs'] = inputs # save user defined inputs for other extensions + + print('---------- Inputs/outpus -----------') + print(packed_user_shapes) + print(packed_outputs) + print(freeze_placeholder) + print(inputs) + print('------------------------------------') diff --git a/model-optimizer/mo/front/extractor.py b/model-optimizer/mo/front/extractor.py index acb5003d266f77..4f3aed8f096060 100644 --- a/model-optimizer/mo/front/extractor.py +++ b/model-optimizer/mo/front/extractor.py @@ -451,6 +451,47 @@ def extract_node_attrs(graph: Graph, extractor: callable): return graph +def decodeNameWithPort (graph: Graph, node_name: str): + """ + Decode name with optional port specification w/o traversing all the nodes in the graph + :param graph: + :param node_name: + :return: decoded place in the graph + """ + inputModel = graph.graph['input_model'] + + # First try to interpret node_name as pure FW name and find exact match: + node = inputModel.getPlaceByName(node_name) + + if node: + return node + + # If there are not matches , try to decode MO syntax + + # Postfix port + regexpPost = r'(.*)(:(\d+))' + matchPost = re.search(regexpPost, node_name) + # Again search in all names, including tensors and operations + # TODO: Search for operation only? + nodePost = inputModel.getPlaceByName(matchPost.group(1)) if matchPost else None + + # Prefix port + regexpPre = r'((\d+):)(.*)' + matchPre = re.search(regexpPre, node_name) + # TODO: Search for operation only? + nodePre = inputModel.getPlaceByName(matchPre.group(3)) if matchPost else None + + # Next we try to get the input and output ports for any places, including tensors! + + if nodePost and nodePre: + raise Error('Name collision for {}'.format(node_name)) + if nodePost: + return nodePost.getOutputPort(int(matchPost.group(3))) + if nodePre: + return nodePre.getInputPort(int(matchPre.group(1))) + raise Error('There is no node with name {}'.format(node_name)) + + def get_node_id_with_ports(graph: Graph, node_name: str, skip_if_no_port=True): """ Extracts port and node ID out of user provided name @@ -552,13 +593,38 @@ def input_user_data_repack(graph: Graph, input_user_shapes: [None, list, dict, n 'phase_train' : False } """ + _input_shapes = [] + if 'frontend' in graph.graph: + # New version of FrontEnd is activated + print("I'm HERE") + inputModel = graph.graph['input_model'] + if isinstance(input_user_shapes, list) or isinstance(input_user_shapes, dict): + for input_name in input_user_shapes: + node = decodeNameWithPort(graph, input_name) + if node is None: + raise Error('Cannot find location {} in the graph'.format(input_name)) + shape = None if isinstance(input_user_shapes, list) else input_user_shapes[input_name] + if input_name in input_user_data_types and input_user_data_types[input_name] is not None: + data_type = input_user_data_types[input_name] + _input_shapes.append({'node': node, 'shape': shape, 'data_type': data_type}) + else: + _input_shapes.append({'node': node, 'shape': shape}) + elif isinstance(input_user_shapes, np.ndarray): + model_inputs = inputModel.getInputs() + assert len(model_inputs) == 1 + _input_shapes.append({'node': model_inputs[0], 'shape': input_user_shapes}) + else: + assert input_user_shapes is None + return _input_shapes, dict() + + _input_shapes = defaultdict(list) _freeze_placeholder = dict() _freeze_new_placeholder = defaultdict(list) # freeze placeholder restructure # Replaces placeholder name with placeholder id. Raises if there is no placeholder with such ID - placeholders_ids = graph.get_nodes_with_attributes(op='Parameter') + placeholders_ids = graph.get_input_ids() if freeze_placeholder is None: _freeze_placeholder = None else: @@ -952,6 +1018,20 @@ def add_input_ops(graph: Graph, user_defined_inputs: dict, before_infer: bool): For case with before_infer=False data nodes are added to this schemes. """ + + # Limited implementation for nGraph + # Support reshape only, no model cutting is available + #print('--- USER DEFINED INPUTS ---:' + str(user_defined_inputs)) + if 'network' in graph.graph: + # warning connection through node name + #print(user_defined_inputs) + # temporary we handle only 0th index + if user_defined_inputs: + reshape_request = {k.get_friendly_name(): user_defined_inputs[k][0]['shape'] for k in user_defined_inputs} + #print('RESHAPE REQUEST: ' + str(reshape_request)) + graph.graph['network'].reshape(reshape_request) + return + inputs = [] set_is_input(graph, graph.get_nodes_with_attributes(op='Parameter'), False) if user_defined_inputs is None: diff --git a/model-optimizer/mo/graph/graph.py b/model-optimizer/mo/graph/graph.py index 6fca02b665369b..9bf7c9724f082e 100644 --- a/model-optimizer/mo/graph/graph.py +++ b/model-optimizer/mo/graph/graph.py @@ -734,7 +734,7 @@ def unique_id(self, prefix: str = ""): return prefix + str(self.unique_id_count) def check_empty_graph(self, description: str): - if len(self.nodes()) <= 1: + if len(self.nodes()) <= 1 and self.graph['frontend'] is None: raise Error( "Graph contains {} node after executing {}. It considered as error because resulting IR will be " "empty which is not usual".format(len(self.nodes()), description)) @@ -996,6 +996,14 @@ def clean_up(self, undead_node_types: list = None): # Add Const op for constant data nodes add_constant_operations(self) + def get_input_ids(self): + if 'network' in self.graph: + import ngraph as ng + func = ng.function_from_cnn(self.graph['network']) + return func.get_parameters() + else: + return self.get_nodes_with_attributes(op='Parameter') + def fill_graph_with_nodes(graph, src_nodes, get_id: callable, get_attrs: callable): """ diff --git a/model-optimizer/mo/main.py b/model-optimizer/mo/main.py index 7783bea36c8982..26edc9bddbb79c 100644 --- a/model-optimizer/mo/main.py +++ b/model-optimizer/mo/main.py @@ -20,7 +20,7 @@ from mo.graph.graph import Graph from mo.middle.pattern_match import for_graph_and_each_sub_graph_recursively from mo.pipeline.common import prepare_emit_ir, get_ir_version -from mo.pipeline.unified import unified_pipeline +from mo.pipeline.unified import unified_pipeline, moc_pipeline from mo.utils import import_extensions from mo.utils.cli_parser import get_placeholder_shapes, get_tuple_values, get_model_name, \ get_common_cli_options, get_caffe_cli_options, get_tf_cli_options, get_mxnet_cli_options, get_kaldi_cli_options, \ @@ -35,6 +35,8 @@ from mo.utils.version import get_version, get_simplified_mo_version, get_simplified_ie_version from mo.utils.versions_checker import check_requirements +from ngraph import FrontEndManager + def replace_ext(name: str, old: str, new: str): base, ext = os.path.splitext(name) @@ -88,9 +90,18 @@ def print_argv(argv: argparse.Namespace, is_caffe: bool, is_tf: bool, is_mxnet: def prepare_ir(argv: argparse.Namespace): is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx = deduce_framework_by_namespace(argv) + new_front_ends = [] + if not argv.use_legacy_frontend: + fem = FrontEndManager() + new_front_ends = fem.availableFrontEnds() + argv.feManager = fem + if not any([is_tf, is_caffe, is_mxnet, is_kaldi, is_onnx]): - raise Error('Framework {} is not a valid target. Please use --framework with one from the list: caffe, tf, ' - 'mxnet, kaldi, onnx. ' + refer_to_faq_msg(15), argv.framework) + frameworks = ['tf', 'caffe', 'mxnet', 'kaldi', 'onnx'] + frameworks = list(set(frameworks + new_front_ends)) + if argv.framework not in frameworks: + raise Error('Framework {} is not a valid target. Please use --framework with one from the list: {}. ' + + refer_to_faq_msg(15), argv.framework, frameworks) if is_tf and not argv.input_model and not argv.saved_model_dir and not argv.input_meta_graph: raise Error('Path to input model or saved model dir is required: use --input_model, --saved_model_dir or ' @@ -234,18 +245,23 @@ def prepare_ir(argv: argparse.Namespace): t.send_event('mo', 'framework', 'kaldi') from mo.front.kaldi.register_custom_ops import get_front_classes import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - elif is_onnx: + elif is_onnx and ('onnx' not in new_front_ends or argv.use_legacy_frontend): t.send_event('mo', 'framework', 'onnx') from mo.front.onnx.register_custom_ops import get_front_classes import_extensions.load_dirs(argv.framework, extensions, get_front_classes) - graph = unified_pipeline(argv) + + if argv.framework not in new_front_ends or argv.use_legacy_frontend: + graph = unified_pipeline(argv) + else: + graph = moc_pipeline(argv) return graph -def emit_ir(graph: Graph, argv: argparse.Namespace): - NormalizeTI().find_and_replace_pattern(graph) - for_graph_and_each_sub_graph_recursively(graph, RemoveConstOps().find_and_replace_pattern) - for_graph_and_each_sub_graph_recursively(graph, CreateConstNodesReplacement().find_and_replace_pattern) +def emit_ir(graph, argv: argparse.Namespace): + if 'network' not in graph.graph: + NormalizeTI().find_and_replace_pattern(graph) + for_graph_and_each_sub_graph_recursively(graph, RemoveConstOps().find_and_replace_pattern) + for_graph_and_each_sub_graph_recursively(graph, CreateConstNodesReplacement().find_and_replace_pattern) mean_data = deepcopy(graph.graph['mf']) if 'mf' in graph.graph else None input_names = deepcopy(graph.graph['input_names']) if 'input_names' in graph.graph else [] @@ -266,6 +282,10 @@ def emit_ir(graph: Graph, argv: argparse.Namespace): output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd() orig_model_name = os.path.normpath(os.path.join(output_dir, argv.model_name)) + if 'network' in graph.graph: + graph.graph['network'].serialize(orig_model_name + ".xml", orig_model_name + ".bin") + print('[ SUCCESS ] Converted with ONNX Importer') + return_code = "not executed" # This try-except is additional reinsurance that the IE # dependency search does not break the MO pipeline diff --git a/model-optimizer/mo/pipeline/unified.py b/model-optimizer/mo/pipeline/unified.py index 790deeb39d6c11..64908f2c7816c8 100644 --- a/model-optimizer/mo/pipeline/unified.py +++ b/model-optimizer/mo/pipeline/unified.py @@ -7,8 +7,18 @@ from mo.pipeline.common import get_ir_version from mo.utils import class_registration +from extensions.front.user_data_repack import UserDataRepack +from extensions.front.input_cut import InputCut +from extensions.front.output_cut import OutputCut +from mo.utils.class_registration import apply_replacements_list +from ngraph import FrontEndManager +from ngraph import function_to_cnn +from ngraph import PartialShape +import logging as log + def unified_pipeline(argv: argparse.Namespace): + log.info('Legacy unified pipeline') graph = Graph(cmd_params=argv, name=argv.model_name, ir_version=get_ir_version(argv)) class_registration.apply_replacements(graph, [ class_registration.ClassType.LOADER, @@ -17,3 +27,37 @@ def unified_pipeline(argv: argparse.Namespace): class_registration.ClassType.BACK_REPLACER ]) return graph + +def moc_pipeline(argv: argparse.Namespace): + log.info('New MOC pipeline') + fem = argv.feManager if 'feManager' in argv else FrontEndManager() + log.info('fem.availableFrontEnds: ' + str(fem.availableFrontEnds())) + log.info('Initializing new FE for framework {}'.format(argv.framework)) + fe = fem.loadByFramework(argv.framework) + print(fe) + inputModel = fe.loadFromFile(argv.input_model) + + + # Wrap nGraph network to Graph for smoothly pass through the legacy code in MO. + # This trick doesn't mean that we will hold Graph forever as a wrapper, it is derived from + # NX graph and this is not required. But Graph has several methods that can be implemented for nGraph + # and probably they should be kept at least for transition period where some existing transformations + # that manipulate Graph object really translate those modifications directly to nGraph representation. + graph = Graph(frontend=fe, input_model=inputModel, cmd_params=argv, name=argv.model_name, ir_version=get_ir_version(argv)) + + transforms = [ + UserDataRepack, + #InputCut, + #OutputCut + ] + + apply_replacements_list(graph, transforms) + user_shapes = graph.graph['user_shapes'] + if len(user_shapes) > 0: + assert len(inputModel.getInputs()) == 1 + assert len(user_shapes) == 1 + inputModel.setPartialShape(user_shapes[0]['node'], PartialShape(user_shapes[0]['shape'])) + nGraphModel = fe.convert(inputModel) + network = function_to_cnn(nGraphModel) + graph.graph['network'] = network + return graph \ No newline at end of file diff --git a/model-optimizer/mo/utils/cli_parser.py b/model-optimizer/mo/utils/cli_parser.py index b44e47fabf875c..5c3390f942e6cd 100644 --- a/model-optimizer/mo/utils/cli_parser.py +++ b/model-optimizer/mo/utils/cli_parser.py @@ -19,6 +19,8 @@ from mo.utils.utils import refer_to_faq_msg from mo.utils.version import get_version +from ngraph import FrontEndManager + class DeprecatedStoreTrue(argparse.Action): def __init__(self, nargs=0, **kw): @@ -92,8 +94,8 @@ def readable_file(path: str): :param path: path to check :return: path if the file is readable """ - if not os.path.isfile(path): - raise Error('The "{}" is not existing file'.format(path)) + if not os.path.exists(path): + raise Error('The "{}" doesn\'t exist'.format(path)) elif not os.access(path, os.R_OK): raise Error('The "{}" is not readable'.format(path)) else: @@ -607,6 +609,12 @@ def get_onnx_cli_parser(parser: argparse.ArgumentParser = None): onnx_group = parser.add_argument_group('ONNX*-specific parameters') + onnx_group.add_argument("--use_legacy_frontend", + help="Switch back to the original (legacy) frontend for ONNX model conversion. " + + "By default, ONNX Importer is used as a converter.", + default=False, + action='store_true') + return parser @@ -620,10 +628,14 @@ def get_all_cli_parser(): """ parser = argparse.ArgumentParser(usage='%(prog)s [options]') + # TODO: Move FE API load to a single place (except this place there is another one when it is loaded). + fem = FrontEndManager() + frameworks = list(set(['tf', 'caffe', 'mxnet', 'kaldi', 'onnx'] + fem.availableFrontEnds())) + parser.add_argument('--framework', help='Name of the framework used to train the input model.', type=str, - choices=['tf', 'caffe', 'mxnet', 'kaldi', 'onnx']) + choices=frameworks) get_common_cli_parser(parser=parser) diff --git a/model-optimizer/mo/utils/versions_checker.py b/model-optimizer/mo/utils/versions_checker.py index 17ee21f14d1fe7..2f07e498f5439a 100644 --- a/model-optimizer/mo/utils/versions_checker.py +++ b/model-optimizer/mo/utils/versions_checker.py @@ -143,12 +143,15 @@ def get_module_version_list_from_file(file_name, env_setup): [('tensorflow', '>=', '1.2.0'), ('networkx', '==', '2.1'), ('numpy', None, None)] """ req_dict = [] - with open(file_name) as f: - for line in f: - # handle comments - line = line.split('#')[0] + try: + with open(file_name) as f: + for line in f: + # handle comments + line = line.split('#')[0] - req_dict = parse_and_filter_versions_list(line, req_dict, env_setup) + req_dict = parse_and_filter_versions_list(line, req_dict, env_setup) + except FileNotFoundError: + log.warning("No file with name {} that contains requirements for selected framework was found".format(file_name)) return req_dict diff --git a/ngraph/core/include/ngraph/pass/transpose_sinking.h b/ngraph/core/include/ngraph/pass/transpose_sinking.h new file mode 100644 index 00000000000000..0a649bc21b0830 --- /dev/null +++ b/ngraph/core/include/ngraph/pass/transpose_sinking.h @@ -0,0 +1,34 @@ +//***************************************************************************** +// Copyright 2017-2020 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 + +namespace ngraph { +namespace pass { + +class NGRAPH_API TransposeSinking : public ngraph::pass::FunctionPass { + public: + TransposeSinking() { + set_property(ngraph::pass::PassProperty::REQUIRE_STATIC_SHAPE, true); + } + bool run_on_function(std::shared_ptr function) override; +}; + +} // namespace pass +} // namespace ngraph diff --git a/ngraph/core/src/pass/transpose_sinking.cpp b/ngraph/core/src/pass/transpose_sinking.cpp new file mode 100644 index 00000000000000..92c2f3d65b6659 --- /dev/null +++ b/ngraph/core/src/pass/transpose_sinking.cpp @@ -0,0 +1,501 @@ +//***************************************************************************** +// Copyright 2017-2020 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 +#include + +#include + +using namespace std; + +#include "ngraph/opsets/opset5.hpp" + +#if 0 +#define NGRAPH_VLOG(LEVEL) std::cerr << "[ DEBUG ][ " << LEVEL << " ]" +#else +#define NGRAPH_VLOG(LEVEL) std::ostringstream() +#endif + +namespace ngraph { + +namespace pass { + +namespace opset = ngraph::opset5; +namespace default_opset = ngraph::opset5; + + +using TransposeMap = unordered_map>; + +static ngraph::CoordinateDiff apply_permutation(ngraph::CoordinateDiff input, + ngraph::AxisVector order) { + ngraph::CoordinateDiff output(input.size()); + for (size_t i = 0; i < order.size(); i++) { + output[i] = input.at(order.at(i)); + } + return output; +} + +static ngraph::AxisVector permutation_to_default_order( + const ngraph::AxisVector& axis_order) { + ngraph::AxisVector out(axis_order.size()); + for (size_t i = 0; i < axis_order.size(); i++) { + out.at(axis_order[i]) = i; + } + return out; +} + +template +static string describe(shared_ptr node) { + // ensure that it's either a reshape or a transpose + // TODO: use static_assert + if (!(std::is_base_of::value || + std::is_base_of::value)) { + throw runtime_error( + "describe template specialization has to be either reshape or " + "transpose"); + } + stringstream ss; + auto transpose = ngraph::as_type_ptr(node); + auto const1 = ngraph::as_type_ptr( + transpose->get_input_node_shared_ptr(1)); + ss << transpose->get_name() << " ( axis order = " + << ngraph::vector_to_string(const1->get_axis_vector_val()) + << " , shape = " << ngraph::vector_to_string(transpose->get_shape()) + << " ) " + << " , input = " << transpose->input_value(0).get_node()->get_name(); + return ss.str(); +} + +static shared_ptr make_transpose( + ngraph::Output arg, const ngraph::AxisVector& input_order) { + auto order = std::make_shared( + ngraph::element::u64, ngraph::Shape{input_order.size()}, input_order); + auto transpose = make_shared(arg, order); + NGRAPH_VLOG(4) << "Make Transpose " << describe(transpose); + return transpose; +} + +static shared_ptr make_reshape( + ngraph::Output arg, const ngraph::AxisVector& input_order) { + auto order = std::make_shared( + ngraph::element::u64, ngraph::Shape{input_order.size()}, input_order); + auto transpose = make_shared(arg, order, false); + NGRAPH_VLOG(4) << "Make Reshape " << describe(transpose); + return transpose; +} + +static void write_transposemap(TransposeMap& reorders, + ngraph::Output target, + shared_ptr transpose) { + auto name = + target.get_node()->get_name() + "." + to_string(target.get_index()); + NGRAPH_VLOG(4) << "Write TransposeMap[" << name + << "] = " << describe(transpose); + reorders[name] = transpose; +} + +static shared_ptr read_transposemap( + TransposeMap& reorders, ngraph::Output target) { + auto name = + target.get_node()->get_name() + "." + to_string(target.get_index()); + auto transpose = reorders[name]; + NGRAPH_VLOG(4) << "Read TransposeMap[" << name << "] -> " + << describe(transpose); + return transpose; +} + +static shared_ptr combine_transposes( + shared_ptr t1, shared_ptr t2) { + auto default_order = ngraph::get_default_order(t1->get_shape()); + auto t1_const = ngraph::as_type_ptr( + t1->input_value(1).get_node_shared_ptr()); + auto t2_const = ngraph::as_type_ptr( + t2->input_value(1).get_node_shared_ptr()); + auto perm_t1 = + ngraph::apply_permutation(default_order, t1_const->get_axis_vector_val()); + auto perm_t2 = + ngraph::apply_permutation(perm_t1, t2_const->get_axis_vector_val()); + auto combined = make_transpose(t2->input_value(0), perm_t2); + NGRAPH_VLOG(4) << "Combining " << describe(t1) << " and " + << describe(t2) << " into " + << describe(combined); + return combined; +} + +static void insert_transpose(shared_ptr target, + shared_ptr transpose, + size_t input_index) { + NGRAPH_VLOG(4) << "Inserting transpose at input " << target->get_name() + << " input index " << input_index; + auto arg = target->input(input_index).get_source_output(); + NGRAPH_VLOG(4) << "Arg shape: " << arg.get_shape(); + auto new_order = ngraph::as_type_ptr( + transpose->input_value(1).get_node_shared_ptr()); + auto new_transpose = make_transpose(arg.get_node_shared_ptr(), + new_order->get_axis_vector_val()); + NGRAPH_VLOG(4) << "Inserting transpose " + << describe(new_transpose) << " at input " + << target->get_name() << " input index " << input_index; + + target->input(input_index).replace_source_output(new_transpose->output(0)); +} + +static void delete_transpose(shared_ptr transpose) { + NGRAPH_VLOG(4) << "Removing transpose " << transpose->get_name(); + if (!transpose->get_users().empty()) { + ngraph::Output output = transpose->output(0); + NGRAPH_VLOG(5) << "output " << output.get_node_shared_ptr()->get_name(); + NGRAPH_VLOG(5) << "target input size " << output.get_target_inputs().size(); + for (auto input : output.get_target_inputs()) { + NGRAPH_VLOG(5) << "input " << input.get_node()->get_name(); + input.replace_source_output(transpose->input_value(0)); + } + } +} + +static void mark_transpose_for_deletion( + shared_ptr transpose, + set>& transposes_to_delete) { + NGRAPH_VLOG(4) << "Marking transpose " << transpose->get_name() + << " for deletion"; + transposes_to_delete.insert(transpose); +} + +static shared_ptr create_default_transpose( + ngraph::Output n) { + auto default_order = ngraph::get_default_order(n.get_shape()); + auto order = std::make_shared( + ngraph::element::u64, ngraph::Shape{default_order.size()}, default_order); + return make_shared(n, order); +} + +// convert_binary_to_default_order is used when one of the arguments +// of a binary op isn't in the default format (i.e. nhwc instead of nchw) +// We normalize the "left" argument to match the order of the "right" argument +// by either inserting a transpose or a reshape, depending on the shape of the +// "left" argument. +static void convert_binary_to_default_order( + shared_ptr binary, const ngraph::Input& input, + ngraph::Output right, TransposeMap& reorders, + set>& transposes_to_delete) { + auto left = input.get_source_output(); + auto right_t = read_transposemap(reorders, right); + auto right_const = ngraph::as_type_ptr( + right_t->input_value(1).get_node_shared_ptr()); + + auto perm_to_def = + permutation_to_default_order(right_const->get_axis_vector_val()); + + // if right input is being implicitly broadcasted, insert a reshape + // instead of a transpose + shared_ptr new_node; + auto left_shape = left.get_shape(); + if (left_shape.size() < perm_to_def.size()) { + left_shape.insert(left_shape.begin(), + perm_to_def.size() - left_shape.size(), 1); + auto new_shape = ngraph::apply_permutation(left_shape, perm_to_def); + new_node = make_reshape(left, new_shape); + } else if (left_shape.size() == perm_to_def.size()) { + new_node = make_transpose(left, perm_to_def); + } else { + throw runtime_error( + "case not supported when converting binary to default order"); + } + input.replace_source_output(new_node->output(0)); + + NGRAPH_VLOG(4) << "right = " << ngraph::vector_to_string(right.get_shape()) + << ", " << right.get_node_shared_ptr()->get_name(); + // this should now insert transpose on right + mark_transpose_for_deletion(right_t, transposes_to_delete); + write_transposemap(reorders, binary, right_t); +} + +static void materialize_shapes( + shared_ptr n, TransposeMap& reorders, + set>& transposes_to_delete) { + // For each node, create a default transpose for + // each of the outputs and store in the map + for (auto& it : n->outputs()) { + write_transposemap(reorders, it, create_default_transpose(it)); + } + + for (size_t i = 0; i < n->input_values().size(); i++) { + // materialize all pending transposes, flush pending transposes + auto arg = n->input_value(i); + auto arg_transpose = read_transposemap(reorders, arg); + NGRAPH_VLOG(4) << "Materializing " + << describe(arg_transpose) << " for " + << arg.get_node_shared_ptr()->get_name(); + mark_transpose_for_deletion(arg_transpose, transposes_to_delete); + auto arg_shape = arg.get_shape(); + auto arg_transpose_order = ngraph::as_type_ptr( + arg_transpose->input_value(1).get_node_shared_ptr()); + if (arg_transpose_order->get_axis_vector_val() != + get_default_order(arg.get_shape())) { + // Insert if arg needs to be transposed. + insert_transpose(n, arg_transpose, i); + } + } +} + +static void sink_transpose( + shared_ptr transpose, TransposeMap& reorders, + set>& transposes_to_delete) { + NGRAPH_VLOG(4) << "Sinking Transpose :" + << describe(transpose); + auto transpose_in = transpose->input_value(0); + auto orig_transpose = read_transposemap(reorders, transpose_in); + // combine both transposes + auto new_transpose = combine_transposes(orig_transpose, transpose); + // remove original transpose now it's combined with a new one + // should be safe to remove an already detached node + mark_transpose_for_deletion(orig_transpose, transposes_to_delete); + // replace transpose with combined one + ngraph::replace_node(transpose, new_transpose); + mark_transpose_for_deletion(new_transpose, transposes_to_delete); + write_transposemap(reorders, new_transpose, new_transpose); +} + +static void sink_unary( + shared_ptr n, TransposeMap& reorders, + set>& /* transposes_to_delete */) { + auto arg_transpose = read_transposemap(reorders, n->input_value(0)); + NGRAPH_VLOG(4) << "Propagating " << describe(arg_transpose) + << " for " << n->get_name(); + write_transposemap(reorders, n, arg_transpose); +} + +static void sink_binary(shared_ptr binary, TransposeMap& reorders, + set>& transposes_to_delete) { + auto left = binary->input_value(0); + auto right = binary->input_value(1); + auto left_t = read_transposemap(reorders, left); + auto right_t = read_transposemap(reorders, right); + auto left_const = ngraph::as_type_ptr( + left_t->input_value(1).get_node_shared_ptr()); + auto right_const = ngraph::as_type_ptr( + right_t->input_value(1).get_node_shared_ptr()); + + auto left_order = left_const->get_axis_vector_val(); + auto right_order = right_const->get_axis_vector_val(); + + auto left_mismatch = + left_order != ngraph::get_default_order(left.get_shape()); + auto right_mismatch = + right_order != ngraph::get_default_order(right.get_shape()); + + NGRAPH_VLOG(4) + << "Sink binary " << binary->get_name() + << " left transpose: " << ngraph::vector_to_string(left_order) + << " left default: " + << ngraph::vector_to_string(ngraph::get_default_order(left.get_shape())) + << " right transpose: " << ngraph::vector_to_string(right_order) + << " right default: " + << ngraph::vector_to_string(ngraph::get_default_order(right.get_shape())); + + if ((left_order.size() == right_order.size() && left_order == right_order) || + (!left_mismatch && !right_mismatch)) { + // Propagate the reshape which matches the shape of the binary node + auto new_transpose = + (binary->get_output_shape(0) == left.get_shape()) ? left_t : right_t; + NGRAPH_VLOG(4) << "Propagating " + << describe(new_transpose) << " for " + << binary->get_name(); + write_transposemap(reorders, binary, new_transpose); + // at this point, both transposes will be eventually removed + mark_transpose_for_deletion(left_t, transposes_to_delete); + mark_transpose_for_deletion(right_t, transposes_to_delete); + } else { + if (right_mismatch) { + convert_binary_to_default_order(binary, binary->input(0), right, reorders, + transposes_to_delete); + } + if (left_mismatch) { + convert_binary_to_default_order(binary, binary->input(1), left, reorders, + transposes_to_delete); + } + } +} + +static void sink_pad( + shared_ptr n, TransposeMap& reorders, + set>& /* transposes_to_delete */) { + auto n_in = n->input_value(0); + auto arg_transpose = read_transposemap(reorders, n_in); + describe(arg_transpose); + auto arg_transpose_order = ngraph::as_type_ptr( + arg_transpose->input_value(1).get_node_shared_ptr()); + auto order = arg_transpose_order->get_axis_vector_val(); + // we need the correct input shape to produce the right output shape + // we are going to create a label of the right input shape, + // so a new pad will have the right shape + auto def_order = permutation_to_default_order(order); + auto input_shape = + ngraph::apply_permutation(arg_transpose->get_shape(), def_order); + auto dummy_correct_shape = make_shared( + arg_transpose->get_element_type(), input_shape); + + auto pad_begin = apply_permutation(n->get_pads_begin(), def_order); + auto pad_end = apply_permutation(n->get_pads_end(), def_order); + auto new_begin = make_shared( + ngraph::element::i64, ngraph::Shape{pad_begin.size()}, pad_begin); + auto new_end = make_shared( + ngraph::element::i64, ngraph::Shape{pad_end.size()}, pad_end); + auto new_pad = + make_shared(dummy_correct_shape, new_begin, new_end, + n->input_value(3), n->get_pad_mode()); + ngraph::replace_node(dummy_correct_shape, + n->input_value(0).get_node_shared_ptr()); + NGRAPH_VLOG(4) << "Replacing " << n->get_name() << " with " + << new_pad->get_name(); + ngraph::replace_node(n, new_pad); + auto new_transpose = make_transpose(new_pad, order); + NGRAPH_VLOG(4) << "Propagating " << describe(new_transpose) + << " for " << n->get_name(); + write_transposemap(reorders, new_pad, new_transpose); +} + +static void sink_concat(shared_ptr n, TransposeMap& reorders, + set>& transposes_to_delete) { + auto n_in = n->input_value(0); + auto arg_transpose = read_transposemap(reorders, n_in); + auto arg_transpose_order = ngraph::as_type_ptr( + arg_transpose->input_value(1).get_node_shared_ptr()); + auto order = arg_transpose_order->get_axis_vector_val(); + // we need the correct input shape to produce the right output shape + // we are going to create a label of the right input shape, + // so a new concat will have the right shape + auto def_order = permutation_to_default_order(order); + auto input_shape = + ngraph::apply_permutation(arg_transpose->get_shape(), def_order); + auto dummy_correct_shape = make_shared( + arg_transpose->get_element_type(), input_shape); + + ngraph::NodeVector new_args; + new_args.push_back(dummy_correct_shape); + + for (size_t i = 1; i < n->get_input_size(); i++) { + auto iarg = n->input_value(i); + auto iarg_transpose = read_transposemap(reorders, iarg); + auto iarg_transpose_order = ngraph::as_type_ptr( + iarg_transpose->input_value(1).get_node_shared_ptr()); + auto iorder = iarg_transpose_order->get_axis_vector_val(); + if (iorder != order) { + NGRAPH_VLOG(4) << " input order at " << i + << "-th arg is different from first arg"; + materialize_shapes(n, reorders, transposes_to_delete); + return; + } + + auto iinput_shape = + ngraph::apply_permutation(iarg_transpose->get_shape(), def_order); + auto idummy_correct_shape = make_shared( + iarg_transpose->get_element_type(), iinput_shape); + new_args.push_back(idummy_correct_shape); + } + + auto new_axis = order.at(n->get_concatenation_axis()); + auto new_concat = make_shared(new_args, new_axis); + // put back the original arguments + for (size_t i = 0; i < new_concat->get_input_size(); i++) { + NGRAPH_VLOG(4) << "Replacing " << new_concat->get_name() << " input " << i + << " with " << n->get_name() << " input " << i; + new_concat->input(i).replace_source_output(n->input_value(i)); + } + NGRAPH_VLOG(4) << "Replacing " << n->get_name() << " with " + << new_concat->get_name(); + ngraph::replace_node(n, new_concat); + auto new_transpose = make_transpose(new_concat, order); + NGRAPH_VLOG(4) << "Propagating " << describe(new_transpose) + << " for " << n->get_name(); + write_transposemap(reorders, new_concat, new_transpose); +} + +// The goal of TransposeSinking is to remove +// round-trip transposes(i.e. nhwc->nchw(nchw-only-op)->nhwc) +// around nchw-only-op (e.g.Convolution, Batchnorm, Avg/MaxPool) +// This is achieved by both **sinking**, propagating transposes +// through ops towards ngraph::op::Results, +// or **swimming** Transposes up towards ngraph::op::Parameter +// For each op type we support we can either combine +// two transposes by replacing the existing Transpose, +// materialize pending transposes if they can't be propagated through op +bool TransposeSinking::run_on_function(shared_ptr f) { + TransposeMap reorders; + set> transposes_to_delete; + unordered_map orig_result_out_shape; + + // STEP 1 : Sink or Swim transposes away for op clusters + for (auto n : f->get_ordered_ops()) { + NGRAPH_VLOG(4) << "Processing " << n->get_name(); + // collect output shape of all Result nodes for a sanity check + if (ngraph::op::is_output(n)) { + orig_result_out_shape[n->get_name()] = n->get_output_shape(0); + } + if (auto transpose = ngraph::as_type_ptr(n)) { + sink_transpose(transpose, reorders, transposes_to_delete); + } else if (ngraph::op::is_unary_elementwise_arithmetic(n)) { + sink_unary(n, reorders, transposes_to_delete); + } else if (ngraph::op::is_binary_elementwise_arithmetic(n)) { + sink_binary(n, reorders, transposes_to_delete); + } else if (auto pad = ngraph::as_type_ptr(n)) { + sink_pad(pad, reorders, transposes_to_delete); + } else if (auto concat = ngraph::as_type_ptr(n)) { + sink_concat(concat, reorders, transposes_to_delete); + } else { + materialize_shapes(n, reorders, transposes_to_delete); + } + } + + // STEP 2: purge all the transposes we either sunk or swam. + NGRAPH_VLOG(4) << "Purging transposes "; + for (auto r : transposes_to_delete) { + delete_transpose(r); + } + + // STEP 3: fix wrong shape info wholesale + NGRAPH_VLOG(4) << "Fixing wrong shape info for the whole graph"; + for (auto n : f->get_ordered_ops()) { + n->revalidate_and_infer_types(); + } + + const ngraph::ResultVector& results = f->get_results(); + for (auto r : results) { + // make sure shapes are always materialized before results + NGRAPH_CHECK( + r->get_shape() == r->get_input_shape(0) && + r->get_element_type() == r->input_value(0).get_element_type(), + " op::Result = ", *r, ", Arg = ", r->input_value(0).get_node()); + + // make sure that after TransposeSinking pass the output_shape for Result + // does not change from the expected output_shape before the pass + NGRAPH_CHECK(r->get_output_shape(0) == orig_result_out_shape[r->get_name()], + " op::Result = ", *r, " expected output shape = ", + orig_result_out_shape[r->get_name()]); + } + + return true; +} + +} // namespace pass +} // namespace ngraph diff --git a/ngraph/frontend/CMakeLists.txt b/ngraph/frontend/CMakeLists.txt index 3e21b4b50171ec..31ee7199f470d3 100644 --- a/ngraph/frontend/CMakeLists.txt +++ b/ngraph/frontend/CMakeLists.txt @@ -3,6 +3,7 @@ # if (NGRAPH_ONNX_IMPORT_ENABLE) + add_subdirectory(generic) add_subdirectory(onnx_common) add_subdirectory(onnx_import) endif() @@ -10,3 +11,6 @@ endif() if (NGRAPH_ONNX_EDITOR_ENABLE) add_subdirectory(onnx_editor) endif() + +#add_subdirectory(tensorflow) +#add_subdirectory(paddlepaddle) diff --git a/ngraph/frontend/generic/CMakeLists.txt b/ngraph/frontend/generic/CMakeLists.txt new file mode 100644 index 00000000000000..6f0997f41a8cda --- /dev/null +++ b/ngraph/frontend/generic/CMakeLists.txt @@ -0,0 +1,86 @@ +# ****************************************************************************** +# 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. +# ****************************************************************************** + +file(GLOB_RECURSE LIBRARY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) +file(GLOB_RECURSE LIBRARY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) +file(GLOB_RECURSE LIBRARY_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp) + +set(FRONTEND_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Create named folders for the sources within the .vcproj +# Empty name lists them directly under the .vcproj + +source_group("src" FILES ${LIBRARY_SRC}) +source_group("include" FILES ${LIBRARY_HEADERS}) +source_group("public include" FILES ${LIBRARY_PUBLIC_HEADERS}) + +# Create shared library +add_library(frontend_manager SHARED ${LIBRARY_SRC} ${LIBRARY_HEADERS} ${LIBRARY_PUBLIC_HEADERS} ${PROTO_SRCS} ${PROTO_HDRS}) +add_library(ngraph::frontend_manager ALIAS frontend_manager) + +target_include_directories(frontend_manager + PRIVATE + #${CMAKE_CURRENT_SOURCE_DIR}/../tensorflow/include + ${CMAKE_CURRENT_SOURCE_DIR}/../onnx_editor/include + ${CMAKE_CURRENT_SOURCE_DIR}/../onnx_import/include + ${CMAKE_CURRENT_SOURCE_DIR}/../onnx_editor/include + ${CMAKE_CURRENT_SOURCE_DIR}/../onnx_common/include + #${CMAKE_CURRENT_SOURCE_DIR}/../paddlepaddle/include + ) + +MESSAGE("HERE4: " ${CMAKE_CURRENT_SOURCE_DIR}) + +if(COMMAND ie_add_vs_version_file) + ie_add_vs_version_file(NAME frontend_manager + FILEDESCRIPTION "Manager of OpenVINO nGraph Front Ends") +endif() + +# TODO: Add other frontends to the list of dependencies temporary; in the produce version they should be discovered +# TODO: in runtime. +target_link_libraries(frontend_manager PRIVATE onnx_importer onnx_editor onnx_common PUBLIC ngraph) + +set(FRONTEND_INSTALL_INCLUDE "${NGRAPH_INSTALL_INCLUDE}/ngraph/frontend/generic") +target_include_directories(frontend_manager SYSTEM PUBLIC $ + $ ${ONNX_IMPORT_INCLUDE_DIR} ${TENSORFLOW_FRONTEND_INCLUDE_DIR}) +target_include_directories(frontend_manager SYSTEM PRIVATE ${NGRAPH_INCLUDE_PATH} ${ONNX_IMPORT_INCLUDE_PATH} ${ONNX_IMPORT_INCLUDE_DIR} + ${FRONTEND_INCLUDE_DIR} ${TENSORFLOW_FRONTEND_INCLUDE_DIR}) + +target_include_directories(frontend_manager PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${ONNX_IMPORT_INCLUDE_DIR} ${TENSORFLOW_FRONTEND_INCLUDE_DIR}) + +# TODO: Consider to remove the following block (inherited from onnx_import just in case). +if (CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$") + target_compile_options(frontend_manager PRIVATE -Wno-undef -Wno-reserved-id-macro -Wno-switch-enum + -Wno-invalid-offsetof -Wno-shorten-64-to-32 -Wno-unused-macros -Wno-missing-variable-declarations + -Wno-unused-private-field -Wno-shadow -Wno-deprecated PUBLIC -Wno-undefined-func-template) +endif() + +install(TARGETS frontend_manager EXPORT ngraphTargets + RUNTIME DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph + ARCHIVE DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph + LIBRARY DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph) + +install(DIRECTORY ${FRONTEND_INCLUDE_DIR}/frontend_manager + DESTINATION ${FRONTEND_INSTALL_INCLUDE} + COMPONENT ngraph + FILES_MATCHING + PATTERN "*.hpp" + PATTERN "*.h" +) + + +if (NGRAPH_EXPORT_TARGETS_ENABLE) + export(TARGETS frontend_manager NAMESPACE ngraph:: APPEND FILE "${NGRAPH_TARGETS_FILE}") +endif() diff --git a/ngraph/frontend/generic/include/frontend_manager/frontend_manager.hpp b/ngraph/frontend/generic/include/frontend_manager/frontend_manager.hpp new file mode 100644 index 00000000000000..53be3f89ec3dd7 --- /dev/null +++ b/ngraph/frontend/generic/include/frontend_manager/frontend_manager.hpp @@ -0,0 +1,335 @@ +//***************************************************************************** +// 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 "ngraph/function.hpp" +#include "ngraph/visibility.hpp" + +namespace ngraph +{ +namespace frontend +{ + + +/// \brief An interface for identifying a place in a graph and iterate over it; can refer to an operation node, tensor, port etc. +/// +/// \note Each front end implementation provides specialization of this interface to represent a place +/// in a model graph. Various methods in the front end classes accept and retrieve instances +/// of Place to point to particular node part which should be modified or satisfies some criteria. +/// For example, this class is used to report model inputs and outputs, for searching operations and tensors +/// by name, for setting shape etc. +/// +/// Place can refer to Tensor, Input Edge, Input Port, Operation, Output Port, Output Edge +/// +/// [Tensor A] +/// | +/// | [Input Edge] +/// | +/// V +/// ------------------- +/// [ [Input Port 0] ] +/// [ ] +/// [ Operation A ] +/// [ ] +/// [ [Output Port 0] ] +/// ------------------- +/// | +/// | [Output Edge] +/// | +/// V +/// [Tensor B] +/// | +/// | [Input Edge] +/// | +/// V +/// ------------------- +/// [ [Input Port 0] ] +/// [ ] +/// [ Operation B ] +/// [ ] +/// [ [Output Port 0] ] +/// ------------------- +/// | +/// | [Output Edge] +/// | +/// V +/// [Tensor C] +/// +class NGRAPH_API Place +{ +public: + + typedef std::shared_ptr Ptr; + + /// \brief All associated names (synonyms) that identify this place in the graph in a framework specific way + /// \return A vector of strings each representing a name that identifies this place in the graph. + /// Can be empty if there are no names associated with this place or name cannot be attached. + virtual std::vector getNames () const; + + /// \brief Returns references to all operation nodes that consume data from this place + /// \note It can be called for any kind of graph place searching for the first consuming opertions. + /// + /// \param outputPortIndex If place is an operational node it specifies which output port should be considered + /// \return A vector with all operation node references that consumes data from this place + virtual std::vector getConsumingOperations (int outputPortIndex = -1) const; + + /// \brief Returns a tensor place that gets data from this place; applicable for operations, output ports and output edges + /// + /// \param outputPortIndex Output port index if the current place is an operation node and has multiple output ports + /// \return A tensor place which hold the resulting value for this place + virtual Ptr getTargetTensor (int outputPortIndex = -1) const; + + /// \brief Returns a tensor place that supplies data for this place; applicable for operations, input ports and input edges + /// + /// \param inputPortIndex Input port index for operational nodes + /// \return A tensor place which supplies data for this place + virtual Ptr getSourceTensor (int inputPortIndex = -1) const; + + /// \brief Get an operation node place that immediately produces data for this place + /// + /// \param inputPortIndex If a given place is itself an operation node, this specifies a port index + /// \return An operation place that produces data for this place + virtual Ptr getProducingOperation (int inputPortIndex = -1) const; + + /// Returns a port that produces data for this place + virtual Ptr getProducingPort () const; + + /// For operation node returns reference to an input port with specified index, + /// for a tensor returns input port of operation that produces that tensor + virtual Ptr getInputPort (int inputPortIndex = -1) const; + + /// For operation node returns reference to an output port with specified index, + /// for a tensor returns output port that that produces this tensor + virtual Ptr getOutputPort (int outputPortIndex = -1) const; + + /// Returns all input ports that consume data flows through this place + virtual std::vector getConsumingPorts () const; + + /// Returns true if this place is input for a model. + virtual bool isInput () const; + + /// Returns true if this place is output for a model. + virtual bool isOutput () const; + + /// Returns true if another place is the same as this place. + virtual bool isEqual (Ptr another) const; + + /// \brief Returns true if another place points to the same data. + /// \note The same data means all places on path: output port -> output edge -> tensor -> input edge -> input port. + virtual bool isEqualData (Ptr another) const; +}; + + +/// \brief InputModel class represents an original, not yet converted model graph in a framework format given +/// services to find places of interest in a graph or specialize/edit the model before conversion. +/// +/// \note Class methods are divided into several groups: searching for places, naming and annotation, +/// topology editing, setting tensor properties. +/// +/// Editing requests may affect ability to convert the original model to nGraph function. Aim to provide +/// these editing capabilities is to unlock conversion for models that are not natively supported "as-is" +/// because of undefined shapes, types or operations. +/// +/// Specific front-end implementation is supposed to have a lazy implementation for all methods, not doing +/// a complete load of a model without an explicit method call. For example, the list of all inputs +/// are not pre-fetched by InputModel derived class instance creation, but only when getInputs method is called. +/// But it is not an obligation, the most convenient way should be chosen depending on the framework model +/// representation. +/// +/// All editing requests affect the model representation that is held behind the scene and successive method +/// calls observe a new graph structure. +class NGRAPH_API InputModel +{ +public: + + typedef std::shared_ptr Ptr; + + + ///// Searching for places ///// + + + /// \brief Returns all inputs for a model + /// An input is a place in a graph where data is supposed to flow inside graph from outside. + /// It can be a tensor, port, operation; which kind of place can be an output is FW dependent. + /// Usually framework models have a dedicated artifact to code model input, it can be a tensor without producer, + /// that writes to it in ONNX, or a special operation like Placeholder in TensorFlow. + /// \return A vector of input place references + virtual std::vector getInputs () const; + + /// \brief Returns all output for a model + /// An output is a terminal place in a graph where data escapes the flow. It can be a tensor, port, operation; + /// which kind of place can be an output is FW dependent. In comparison to a graph input, the output is less + /// formally defined thing and determination of initial list of outputs may include some conventions defined + /// by a frontend itself, not a framework. For example, all output ports without consumers may be considered + /// as outputs. + /// \return A vector of output place references + virtual std::vector getOutputs () const; + + /// Returns a tensor place by a tensor name following framework conventions, or nullptr if a tensor with this name doesn't exist. + virtual Place::Ptr getPlaceByTensorName (const std::string& tensorName); + + /// Returns an operation place by a tensor name following framework conventions, or nullptr if an operation with this name doesn't exist. + virtual Place::Ptr getPlaceByOperationName (const std::string& operationName); + + /// Returns a tensor place by any name that is convenient for target framework. + virtual Place::Ptr getPlaceByName (const std::string& name); + + /// Returns an input port. + virtual Place::Ptr getPlaceByOperationAndInputPort (const std::string& operationName, int inputPortIndex); + + /// Returns an output port. + virtual Place::Ptr getPlaceByOperationAndOutputPort (const std::string& operationName, int outputPortIndex); + + + ///// Naming and annotation ///// + + + virtual void setNameForTensor (Place::Ptr tensor, const std::string& newName); + virtual void addNameForTensor (Place::Ptr tensor, const std::string& newName); + virtual void setNameForOperation (Place::Ptr operation, const std::string& newName); + virtual void freeNameForTensor (const std::string& name); + virtual void freeNameForOperation (const std::string& name); + + virtual void setNameForDimension (Place::Ptr place, size_t shapeDimIndex, const std::string& dimName); + + + ///// Topology Editing ///// + + /// Cut immediately before this place and assign this place as new input; prune all nodes that don't contribute to any output. + virtual void cutAndAddNewInput (Place::Ptr place, const std::string& newNameOptional = ""); + + /// Cut immediately after this place and assign this place as new output; prune all nodes that don't contribute to any output. + virtual void cutAndAddNewOutput (Place::Ptr place, const std::string& newNameOptional = ""); + + /// \brief Assign this place as new output or add necessary nodes to represent a new output. + /// + /// \param place Anchor point to add an output + /// \return new output place, may be the same as a given place + virtual Place::Ptr addOutput (Place::Ptr place); + + /// Removes any sinks directly attached to this place with all inbound data flow if it is not required by any other output. + virtual void removeOutput (Place::Ptr place); + + /// Removes an input place and all data flow that depends on it. + // TODO: remove it as something not practically useful in the API? + virtual void removeInput (Place::Ptr place); + + /// Replaces all existing outputs with new ones removing all data flow that is not required for new outputs. + /// + /// \param outputs Vector with places that will become new outputs; may intersect existing outputs. + virtual void overrideAllOutputs (const std::vector& outputs); + + /// \brief Modifies the graph to use new inputs instead of existing ones. New inputs should completely satisfy all existing outputs. + virtual void overrideAllInputs (const std::vector& inputs); + + /// Leaves only subgraph that are defined by new inputs and new outputs. + virtual void extractSubgraph (const std::vector& inputs, const std::vector& outputs); + + ///// Setting tensor properties ///// + + /// Sets shape that would be used by default for this place; place should be uniquely refer to some data. + // TODO: define clearly which scenario requires it -- currently it should satisfy requirement to have statically defined shapes for tensors + virtual void setDefaultShape (Place::Ptr place, const ngraph::Shape&); + + /// Defines all possible shape that may be used for this place; place should be uniquely refer to some data. + /// This partial shape will be converted to corresponding shape of results ngraph nodes and will define shape inference + /// when the model is converted to ngraph. + virtual void setPartialShape (Place::Ptr place, const ngraph::PartialShape&); + + /// Sets new element type for a place. + virtual void setElementType (Place::Ptr place, const ngraph::element::Type&); + + /// Freezes a tensor with statically defined value or replace existing value for already constant node or tensor. + virtual void setTensorValue (Place::Ptr place, const void* value); + + /// Defines partial value (lower bound and upper bound) for a tensor place. + // TODO: more details for minValue and maxValue format; who defines shape? + virtual void setTensorPartialValue (Place::Ptr place, const void* minValue, const void* maxValue); + + // TODO: Document "inputs/output assymetry" in more details + + // Traversing + // TODO: remove or add something; most likely will have only a single method that provides a list of operation nodes sorted topologically + + // Support querying + // TODO: remove or add something; there are no candidates, all queries can be satisfied without any API extension here +}; + +class NGRAPH_API FrontEnd +{ +public: + typedef std::shared_ptr Ptr; + + virtual InputModel::Ptr loadFromFile (const std::string& path) const; + virtual InputModel::Ptr loadFromFiles (const std::vector& paths) const; + virtual InputModel::Ptr loadFromMemory (const void* model) const; + virtual InputModel::Ptr loadFromMemoryFragments (const std::vector& modelParts) const; + virtual InputModel::Ptr loadFromStream (std::istream& path) const; + virtual InputModel::Ptr loadFromStreams (const std::vector& paths) const; + + // Extra ctors may be provided by FW-specialized data structure for graph representaion + + /// Completely convert and normalize entire function, throws if it is not possible + virtual std::shared_ptr convert (InputModel::Ptr model) const; + + /// Completely convert the remaining, not converted part of a function. + virtual std::shared_ptr convert (std::shared_ptr partiallyConverted) const; + + /// Convert only those parts of the model that can be converted leaving others as-is. + /// Converted parts are not normalized by additional transformations; normalize function + /// or another form of convert function should be called to finalize the conversion process. + virtual std::shared_ptr convertPartially (InputModel::Ptr model) const; + + /// Convert operations with one-to-one mapping with decoding nodes. + /// Each decoding node is an nGraph node representing a single FW operation node with all attributes + /// represented in FW-independent way. + virtual std::shared_ptr decode (InputModel::Ptr model) const; + + /// Runs normalization passes on function that was loaded with partial conversion + virtual void normalize (std::shared_ptr function) const; +}; + +enum FrontEndCapabilities { + FEC_DEFAULT = 0, // Just reading and conversion, w/o any modifications; intended to be used in Reader + FEC_CUT = 1, + FEC_NAMES = 2, + FEC_REPLACE = 4, + FEC_TRAVERSE = 8, + FEC_WILDCARDS = 16, +}; + +class NGRAPH_API FrontEndManager +{ +public: + FrontEndManager(); + ~FrontEndManager(); + FrontEnd::Ptr loadByFramework(const std::string& framework, FrontEndCapabilities fec = FEC_DEFAULT); + FrontEnd::Ptr loadByModel(const std::string& path, FrontEndCapabilities fec = FEC_DEFAULT); + std::vector availableFrontEnds() const; + + using FrontEndFactory = std::function; + void registerFrontEnd(const std::string& name, FrontEndFactory creator); +private: + class Impl; + std::unique_ptr m_impl; +}; + +} // namespace frontend + +} // namespace ngraph diff --git a/ngraph/frontend/generic/src/frontend_manager.cpp b/ngraph/frontend/generic/src/frontend_manager.cpp new file mode 100644 index 00000000000000..71efbf3f855d95 --- /dev/null +++ b/ngraph/frontend/generic/src/frontend_manager.cpp @@ -0,0 +1,548 @@ +//***************************************************************************** +// 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 "onnx_import/onnx.hpp" +#include "onnx_editor/editor.hpp" +//#include "tensorflow_frontend/tensorflow.hpp" +#include "frontend_manager/frontend_manager.hpp" +//#include "paddlepaddle_frontend/frontend.hpp" + + +namespace ngraph +{ + namespace frontend + { + + #define FRONT_END_NOT_IMPLEMENTED(NAME) throw #NAME " is not implemented for this FrontEnd class"; + #define FRONT_END_ASSERT(EXPRESSION) \ + { if (!(EXPRESSION)) throw "AssertionFailed"; } + + std::vector InputModel::getInputs () const + { + FRONT_END_NOT_IMPLEMENTED(getInputs); + } + + std::vector InputModel::getOutputs () const + { + FRONT_END_NOT_IMPLEMENTED(getOutputs); + } + + Place::Ptr InputModel::getPlaceByTensorName (const std::string& tensorName) + { + FRONT_END_NOT_IMPLEMENTED(getPlaceByTensorName); + } + + Place::Ptr InputModel::getPlaceByOperationName (const std::string& operationName) + { + FRONT_END_NOT_IMPLEMENTED(getPlaceByOperationName); + } + + Place::Ptr InputModel::getPlaceByName (const std::string& operationName) + { + FRONT_END_NOT_IMPLEMENTED(getPlaceByName); + } + + Place::Ptr InputModel::getPlaceByOperationAndInputPort (const std::string& operationName, int inputPortIndex) + { + FRONT_END_NOT_IMPLEMENTED(getPlaceByOperationAndInputPort); + } + + Place::Ptr InputModel::getPlaceByOperationAndOutputPort (const std::string& operationName, int outputPortIndex) + { + FRONT_END_NOT_IMPLEMENTED(getPlaceByOperationAndOutputPort); + } + + void InputModel::setNameForTensor (Place::Ptr tensor, const std::string& newName) + { + FRONT_END_NOT_IMPLEMENTED(setNameForTensor); + } + + void InputModel::addNameForTensor (Place::Ptr tensor, const std::string& newName) + { + FRONT_END_NOT_IMPLEMENTED(addNameForTensor); + } + + void InputModel::setNameForOperation (Place::Ptr operation, const std::string& newName) + { + FRONT_END_NOT_IMPLEMENTED(setNameForOperation); + } + + void InputModel::freeNameForTensor (const std::string& name) + { + FRONT_END_NOT_IMPLEMENTED(freeNameForTensor); + } + + void InputModel::freeNameForOperation (const std::string& name) + { + FRONT_END_NOT_IMPLEMENTED(freeNameForOperation); + } + + void InputModel::setNameForDimension (Place::Ptr place, size_t shapeDimIndex, const std::string& dimName) + { + FRONT_END_NOT_IMPLEMENTED(setNameForDimension); + } + + void InputModel::cutAndAddNewInput (Place::Ptr place, const std::string& newNameOptional) + { + FRONT_END_NOT_IMPLEMENTED(cutAndAddNewInput); + } + + void InputModel::cutAndAddNewOutput (Place::Ptr place, const std::string& newNameOptional) + { + FRONT_END_NOT_IMPLEMENTED(cutAndAddNewOutput); + } + + Place::Ptr InputModel::addOutput (Place::Ptr place) + { + FRONT_END_NOT_IMPLEMENTED(addOutput); + } + + void InputModel::removeOutput (Place::Ptr place) + { + FRONT_END_NOT_IMPLEMENTED(removeOutput); + } + + void InputModel::removeInput (Place::Ptr place) + { + FRONT_END_NOT_IMPLEMENTED(removeInput); + } + + void InputModel::overrideAllOutputs (const std::vector& outputs) + { + FRONT_END_NOT_IMPLEMENTED(overrideAllOutputs); + } + + void InputModel::overrideAllInputs (const std::vector& inputs) + { + FRONT_END_NOT_IMPLEMENTED(overrideAllInputs); + } + + void InputModel::extractSubgraph (const std::vector& inputs, const std::vector& outputs) + { + FRONT_END_NOT_IMPLEMENTED(extractSubgraph); + } + + // Setting tensor properties + void InputModel::setDefaultShape (Place::Ptr place, const ngraph::Shape&) + { + FRONT_END_NOT_IMPLEMENTED(setDefaultShape); + } + + void InputModel::setPartialShape (Place::Ptr place, const ngraph::PartialShape&) + { + FRONT_END_NOT_IMPLEMENTED(setPartialShape); + } + + void InputModel::setElementType (Place::Ptr place, const ngraph::element::Type&) + { + FRONT_END_NOT_IMPLEMENTED(setElementType); + } + + void InputModel::setTensorValue (Place::Ptr place, const void*) + { + FRONT_END_NOT_IMPLEMENTED(setTensorValue); + } + + void InputModel::setTensorPartialValue (Place::Ptr place, const void* minValue, const void* maxValue) + { + FRONT_END_NOT_IMPLEMENTED(setTensorPartialValue); + } + + std::vector Place::getNames () const + { + FRONT_END_NOT_IMPLEMENTED(getNames); + } + + std::vector Place::getConsumingOperations (int outputPortIndex) const + { + FRONT_END_NOT_IMPLEMENTED(getConsumingOperations); + } + + Place::Ptr Place::getTargetTensor (int outputPortIndex) const + { + FRONT_END_NOT_IMPLEMENTED(getTargetTensor); + } + + Place::Ptr Place::getProducingOperation (int inputPortIndex) const + { + FRONT_END_NOT_IMPLEMENTED(getProducingOperation); + } + + Place::Ptr Place::getProducingPort () const + { + FRONT_END_NOT_IMPLEMENTED(getProducingPort); + } + + Place::Ptr Place::getInputPort (int inputPortIndex) const + { + FRONT_END_NOT_IMPLEMENTED(getInputPort); + } + + Place::Ptr Place::getOutputPort (int outputPortIndex) const + { + FRONT_END_NOT_IMPLEMENTED(getOutputPort); + } + + std::vector Place::getConsumingPorts () const + { + FRONT_END_NOT_IMPLEMENTED(getConsumingPorts); + } + + bool Place::isInput () const + { + FRONT_END_NOT_IMPLEMENTED(isInput); + } + + bool Place::isOutput () const + { + FRONT_END_NOT_IMPLEMENTED(isOutput); + } + + bool Place::isEqual (Ptr another) const + { + FRONT_END_NOT_IMPLEMENTED(isEqual); + } + + bool Place::isEqualData (Ptr another) const + { + FRONT_END_NOT_IMPLEMENTED(isEqualData); + } + + Place::Ptr Place::getSourceTensor (int inputPortIndex) const + { + FRONT_END_NOT_IMPLEMENTED(getSourceTensor); + } + + class PlaceInputEdgeONNX : public Place + { + public: + + onnx_editor::InputEdge edge; + + PlaceInputEdgeONNX (onnx_editor::InputEdge _edge) : + edge(_edge) + {} + }; + + class PlaceOutputEdgeONNX : public Place + { + public: + + onnx_editor::OutputEdge edge; + + PlaceOutputEdgeONNX (onnx_editor::OutputEdge _edge) : + edge(_edge) + {} + }; + + class InputModelONNX; + + class PlaceTensorONNX : public Place + { + std::string tensorName; + const InputModelONNX* model; + + public: + + PlaceTensorONNX (const std::string& _tensorName, const InputModelONNX* _model) : tensorName(_tensorName), model(_model){} + + virtual std::vector getNames () const override + { + return std::vector(1, tensorName); + } + + virtual std::vector getConsumingPorts () const override; + virtual Place::Ptr getProducingPort () const override; + virtual Ptr getInputPort (int inputPortIndex = -1) const override; + }; + + class InputModelONNX : public InputModel + { + + public: + // TODO: Move to private + onnx_editor::ONNXModelEditor editor; + + InputModelONNX (const std::string& model_path) : editor(model_path) {} + + Place::Ptr getPlaceByTensorName (const std::string& tensorName) override + { + // TODO: Validate name here. Cannot simply do that because I have to differentiate between inputs and outputs + //if(!editor.validate_tensor_name(tensorName)) { + // std::cerr << " [ ERROR ] Node with name " << tensorName << " is not valid for a given model\n"; + // return nullptr; + //} + return std::make_shared(tensorName, this); + } + + std::vector getInputs () const override { + auto inputs = editor.model_inputs(); + std::vector outputs; + outputs.reserve(inputs.size()); + for(auto const& input: inputs) + { + outputs.push_back(std::make_shared(input, this)); + } + return outputs; + } + + void setPartialShape (Place::Ptr place, const ngraph::PartialShape& shape) override + { + std::map m; + m[place->getNames()[0]] = shape; + editor.set_input_shapes(m); + } + + void overrideAllInputs (const std::vector& inputs) override + { + extractSubgraph(inputs, {}); + } + + void overrideAllOutputs (const std::vector& outputs) override + { + extractSubgraph({}, outputs); + } + + void extractSubgraph (const std::vector& inputs, const std::vector& outputs) override + { + std::cerr << "\nTTTTTTTTTTTTT\n"; + std::cerr << "inputs.size() = " << inputs.size() << "\n"; + // Current implementation is limited by tensor places only, each input tensor should be consumed by a single op only + // TODO Extend to non tensor inputs/outputs and remove other limitations + std::vector onnx_inputs; + onnx_inputs.reserve(inputs.size()); + for(const auto& input: inputs) + { + if(auto inputPort = std::dynamic_pointer_cast(input)) + { + onnx_inputs.push_back(inputPort->edge); + } + else if(auto tensor = std::dynamic_pointer_cast(input)) + { + // Have to replace by a single node, NOT multiple nodes (per each consumer) + // TODO: Extend to multiple consumers; currently cannot be implemented due to limitations in the editor API + auto consumers = editor.find_consuming_input_edges(tensor->getNames()[0]); // at least single name should be available + NGRAPH_CHECK(consumers.size() <= 1, "Multiple consumers cutting are not supported, use separate input port for each consumer"); + NGRAPH_CHECK(consumers.size() >= 1, "Tensor that doesn't have consumer is not supported to be specified as a new input"); + onnx_inputs.push_back(consumers[0]); + } + /* + std::cerr << "[] = " << input.get() << "\n"; + // TODO check if input is a tensor + auto inputPorts = input->getConsumingPorts(); + std::cerr << "{1}\n"; + NGRAPH_CHECK(inputPorts.size() == 1); + std::cerr << "{2}\n"; + auto inputPort = inputPorts.front(); + std::cerr << "{3}\n"; + auto onnxInputEdge = std::dynamic_pointer_cast(inputPort); + NGRAPH_CHECK(onnxInputEdge); + onnx_inputs.push_back(onnxInputEdge->edge); + */ + } + std::cerr << "{4}\n"; + + std::vector onnx_outputs; + onnx_outputs.reserve(outputs.size()); + for(const auto& output: outputs) + { + // TODO check if output is a tensor + auto outputPort = output->getProducingPort(); + auto onnxOutputEdge = std::dynamic_pointer_cast(outputPort); + NGRAPH_CHECK(onnxOutputEdge); + onnx_outputs.push_back(onnxOutputEdge->edge); + } + + editor.cut_graph_fragment(onnx_inputs, onnx_outputs); + } + }; + + std::vector PlaceTensorONNX::getConsumingPorts () const + { + std::vector result; + auto edges = model->editor.find_consuming_input_edges(tensorName); + std::transform(edges.begin(), edges.end(), std::back_inserter(result), [](onnx_editor::InputEdge edge) { + return std::make_shared(edge); + }); + return result; + } + + Place::Ptr PlaceTensorONNX::getProducingPort () const + { + return std::make_shared(model->editor.find_output_edge(tensorName)); + } + + Place::Ptr PlaceTensorONNX::getInputPort (int index) const + { + return std::make_shared(model->editor.find_input_edge( + onnx_editor::EditorOutput(tensorName), + onnx_editor::EditorInput(index))); + } + + class FrontEndONNX : public FrontEnd + { + public: + + FrontEndONNX () + { + } + + virtual InputModel::Ptr loadFromFile (const std::string& path) const override + { + return std::make_shared(path); + } + + virtual std::shared_ptr convert (InputModel::Ptr model) const override + { + const auto& model_editor = std::dynamic_pointer_cast(model)->editor; + return onnx_import::detail::convert_to_ng_function( + model_editor.model(), + false); + } + + virtual std::shared_ptr decode (InputModel::Ptr model) const override + { + const auto& model_editor = std::dynamic_pointer_cast(model)->editor; + return onnx_import::detail::convert_to_ng_function( + model_editor.model(), + true); + } + + virtual std::shared_ptr convert (std::shared_ptr f) const override + { + onnx_import::convert_onnx_nodes(f); + return f; + } + }; + + InputModel::Ptr FrontEnd::loadFromFile (const std::string& paths) const + { + FRONT_END_NOT_IMPLEMENTED(loadFromFile); + } + + InputModel::Ptr FrontEnd::loadFromFiles (const std::vector& paths) const + { + FRONT_END_NOT_IMPLEMENTED(loadFromFiles); + } + + InputModel::Ptr FrontEnd::loadFromMemory (const void* model) const + { + FRONT_END_NOT_IMPLEMENTED(loadFromMemory); + } + + InputModel::Ptr FrontEnd::loadFromMemoryFragments (const std::vector& modelParts) const + { + FRONT_END_NOT_IMPLEMENTED(loadFromMemoryFragments); + } + + InputModel::Ptr FrontEnd::loadFromStream (std::istream& path) const + { + FRONT_END_NOT_IMPLEMENTED(loadFromStream); + } + + InputModel::Ptr FrontEnd::loadFromStreams (const std::vector& paths) const + { + FRONT_END_NOT_IMPLEMENTED(loadFromStreams); + } + + std::shared_ptr FrontEnd::convert (InputModel::Ptr model) const + { + FRONT_END_NOT_IMPLEMENTED(convert); + } + + std::shared_ptr FrontEnd::convert (std::shared_ptr) const + { + FRONT_END_NOT_IMPLEMENTED(convert); + } + + std::shared_ptr FrontEnd::convertPartially (InputModel::Ptr model) const + { + FRONT_END_NOT_IMPLEMENTED(convertPartially); + } + + std::shared_ptr FrontEnd::decode (InputModel::Ptr model) const + { + FRONT_END_NOT_IMPLEMENTED(convertDecodingOnly); + } + + void FrontEnd::normalize (std::shared_ptr function) const + { + FRONT_END_NOT_IMPLEMENTED(normalize); + } + + ////////////////////////////////////////////////////////////// + class FrontEndManager::Impl + { + std::map m_factories; + + void registerDefault() { + registerFrontEnd("onnx", [](FrontEndCapabilities){return std::make_shared();}); + //registerFrontEnd("pdpd", [](FrontEndCapabilities){return std::make_shared();}); + //registerFrontEnd("tf", [](FrontEndCapabilities){return std::make_shared();}); + } + public: + Impl() { + registerDefault(); + } + ~Impl() = default; + FrontEnd::Ptr loadByFramework(const std::string& framework, FrontEndCapabilities fec) { + FRONT_END_ASSERT(m_factories.count(framework)) + return m_factories[framework](fec); + } + + std::vector availableFrontEnds() const { + std::vector keys; + + std::transform(m_factories.begin(), m_factories.end(), + std::back_inserter(keys), + [](const std::pair& item) { + return item.first; + }); + return keys; + } + + FrontEnd::Ptr loadByModel (const std::string& path, FrontEndCapabilities fec) + { + FRONT_END_NOT_IMPLEMENTED(loadByModel); + } + + void registerFrontEnd(const std::string& name, FrontEndFactory creator) { + m_factories.insert({name, creator}); + } + }; + + FrontEndManager::FrontEndManager(): m_impl(new Impl()) { + } + FrontEndManager::~FrontEndManager() = default; + + FrontEnd::Ptr FrontEndManager::loadByFramework(const std::string& framework, FrontEndCapabilities fec) + { + return m_impl->loadByFramework(framework, fec); + } + + FrontEnd::Ptr FrontEndManager::loadByModel(const std::string& path, FrontEndCapabilities fec) + { + return m_impl->loadByModel(path, fec); + } + + std::vector FrontEndManager::availableFrontEnds() const + { + return m_impl->availableFrontEnds(); + } + } // namespace frontend + +} // namespace ngraph diff --git a/ngraph/frontend/onnx_editor/include/onnx_editor/edge_mapper.hpp b/ngraph/frontend/onnx_editor/include/onnx_editor/edge_mapper.hpp new file mode 100644 index 00000000000000..3e2c6afaa07df5 --- /dev/null +++ b/ngraph/frontend/onnx_editor/include/onnx_editor/edge_mapper.hpp @@ -0,0 +1,113 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include + +#include "onnx_editor/editor_types.hpp" + +namespace ONNX_NAMESPACE +{ + // Forward declaration to avoid the necessity of including paths in components + // that don't directly depend on the ONNX library + class GraphProto; +} // namespace ONNX_NAMESPACE + +namespace ngraph +{ + namespace onnx_editor + { + /// \brief A class which allows specifying InputEdge and OutputEdge by user-friendly ONNX + /// names. + class EdgeMapper + { + public: + EdgeMapper() = default; + + /// \brief Creates an edge mapper based on a GraphProto object. + /// + /// \note If state of graph_proto will be changed, the information from edge mapper + /// is outdated. In such a case the update method should be called. + /// + /// \param graph_proto Reference to a GraphProto object. + EdgeMapper(const ONNX_NAMESPACE::GraphProto& graph_proto); + + /// \brief Returns the InputEdge based on a node (node name or output name) + /// and an input (input name or input index). + /// + /// \note The node name can be ambiguous (many ONNX nodes can have the same name). + /// In such a case the algorthim tries to match the given node name + /// with the input name (providing an input index is not enough). + /// If a unique edge is found, it will be returned. + /// If InputEdge cannot be determined based on parameter values an ngraph_error + /// exception will be thrown. + /// + /// \param node An EditorNode helper structure created based on a node name + /// or a node output name. + /// + /// \param input An EditorInput helper structure created based on a input name + /// or a input index. + InputEdge find_input_edge(const EditorNode& node, const EditorInput& input) const; + + /// \brief Returns an OutputEdge based on a node (node name or output name) + /// and an output (output name or output index). + /// + /// \note The node name can be ambiguous (many ONNX nodes can have the same name). + /// In such a case the algorthim will try to match the given node name + /// with the output name (providing an output index is not enough). + /// If after such operation a found edge is unique, it is returned. + /// If OutputEdge cannot be determined based on given params the ngraph_error + /// exception is thrown. + /// + /// \param node An EditorNode helper structure created based on a node name + /// or a node output name. + /// + /// \param output An EditorOutput helper structure created based on a output name + /// or a output index. + OutputEdge find_output_edge(const EditorNode& node, const EditorOutput& output) const; + + /// \brief Returns an OutputEdge based on a output name. + /// + /// \note The output name guarantees the uniqueness of the edge. + /// + /// \param output_name A node output name. + /// + OutputEdge find_output_edge(const std::string& output_name) const; + + /// \brief Returns a vector of InputEdges which consume an output of a node + /// determined by provided output name. + /// + /// \note The output name is deterministic in the ONNX standard. + /// + /// \param output_name A node output name. + /// + std::vector find_output_consumers(const std::string& output_name) const; + + /// \brief Returns true if a provided node is correct (exists in a graph) + /// and is not ambiguous (identification of an ONNX node can be ambiguous + /// if an only tensor name is provided). + /// + /// \param node An EditorNode helper structure created based on a node name + /// or a node output name. + /// + bool is_correct_and_unambiguous_node(const EditorNode& node) const; + + private: + std::vector find_node_indexes(const std::string& node_name, + const std::string& output_name) const; + + int get_node_input_idx(int node_index, const std::string& input_name) const; + int get_node_output_idx(int node_index, const std::string& output_name) const; + + std::vector> m_node_inputs; + std::vector> m_node_outputs; + std::multimap m_node_name_to_index; + std::map m_node_output_name_to_index; + std::multimap m_output_consumers_index; + }; + } // namespace onnx_editor +} // namespace ngraph diff --git a/ngraph/frontend/onnx_editor/include/onnx_editor/editor.hpp b/ngraph/frontend/onnx_editor/include/onnx_editor/editor.hpp index 465890cab110ba..d43ba5469e3bd2 100644 --- a/ngraph/frontend/onnx_editor/include/onnx_editor/editor.hpp +++ b/ngraph/frontend/onnx_editor/include/onnx_editor/editor.hpp @@ -87,6 +87,12 @@ namespace ngraph void set_input_values( const std::map>& input_values); + /// \brief Returns a non-const reference to the underlying ModelProto object, possibly + /// modified by the editor's API calls + /// + /// \return A reference to ONNX ModelProto object containing the in-memory model + std::shared_ptr model() const; + /// \brief Returns a serialized ONNX model, possibly modified by the editor. std::string model_string() const; @@ -108,11 +114,84 @@ namespace ngraph /// \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; + // Finds an ONNX node index that writes to a given tensorName + //int find_producing_node_idx(const std::string& tensorName) const; + + // Finds all ONNX nodes indices that consume a value of a given tensorName + std::vector find_consuming_input_edges (const std::string& tensorName) const; + + // Find out whether there is a tensor with a given name + //bool validate_tensor_name(const std::string& tensorName) const; + + /// \brief Returns the InputEdge based on a node (node name or output name) + /// and an input (input name or input index). + /// + /// \note The node name can be ambiguous (many ONNX nodes can have the same name). + /// In such a case the algorthim tries to match the given node name + /// with the input name (providing an input index is not enough). + /// If a unique edge is found, it will be returned. + /// If InputEdge cannot be determined based on parameter values an ngraph_error + /// exception will be thrown. + /// + /// \param node A node helper structure created based on a node name + /// or a node output name. + /// + /// \param input An input helper structure created based on a input name + /// or a input index. + InputEdge find_input_edge(const EditorNode& node, const EditorInput& input) const; + + /// \brief Returns an OutputEdge based on a node (node name or output name) + /// and an output (output name or output index). + /// + /// \note The node name can be ambiguous (many ONNX nodes can have the same name). + /// In such a case the algorthim will try to match the given node name + /// with the output name (providing an output index is not enough). + /// If after such operation a found edge is unique, it is returned. + /// If OutputEdge cannot be determined based on given params the ngraph_error + /// exception is thrown. + /// + /// \param node A node helper structure created based on a node name + /// or a node output name. + /// + /// \param output A output helper structure created based on a output name + /// or a output index. + OutputEdge find_output_edge(const EditorNode& node, const EditorOutput& output) const; + + /// \brief Returns an OutputEdge based on a output name. + /// + /// \note The output name guarantees the uniqueness of the edge. + /// + /// \param output_name A node output name. + /// + OutputEdge find_output_edge(const std::string& output_name) const; + + /// \brief Returns a vector of InputEdges which consume an output of a node + /// determined by provided output name. + /// + /// \note The output name is deterministic in the ONNX standard. + /// + /// \param output_name A node output name. + /// + std::vector find_output_consumers(const std::string& output_name) const; + + /// \brief Returns a vector of InputEdges which consume an output of a node + /// determined by provided output name. + /// + /// \note The output name is deterministic in the ONNX standard. + /// + /// \param output_name A node output name. + /// + bool is_correct_and_unambiguous_node(const EditorNode& node) const; + private: + void update_mapper_if_needed() const; + const std::string m_model_path; + //mutable bool m_is_mapper_updated; struct Impl; std::unique_ptr m_pimpl; + //mutable EdgeMapper m_edge_mapper; }; } // namespace onnx_editor } // namespace ngraph diff --git a/ngraph/frontend/onnx_editor/include/onnx_editor/editor_types.hpp b/ngraph/frontend/onnx_editor/include/onnx_editor/editor_types.hpp index 56afa34af32ecd..bb08fb87267699 100644 --- a/ngraph/frontend/onnx_editor/include/onnx_editor/editor_types.hpp +++ b/ngraph/frontend/onnx_editor/include/onnx_editor/editor_types.hpp @@ -19,46 +19,115 @@ namespace ngraph struct Edge { Edge() = delete; - Edge(const int node_idx, std::string tensor_name) + Edge(const int node_idx, const int port_idx) : m_node_idx{node_idx} - , m_tensor_name{std::move(tensor_name)} + , m_port_idx{port_idx} { } const int m_node_idx; - const std::string m_tensor_name; + const int m_port_idx; }; namespace onnx_editor { /// \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() + /// It consists of a node index in the processed ONNX model and the port index. + /// The node 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)----> +--------+ + /// ----(0)----> +--------+ + /// ----(1)----> | node 5 | ----(0)----> + /// ----(2)----> +--------+ /// /// there are 3 possible valid instances of this struct: - /// InputEdge(5, "in_A") - /// InputEdge(5, "in_B") - /// InputEdge(5, "in_C") + /// InputEdge(5, 0) + /// InputEdge(5, 1) + /// InputEdge(5, 2) 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. + /// It consists of a node index in the processed ONNX model and the port index. /// /// For a node number 5, with 2 outputs: /// - /// +--------+ ----(out1)----> - /// ----(in_A)----> | node 5 | - /// +--------+ ----(out2)----> + /// +--------+ ----(0)----> + /// ----(0)----> | node 5 | + /// +--------+ ----(1)----> /// /// there are 2 possible valid instances of this struct: - /// OutputEdge(5, "out1") - /// OutputEdge(5, "out2") + /// OutputEdge(5, 0) + /// OutputEdge(5, 1) using OutputEdge = Edge; + + /// \brief Specifies a single node input by the name or index. + /// + /// For a node test_node, with 3 inputs: + /// + /// ----(in_A)----> +-----------+ + /// ----(in_B)----> | test_node | ----(out)----> + /// ----(in_C)----> +-----------+ + /// You can indicate in_B as EditorInput("in_B") or EditorInput(1) + struct EditorInput + { + EditorInput() = delete; + EditorInput(std::string input_name) + : m_input_name{std::move(input_name)} + { + } + EditorInput(const int input_index) + : m_input_index{input_index} + { + } + const std::string m_input_name = ""; + const int m_input_index = -1; + }; + + /// \brief Specifies a single node output by the name or index. + /// For a node test_node, with 2 outputs: + /// + /// +-----------+ ---(out1)---> + /// ----(in_A)----> | test_node | + /// +-----------+ ---(out2)---> + /// You can indicate out2 as EditorOutput("out2") or EditorOutput(1) + struct EditorOutput + { + EditorOutput() = delete; + EditorOutput(std::string output_name) + : m_output_name{std::move(output_name)} + { + } + EditorOutput(const int output_index) + : m_output_index{output_index} + { + } + const std::string m_output_name = ""; + const int m_output_index = -1; + }; + + /// \brief Specifies a single node by output name which is determinitic + /// or node name which can be ambiguous. + /// For a node test_node, with 2 outputs: + /// + /// +-----------+ ---(out1)---> + /// ----(in_A)----> | test_node | + /// +-----------+ ---(out2)---> + /// You can indicate test_node by name as EditorNode("test_node") + /// or by assigned output as EditorNode(EditorOutput("out1")) + /// or EditorNode(EditorOutput("out2")) + struct EditorNode + { + EditorNode(std::string node_name) + : m_node_name{std::move(node_name)} + { + } + EditorNode(EditorOutput output) + : m_output_name{std::move(output.m_output_name)} + { + } + const std::string m_node_name = ""; + const std::string m_output_name = ""; + }; } // namespace onnx_editor } // namespace ngraph diff --git a/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.cpp b/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.cpp index 92980dbab0f95d..76388759f1ba8b 100644 --- a/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.cpp +++ b/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.cpp @@ -99,20 +99,49 @@ namespace return *it; } + std::string get_input_tensor_name(ONNX_NAMESPACE::GraphProto& graph, const InputEdge& edge) + { + validate_node_index(graph, edge.m_node_idx); + NGRAPH_CHECK(edge.m_port_idx >= 0 && + edge.m_port_idx < + static_cast(graph.node(edge.m_node_idx).input().size()), + "Node with index: ", + std::to_string(edge.m_node_idx), + " has not input with index: ", + std::to_string(edge.m_port_idx)); + + return graph.node(edge.m_node_idx).input(edge.m_port_idx); + } + + std::string get_output_tensor_name(ONNX_NAMESPACE::GraphProto& graph, const OutputEdge& edge) + { + validate_node_index(graph, edge.m_node_idx); + NGRAPH_CHECK(edge.m_port_idx >= 0 && + edge.m_port_idx < + static_cast(graph.node(edge.m_node_idx).output().size()), + "Node with index: ", + std::to_string(edge.m_node_idx), + " has not output with index: ", + std::to_string(edge.m_port_idx)); + + return graph.node(edge.m_node_idx).output(edge.m_port_idx); + } + /// \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 tensor_name = get_input_tensor_name(graph, edge); const auto it = std::find_if(std::begin(graph.initializer()), std::end(graph.initializer()), - name_equals(edge.m_tensor_name)); + name_equals(tensor_name)); NGRAPH_CHECK(it != std::end(graph.initializer()), "Could not find an initializer in the graph: '", - edge.m_tensor_name); + tensor_name); - if (!already_exists(graph.input(), edge.m_tensor_name)) + if (!already_exists(graph.input(), tensor_name)) { const auto& initializer = *it; auto& new_input = *(graph.add_input()); @@ -127,7 +156,7 @@ namespace new_dim.set_dim_value(initializer_dim); } - *(new_input.mutable_name()) = edge.m_tensor_name; + *(new_input.mutable_name()) = tensor_name; } graph.mutable_initializer()->erase(it); @@ -135,91 +164,57 @@ namespace /// \brief Inserts a new input to the graph and connects it to the node designated by an input /// edge passed to this function. + /// \note input_consumers is number of nodes which consume a new input /// \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) + std::pair append_new_graph_input(ONNX_NAMESPACE::GraphProto& graph, + const InputEdge& edge, + int input_consumers) { - if (already_exists(graph.input(), edge.m_tensor_name) && - !is_graph_initializer(graph, edge.m_tensor_name)) + const auto tensor_name = get_input_tensor_name(graph, edge); + if (already_exists(graph.input(), tensor_name) && !is_graph_initializer(graph, tensor_name)) { // no need to append a new input if an edge points to an existing one in the model - return {false, edge}; + return {false, tensor_name}; } 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), + NGRAPH_CHECK(edge.m_port_idx < target_node.input().size(), "Input '", - edge.m_tensor_name, + edge.m_port_idx, "' not found in the inputs of node ", edge.m_node_idx, ". Cannot append a new graph input to this node."); - // 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)) + std::string new_input_name; + if (input_consumers > 1) { - replace_initializer_with_new_input(graph, edge); - return {false, edge}; + new_input_name = + target_node.output(0) + "/placeholder_port_" + std::to_string(edge.m_port_idx); } 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()) = edge.m_tensor_name; - // attach the new graph input to the target node's input - *target_input = edge.m_tensor_name; - return {true, InputEdge{edge.m_node_idx, edge.m_tensor_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; + new_input_name = tensor_name; } - if (is_graph_initializer(graph, 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, tensor_name)) { replace_initializer_with_new_input(graph, edge); + return {false, tensor_name}; } 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; + new_input.MergeFrom(find_tensor_descriptor(graph, tensor_name)); + *(new_input.mutable_name()) = new_input_name; + // attach the new graph input to the target node's input + auto target_input = target_node.mutable_input(edge.m_port_idx); + *target_input = new_input_name; + return {true, new_input_name}; } - - return -1; } /// \brief Adds new outputs to the ONNX graph for an edge specified by a user @@ -227,27 +222,15 @@ namespace /// original model. void append_new_graph_output(ONNX_NAMESPACE::GraphProto& graph, const OutputEdge& edge) { - if (already_exists(graph.output(), edge.m_tensor_name)) + const auto tensor_name = get_output_tensor_name(graph, edge); + if (already_exists(graph.output(), 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; + new_output.MergeFrom(find_tensor_descriptor(graph, tensor_name)); + *(new_output.mutable_name()) = tensor_name; } /// \brief Removes all items from a container except the ones whose names are in items_to_keep @@ -299,6 +282,7 @@ namespace SubgraphExtractor::SubgraphExtractor(ONNX_NAMESPACE::GraphProto& graph) : m_onnx_graph(graph) + , m_node_inputs(graph.node_size()) { // gathers information about the graph - input edges of every node and number of "consumers" // of all tensors in the graph @@ -306,7 +290,7 @@ SubgraphExtractor::SubgraphExtractor(ONNX_NAMESPACE::GraphProto& graph) { for (const auto& node_input : graph.node(i).input()) { - m_node_inputs.insert({i, node_input}); + m_node_inputs[i].push_back(node_input); m_tensor_consumers[node_input] += 1; } } @@ -316,35 +300,17 @@ 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); + const auto tensor_name = get_input_tensor_name(m_onnx_graph, input_edge); - // 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_input = + append_new_graph_input(m_onnx_graph, input_edge, m_tensor_consumers[tensor_name]); + if (new_input.first) { - // 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); - } + // 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_input.second); } } } @@ -359,23 +325,11 @@ void SubgraphExtractor::add_new_outputs(const std::vector& new_outpu } } -void SubgraphExtractor::replace_input_edge(const InputEdge& old_edge, const InputEdge& new_edge) +void SubgraphExtractor::replace_input_edge(const InputEdge& old_edge, + const std::string& new_input_name) { - // 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}); + // set a new name of an input indicated by the old_edge + m_node_inputs.at(old_edge.m_node_idx).at(old_edge.m_port_idx) = new_input_name; } void SubgraphExtractor::extract_subgraph(std::vector subgraph_outputs) @@ -408,7 +362,8 @@ SubgraphExtractor::SubgraphComponents SubgraphExtractor::discover_output_contrib }; SubgraphComponents output_contributors; - output_contributors.outputs.insert(output_edge.m_tensor_name); + const auto tensor_name = get_output_tensor_name(m_onnx_graph, output_edge); + output_contributors.outputs.insert(tensor_name); // reverse DFS graph traversal std::stack nodes_to_visit; @@ -432,28 +387,28 @@ SubgraphExtractor::SubgraphComponents SubgraphExtractor::discover_output_contrib // 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) + const auto n_inputs = m_node_inputs[n]; + for (auto& input_name : n_inputs) { - if (is_graph_input(m_onnx_graph, input_name->second)) + if (is_graph_input(m_onnx_graph, input_name)) { - output_contributors.inputs.insert(input_name->second); + output_contributors.inputs.insert(input_name); // when an initializer has a matching graph input - if (is_graph_initializer(m_onnx_graph, input_name->second)) + if (is_graph_initializer(m_onnx_graph, input_name)) { - output_contributors.initializers.insert(input_name->second); + output_contributors.initializers.insert(input_name); } } - else if (is_graph_initializer(m_onnx_graph, input_name->second)) + else if (is_graph_initializer(m_onnx_graph, input_name)) { // when an initializer doesn't have a corresponding input - output_contributors.initializers.insert(input_name->second); + output_contributors.initializers.insert(input_name); } 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)); + nodes_to_visit.push(find_source_node_idx(m_onnx_graph, n, input_name)); } } } @@ -475,9 +430,12 @@ std::vector SubgraphExtractor::all_output_edges() const 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()); + const auto node_index = + find_source_node_idx(m_onnx_graph, m_onnx_graph.node_size(), graph_output.name()); + const auto& node_outputs = m_onnx_graph.node(node_index).output(); + const auto output_port_it = + std::find(std::begin(node_outputs), std::end(node_outputs), graph_output.name()); + all_outputs.emplace_back(node_index, output_port_it - std::begin(node_outputs)); } return all_outputs; diff --git a/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.hpp b/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.hpp index 55220d23ad1a9d..191f9e72cd329d 100644 --- a/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.hpp +++ b/ngraph/frontend/onnx_editor/src/detail/subgraph_extraction.hpp @@ -76,14 +76,15 @@ namespace ngraph private: ONNX_NAMESPACE::GraphProto& m_onnx_graph; - // Graph traversal helper: node index -> node inputs (one-to-many) - std::unordered_multimap m_node_inputs; + // Graph traversal helper: the input names of each node + std::vector> 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. + /// \brief Replace the name of an input indicated by the old_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); + void replace_input_edge(const InputEdge& old_edge, const std::string& new_input_name); /// \brief Returns a list of edges of each outputs of the graph "m_onnx_graph" std::vector all_output_edges() const; diff --git a/ngraph/frontend/onnx_editor/src/edge_mapper.cpp b/ngraph/frontend/onnx_editor/src/edge_mapper.cpp new file mode 100644 index 00000000000000..29fbb4e734de32 --- /dev/null +++ b/ngraph/frontend/onnx_editor/src/edge_mapper.cpp @@ -0,0 +1,256 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include "ngraph/except.hpp" +#include "onnx_editor/edge_mapper.hpp" + +using namespace ngraph; +using namespace ngraph::onnx_editor; + +onnx_editor::EdgeMapper::EdgeMapper(const ONNX_NAMESPACE::GraphProto& graph_proto) + : m_node_inputs(graph_proto.node().size()) + , m_node_outputs(graph_proto.node().size()) +{ + int topological_index = 0; + for (const auto& node_proto : graph_proto.node()) + { + for (const auto& out_name : node_proto.output()) + { + // node output name is unique + m_node_output_name_to_index.emplace(out_name, topological_index); + m_node_outputs[topological_index].push_back(out_name); + } + for (const auto& in_name : node_proto.input()) + { + m_node_inputs[topological_index].push_back(in_name); + m_output_consumers_index.emplace(in_name, topological_index); + } + if (!node_proto.name().empty()) + { + // node name can identify node, but it can be ambiguous + m_node_name_to_index.emplace(node_proto.name(), topological_index); + } + ++topological_index; + } +} + +std::vector onnx_editor::EdgeMapper::find_node_indexes(const std::string& node_name, + const std::string& output_name) const +{ + if (!output_name.empty()) + { + const auto& index_iter = m_node_output_name_to_index.find(output_name); + if (index_iter != std::end(m_node_output_name_to_index)) + { + return std::vector{index_iter->second}; + } + } + std::vector result; + if (!node_name.empty()) + { + const auto matched_nodes_range = m_node_name_to_index.equal_range(node_name); + std::transform(matched_nodes_range.first, + matched_nodes_range.second, + std::back_inserter(result), + [](const std::pair& iter) { return iter.second; }); + } + return result; +}; + +int onnx_editor::EdgeMapper::get_node_output_idx(int node_index, + const std::string& output_name) const +{ + if (node_index >= static_cast(m_node_outputs.size())) + { + throw ngraph_error("Node with index: " + std::to_string(node_index) + + "is out of scope outputs list"); + } + const auto out_port_idx = std::find( + std::begin(m_node_outputs[node_index]), std::end(m_node_outputs[node_index]), output_name); + if (out_port_idx == std::end(m_node_outputs[node_index])) + { + throw ngraph_error("Node with index: " + std::to_string(node_index) + + " has not output with name: " + output_name); + } + return (out_port_idx - std::begin(m_node_outputs[node_index])); +} + +int onnx_editor::EdgeMapper::get_node_input_idx(int node_index, const std::string& input_name) const +{ + if (node_index >= 0 && node_index >= static_cast(m_node_inputs.size())) + { + throw ngraph_error("Node with index: " + std::to_string(node_index) + + "is out of scope inputs list"); + } + const auto matched_inputs = std::count( + std::begin(m_node_inputs[node_index]), std::end(m_node_inputs[node_index]), input_name); + if (matched_inputs == 0) + { + throw ngraph_error("Node with index: " + std::to_string(node_index) + + " has not input with name: " + input_name); + } + if (matched_inputs > 1) // more indexes with the same name + { + throw ngraph_error("Node with index: " + std::to_string(node_index) + + " has more than one inputs with name: " + input_name + + ". You should use port indexes to distinguish them."); + } + const auto in_port_idx = std::find( + std::begin(m_node_inputs[node_index]), std::end(m_node_inputs[node_index]), input_name); + return (in_port_idx - std::begin(m_node_inputs[node_index])); +} + +InputEdge onnx_editor::EdgeMapper::find_input_edge(const EditorNode& node, + const EditorInput& in) const +{ + // identification can be both based on node name and output name + const auto& node_indexes = find_node_indexes(node.m_node_name, node.m_output_name); + int node_index = -1; + if (node_indexes.size() == 1) + { + node_index = node_indexes[0]; + } + else if (node_indexes.empty()) + { + throw ngraph_error( + "Node with name: " + (node.m_node_name.empty() ? "not_given" : node.m_node_name) + + " and output_name: " + (node.m_output_name.empty() ? "not_given" : node.m_output_name) + + " was not found"); + } + else if (!in.m_input_name + .empty()) // input indexes are not deterministic if a node name is ambiguous + { + // many nodes with the same name + // check if some of found index matches input name + int matched_inputs_number = 0; + for (const auto& index : node_indexes) + { + if (std::count(std::begin(m_node_inputs[index]), + std::end(m_node_inputs[index]), + in.m_input_name) > 0) + { + node_index = index; + ++matched_inputs_number; + } + } + if (matched_inputs_number == 0) + { + throw ngraph_error("Input edge described by: " + node.m_node_name + + " and input name: " + in.m_input_name + " was not found"); + } + if (matched_inputs_number > 1) + { + throw ngraph_error("Given node name: " + node.m_node_name + " and input name: " + + in.m_input_name + " are ambiguous to determine input edge"); + } + } + else + { + throw ngraph_error("Given node name: " + node.m_node_name + + " and input index: " + std::to_string(in.m_input_index) + + " are ambiguous to determine input edge"); + } + if (in.m_input_index != -1) // input index is set + { + return InputEdge{node_index, in.m_input_index}; + } + if (!in.m_input_name.empty()) + { + const auto input_idx = get_node_input_idx(node_index, in.m_input_name); + return InputEdge{node_index, input_idx}; + } + else + { + throw ngraph_error("Not enough information to determine input edge"); + } +} + +OutputEdge onnx_editor::EdgeMapper::find_output_edge(const EditorNode& node, + const EditorOutput& out) const +{ + // identification can be both based on node name and output name + const auto& node_indexes = find_node_indexes(node.m_node_name, node.m_output_name); + int node_index = -1; + if (node_indexes.size() == 1) + { + node_index = node_indexes[0]; + } + else if (node_indexes.empty()) + { + throw ngraph_error( + "Node with name: " + (node.m_node_name.empty() ? "not_given" : node.m_node_name) + + " and output_name: " + (node.m_output_name.empty() ? "not_given" : node.m_output_name) + + " was not found"); + } + else if (!out.m_output_name + .empty()) // output indexes are not deterministic if a node name is ambiguous + { + // many nodes with the same name + // check if some of found index matches output name + int matched_outputs_number = 0; + for (const auto& index : node_indexes) + { + if (std::count(std::begin(m_node_outputs[index]), + std::end(m_node_outputs[index]), + out.m_output_name) > 0) + { + node_index = index; + ++matched_outputs_number; + } + } + if (matched_outputs_number == 0) + { + throw ngraph_error("Output edge described by: " + node.m_node_name + + " and output name: " + out.m_output_name + " was not found"); + } + } + else + { + throw ngraph_error("Given node name: " + node.m_node_name + + " and output index: " + std::to_string(out.m_output_index) + + " are ambiguous to determine output edge"); + } + if (out.m_output_index != -1) // output index is set + { + return OutputEdge{node_index, out.m_output_index}; + } + if (!out.m_output_name.empty()) + { + const auto output_idx = get_node_output_idx(node_index, out.m_output_name); + return OutputEdge{node_index, output_idx}; + } + else + { + throw ngraph_error("Not enough information to determine output edge"); + } +} + +OutputEdge onnx_editor::EdgeMapper::find_output_edge(const std::string& output_name) const +{ + return find_output_edge(EditorNode{EditorOutput{output_name}}, EditorOutput{output_name}); +} + +std::vector + onnx_editor::EdgeMapper::find_output_consumers(const std::string& output_name) const +{ + const auto matched_nodes_range = m_output_consumers_index.equal_range(output_name); + std::vector input_edges; + std::transform(matched_nodes_range.first, + matched_nodes_range.second, + std::back_inserter(input_edges), + [&output_name, this](const std::pair& iter) { + const auto node_idx = iter.second; + const auto port_idx = this->get_node_input_idx(node_idx, output_name); + return InputEdge{node_idx, port_idx}; + }); + return input_edges; +} + +bool onnx_editor::EdgeMapper::is_correct_and_unambiguous_node(const EditorNode& node) const +{ + return find_node_indexes(node.m_node_name, node.m_output_name).size() == 1; +} diff --git a/ngraph/frontend/onnx_editor/src/editor.cpp b/ngraph/frontend/onnx_editor/src/editor.cpp index 566659e6633a42..26df1aab516ffe 100644 --- a/ngraph/frontend/onnx_editor/src/editor.cpp +++ b/ngraph/frontend/onnx_editor/src/editor.cpp @@ -10,10 +10,12 @@ #include "ngraph/log.hpp" #include "onnx_common/parser.hpp" #include "onnx_common/utils.hpp" +#include "onnx_editor/edge_mapper.hpp" #include "onnx_editor/editor.hpp" #include "onnx_import/utils/onnx_internal.hpp" using namespace ngraph; +using namespace ngraph::onnx_editor; namespace { @@ -185,12 +187,17 @@ namespace /// \brief A helper class used to hold the ModelProto object as its field struct onnx_editor::ONNXModelEditor::Impl { - ONNX_NAMESPACE::ModelProto m_model_proto; + std::shared_ptr m_shared_model_proto; + // leave this member here as a reference to avoid modifying a lot of code + // TODO: reimplement it + ONNX_NAMESPACE::ModelProto& m_model_proto = *m_shared_model_proto; + EdgeMapper m_edge_mapper; + bool m_is_mapper_updated = false; Impl() = delete; Impl(const std::string& model_path) - : m_model_proto{std::move(onnx_common::parse_from_file(model_path))} + : m_shared_model_proto(std::make_shared(std::move(onnx_common::parse_from_file(model_path)))) { } @@ -204,6 +211,11 @@ onnx_editor::ONNXModelEditor::ONNXModelEditor(const std::string& model_path) { } +std::shared_ptr onnx_editor::ONNXModelEditor::model() const +{ + return m_pimpl->m_shared_model_proto; +} + const std::string& onnx_editor::ONNXModelEditor::model_path() const { return m_model_path; @@ -285,6 +297,7 @@ void onnx_editor::ONNXModelEditor::cut_graph_fragment(const std::vectorremove_shape_inference_info(); + m_pimpl->m_is_mapper_updated = false; } std::vector onnx_editor::ONNXModelEditor::model_inputs() const @@ -314,7 +327,7 @@ std::string onnx_editor::ONNXModelEditor::model_string() const std::shared_ptr onnx_editor::ONNXModelEditor::get_function() const { - return onnx_import::detail::import_onnx_model(m_pimpl->m_model_proto, m_model_path); + return onnx_import::detail::import_onnx_model(m_pimpl->m_shared_model_proto, m_model_path, false); } void onnx_editor::ONNXModelEditor::set_input_values( @@ -344,3 +357,115 @@ void onnx_editor::ONNXModelEditor::set_input_values( modify_initializer(*onnx_initializer, name, values, onnx_input); } } + +#if 0 +namespace { + const auto is_equal_to = + +[](const std::string& other) { return [&](const std::string& s) { return s == other; }; }; +} + +int onnx_editor::ONNXModelEditor::find_producing_node_idx(const std::string& tensorName) const +{ + + const auto& graph = m_pimpl->m_model_proto.graph(); + for (int i = 0; i < graph.node_size(); ++i) + { + const auto& outputs = graph.node(i).output(); + const auto output_found = + std::any_of(std::begin(outputs), std::end(outputs), is_equal_to(tensorName)); + + if (output_found) + { + return i; + } + } + + throw ngraph::ngraph_error{"Source node not found in the graph for tensor name: " + + tensorName}; +} +#endif + + +std::vector onnx_editor::ONNXModelEditor::find_consuming_input_edges(const std::string& tensorName) const { + std::cout << "[ WARNING ] Used an extension to ONNX Editor\n"; + const auto &graph = m_pimpl->m_model_proto.graph(); + std::vector result; + for (int i = 0; i < graph.node_size(); ++i) { + const auto &inputs = graph.node(i).input(); + for(int input_index = 0; input_index < inputs.size(); ++input_index) + { + if(inputs[input_index] == tensorName) { + result.push_back(InputEdge(i, input_index)); + } + } + } + return result; +} + +#if 0 +bool onnx_editor::ONNXModelEditor::validate_tensor_name(const std::string& tensorName) const { + const auto &graph = m_pimpl->m_model_proto.graph(); + for (int i = 0; i < graph.node_size(); ++i) { + const auto &outputs = graph.node(i).output(); + const auto output_found = + std::any_of(std::begin(outputs), std::end(outputs), is_equal_to(tensorName)); + + if (output_found) { + return true; + } + + const auto &inputs = graph.node(i).input(); + const auto input_found = + std::any_of(std::begin(inputs), std::end(inputs), is_equal_to(tensorName)); + + if (input_found) { + return true; + } + } + return false; +} +#endif + + +void onnx_editor::ONNXModelEditor::update_mapper_if_needed() const +{ + if (!m_pimpl->m_is_mapper_updated) + { + m_pimpl->m_edge_mapper = EdgeMapper(m_pimpl->m_model_proto.graph()); + } + m_pimpl->m_is_mapper_updated = true; +} + +InputEdge onnx_editor::ONNXModelEditor::find_input_edge(const EditorNode& node, + const EditorInput& input) const +{ + update_mapper_if_needed(); + return m_pimpl->m_edge_mapper.find_input_edge(node, input); +} + +OutputEdge onnx_editor::ONNXModelEditor::find_output_edge(const EditorNode& node, + const EditorOutput& input) const +{ + update_mapper_if_needed(); + return m_pimpl->m_edge_mapper.find_output_edge(node, input); +} + +OutputEdge onnx_editor::ONNXModelEditor::find_output_edge(const std::string& output_name) const +{ + update_mapper_if_needed(); + return m_pimpl->m_edge_mapper.find_output_edge(output_name); +} + +std::vector + onnx_editor::ONNXModelEditor::find_output_consumers(const std::string& output_name) const +{ + update_mapper_if_needed(); + return m_pimpl->m_edge_mapper.find_output_consumers(output_name); +} + +bool onnx_editor::ONNXModelEditor::is_correct_and_unambiguous_node(const EditorNode& node) const +{ + update_mapper_if_needed(); + return m_pimpl->m_edge_mapper.is_correct_and_unambiguous_node(node); +} + diff --git a/ngraph/frontend/onnx_import/include/onnx_import/core/node.hpp b/ngraph/frontend/onnx_import/include/onnx_import/core/node.hpp index e5a59fa9ea92c0..53acb60714c039 100644 --- a/ngraph/frontend/onnx_import/include/onnx_import/core/node.hpp +++ b/ngraph/frontend/onnx_import/include/onnx_import/core/node.hpp @@ -60,6 +60,8 @@ namespace ngraph const std::string& op_type() const; const std::string& get_name() const; + std::vector get_input_names() const; + /// \brief Describe the ONNX Node to make debugging graphs easier /// Function will return the Node's name if it has one, or the names of its outputs. /// \return Description of Node diff --git a/ngraph/frontend/onnx_import/include/onnx_import/framework_node.hpp b/ngraph/frontend/onnx_import/include/onnx_import/framework_node.hpp new file mode 100644 index 00000000000000..bde6c8c3b06809 --- /dev/null +++ b/ngraph/frontend/onnx_import/include/onnx_import/framework_node.hpp @@ -0,0 +1,37 @@ +//***************************************************************************** +// 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 "ngraph/node.hpp" +#include "ngraph/visibility.hpp" + +namespace ngraph +{ +namespace frontend +{ + +class NGRAPH_API FrameworkNode : public ngraph::Node +{ +public: + + using Node::Node; + + // TODO: get_meta_attribute(name) for op_type, domain, name etc.; all properties that are not in FW list of op attributes + + // TODO: get_attribute(name) for real op attributes, the set is individual for each op type +}; + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/onnx_import/include/onnx_import/onnx.hpp b/ngraph/frontend/onnx_import/include/onnx_import/onnx.hpp index 04e6be0cdd527a..d98338046404d1 100644 --- a/ngraph/frontend/onnx_import/include/onnx_import/onnx.hpp +++ b/ngraph/frontend/onnx_import/include/onnx_import/onnx.hpp @@ -12,6 +12,12 @@ #include "ngraph/function.hpp" #include "utils/onnx_importer_visibility.hpp" +#include "utils/onnx_internal.hpp" + +namespace ONNX_NAMESPACE +{ + class ModelProto; +} /// \brief Top level nGraph namespace. namespace ngraph @@ -58,7 +64,7 @@ namespace ngraph /// \return An nGraph function that represents a single output from the created graph. ONNX_IMPORTER_API std::shared_ptr import_onnx_model(std::istream& stream, - const std::string& model_path = ""); + const std::string& model_path = "", bool decode_only = false); /// \brief Imports and converts an ONNX model from the input file /// to an nGraph Function representation. @@ -71,7 +77,11 @@ namespace ngraph /// /// \return An nGraph function that represents a single output from the created graph. ONNX_IMPORTER_API - std::shared_ptr import_onnx_model(const std::string& file_path); + std::shared_ptr import_onnx_model(const std::string& file_path, bool decode_only = false); + + + ONNX_IMPORTER_API + void convert_onnx_nodes (std::shared_ptr f); } // namespace onnx_import } // namespace ngraph diff --git a/ngraph/frontend/onnx_import/include/onnx_import/onnx_node.hpp b/ngraph/frontend/onnx_import/include/onnx_import/onnx_node.hpp new file mode 100644 index 00000000000000..11b8fcfb049b47 --- /dev/null +++ b/ngraph/frontend/onnx_import/include/onnx_import/onnx_node.hpp @@ -0,0 +1,92 @@ +//***************************************************************************** +// 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 "framework_node.hpp" + +namespace ONNX_NAMESPACE +{ + // forward declaration + class ModelProto; +} + +namespace ngraph +{ + +namespace onnx_import +{ + class Model; +} + +namespace frontend +{ + +class NGRAPH_API ONNXNode : public FrameworkNode +{ + NGRAPH_RTTI_DECLARATION; + + onnx_import::Node node; + + std::shared_ptr graph; + std::shared_ptr model; + std::shared_ptr model_proto; + +public: + + ONNXNode (const onnx_import::Node& _node) : + FrameworkNode(_node.get_ng_inputs(), _node.get_outputs_size()), + node(_node) + { + } + + ONNXNode (const OutputVector& _inputs, const onnx_import::Node& _node) : + FrameworkNode(_inputs, _node.get_outputs_size()), + node(_node) + { + } + + void set_onnx_graph (std::shared_ptr _graph) { graph = _graph; } + void set_onnx_model (std::shared_ptr _model) { model = _model; } + void set_onnx_model_proto (std::shared_ptr _model_proto) { model_proto = _model_proto; } + + std::shared_ptr get_onnx_graph () const { return graph; } + //void get_onnx_model (std::shared_ptr _model) { model = _model; } + //void get_onnx_model_proto (std::shared_ptr _model_proto) { model_proto = _model_proto; } + + const onnx_import::Node& get_onnx_node () const { return node; } + + virtual std::shared_ptr clone_with_new_inputs(const OutputVector& inputs) const override; + + virtual bool visit_attributes(AttributeVisitor& visitor) override + { + // TODO: implement reading as well, now it work for serialization only + std::string domain = node.domain(); + std::string op_type = node.op_type(); + visitor.on_attribute("ONNX_META_domain", domain); + visitor.on_attribute("ONNX_META_type", op_type); + return true; + } +}; + +inline OutputVector framework_node_factory (const ngraph::onnx_import::Node& node) +{ + auto ng_node = std::make_shared(node); + return ng_node->outputs(); +} + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/onnx_import/include/onnx_import/utils/onnx_internal.hpp b/ngraph/frontend/onnx_import/include/onnx_import/utils/onnx_internal.hpp index 58554bd3c99234..eb54502d11e234 100644 --- a/ngraph/frontend/onnx_import/include/onnx_import/utils/onnx_internal.hpp +++ b/ngraph/frontend/onnx_import/include/onnx_import/utils/onnx_internal.hpp @@ -21,6 +21,10 @@ namespace ngraph { namespace detail { + ONNX_IMPORTER_API + std::shared_ptr + convert_to_ng_function(std::shared_ptr model_proto, bool decode_only); + /// \brief Imports and converts an serialized ONNX model from a ModelProto /// to an nGraph Function representation. /// @@ -37,8 +41,8 @@ namespace ngraph /// \return An nGraph function that represents a single output from the created /// graph. ONNX_IMPORTER_API - std::shared_ptr import_onnx_model(ONNX_NAMESPACE::ModelProto& model_proto, - const std::string& model_path); + std::shared_ptr import_onnx_model(std::shared_ptr model_proto, + const std::string& model_path, bool decode_only); } // namespace detail } // namespace onnx_import } // namespace ngraph diff --git a/ngraph/frontend/onnx_import/src/core/attribute.cpp b/ngraph/frontend/onnx_import/src/core/attribute.cpp index 950a8494fb751b..923e210b77030f 100644 --- a/ngraph/frontend/onnx_import/src/core/attribute.cpp +++ b/ngraph/frontend/onnx_import/src/core/attribute.cpp @@ -15,7 +15,7 @@ namespace ngraph std::vector result; for (const auto& graph : m_attribute_proto->graphs()) { - result.emplace_back(graph, model); + result.emplace_back(graph, model, false); // TODO: decode_only is false -- not supported here, should be properly propagated } return result; } diff --git a/ngraph/frontend/onnx_import/src/core/graph.cpp b/ngraph/frontend/onnx_import/src/core/graph.cpp index e5323b67fa2df4..6e0b0cc4071bd7 100644 --- a/ngraph/frontend/onnx_import/src/core/graph.cpp +++ b/ngraph/frontend/onnx_import/src/core/graph.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "core/graph.hpp" #include "core/null_node.hpp" @@ -16,6 +17,7 @@ #include "onnx_import/core/node.hpp" #include "utils/common.hpp" #include "utils/provenance_tag.hpp" +#include "onnx_import/onnx_node.hpp" namespace ngraph { @@ -52,8 +54,8 @@ namespace ngraph } } // namespace detail - Graph::Graph(const ONNX_NAMESPACE::GraphProto& graph_proto, Model& model) - : Graph(graph_proto, model, std::unique_ptr(new GraphCache())) + Graph::Graph(const ONNX_NAMESPACE::GraphProto& graph_proto, Model& model, bool decode_only) + : Graph(graph_proto, model, std::unique_ptr(new GraphCache()), decode_only) { // Remove dangling Parameters for (auto param_it = m_parameters.begin(); param_it != m_parameters.end();) @@ -88,10 +90,12 @@ namespace ngraph Graph::Graph(const ONNX_NAMESPACE::GraphProto& graph_proto, Model& model, - std::unique_ptr&& cache) + std::unique_ptr&& cache, + bool decode_only) : m_cache{std::move(cache)} , m_graph_proto{&graph_proto} , m_model{&model} + , m_decode_only(decode_only) { std::map initializers; // Process all initializers in the graph @@ -206,6 +210,10 @@ namespace ngraph } const GraphCache& Graph::get_graph_cache() const { return *m_cache.get(); } + void Graph::update_node_input_cache (const std::string& name, Output&& output) + { + m_cache->emplace_node(name, std::move(output)); + } bool Graph::is_node_in_cache(const std::string& name) const { return m_cache->contains(name); @@ -232,8 +240,19 @@ namespace ngraph OutputVector Graph::make_ng_nodes(const Node& onnx_node) const { - const auto ng_node_factory = - m_model->get_operator(onnx_node.op_type(), onnx_node.domain()); + //if m_decode_only + Operator ng_node_factory; + + //const auto& name = onnx_node.op_type(); + if(m_decode_only /*&& !(name == "Add")*/) + { + ng_node_factory = frontend::framework_node_factory; + } + else + { + ng_node_factory = m_model->get_operator(onnx_node.op_type(), onnx_node.domain()); + } + OutputVector ng_node_vector; try { @@ -341,7 +360,8 @@ namespace ngraph : Graph( proto, model, - std::unique_ptr(new SubgraphCache(parent_graph.get_graph_cache()))) + std::unique_ptr(new SubgraphCache(parent_graph.get_graph_cache())), + false) // TODO: Subgraph is not supported { // find all nodes on edge parent graph-subgraph // (it means input of node from parent graph, output from subgraph) diff --git a/ngraph/frontend/onnx_import/src/core/graph.hpp b/ngraph/frontend/onnx_import/src/core/graph.hpp index 14e3e72f40c977..90c2fbd9357484 100644 --- a/ngraph/frontend/onnx_import/src/core/graph.hpp +++ b/ngraph/frontend/onnx_import/src/core/graph.hpp @@ -23,7 +23,7 @@ namespace ngraph class Graph { public: - Graph(const ONNX_NAMESPACE::GraphProto& proto, Model& model); + Graph(const ONNX_NAMESPACE::GraphProto& proto, Model& model, bool decode_only); Graph(const Graph& graph); const std::vector& get_nodes() const { return m_nodes; } const std::vector& get_inputs() const { return m_inputs; } @@ -35,13 +35,17 @@ namespace ngraph const std::string& get_name() const { return m_graph_proto->name(); } OutputVector make_ng_nodes(const Node& onnx_node) const; const GraphCache& get_graph_cache() const; + void update_node_input_cache (const std::string& name, Output&& output); const OpsetImports& get_opset_imports() const; virtual ~Graph() = default; + void set_decode_only (bool decode_only) { m_decode_only = decode_only; } + protected: Graph(const ONNX_NAMESPACE::GraphProto& proto, Model& model, - std::unique_ptr&& cache); + std::unique_ptr&& cache, + bool decode_only); void set_friendly_names(const Node& onnx_node, const OutputVector& ng_node_vector) const; @@ -65,6 +69,7 @@ namespace ngraph std::vector m_inputs; std::vector m_outputs; Model* m_model; + bool m_decode_only = false; }; /// \brief Representation of ONNX subgraph. It is used for example by ONNX Loop op. diff --git a/ngraph/frontend/onnx_import/src/core/model.cpp b/ngraph/frontend/onnx_import/src/core/model.cpp index 0878b9c146386f..cea99986fb780c 100644 --- a/ngraph/frontend/onnx_import/src/core/model.cpp +++ b/ngraph/frontend/onnx_import/src/core/model.cpp @@ -7,6 +7,7 @@ #include "core/model.hpp" #include "ngraph/log.hpp" #include "ops_bridge.hpp" +#include "onnx_import/onnx_node.hpp" namespace ngraph { diff --git a/ngraph/frontend/onnx_import/src/core/node.cpp b/ngraph/frontend/onnx_import/src/core/node.cpp index 207982ef3b1c33..cf2fadc9cb44f6 100644 --- a/ngraph/frontend/onnx_import/src/core/node.cpp +++ b/ngraph/frontend/onnx_import/src/core/node.cpp @@ -39,6 +39,10 @@ namespace ngraph const std::string& description() const; const std::vector>& get_output_names() const; + std::vector get_input_names() const + { + return std::vector(m_node_proto->input().begin(), m_node_proto->input().end()); + } const std::string& output(int index) const; std::size_t get_outputs_size() const; @@ -200,6 +204,11 @@ namespace ngraph return m_pimpl->get_output_names(); } + std::vector Node::get_input_names() const + { + return m_pimpl->get_input_names(); + } + const std::string& Node::output(int index) const { return m_pimpl->output(index); } std::size_t Node::get_outputs_size() const { return m_pimpl->get_outputs_size(); } bool Node::has_attribute(const std::string& name) const diff --git a/ngraph/frontend/onnx_import/src/onnx.cpp b/ngraph/frontend/onnx_import/src/onnx.cpp index 09f6623611d4eb..99e4e2b46fe1fc 100644 --- a/ngraph/frontend/onnx_import/src/onnx.cpp +++ b/ngraph/frontend/onnx_import/src/onnx.cpp @@ -4,27 +4,32 @@ #include #include +#include #include +#include "core/graph.hpp" +#include "core/model.hpp" +#include "core/transform.hpp" + #include "ngraph/except.hpp" #include "onnx_common/parser.hpp" #include "onnx_import/onnx.hpp" #include "onnx_import/utils/onnx_internal.hpp" #include "ops_bridge.hpp" +#include namespace ngraph { namespace onnx_import { std::shared_ptr import_onnx_model(std::istream& stream, - const std::string& model_path) + const std::string& model_path, bool decode_only) { - ONNX_NAMESPACE::ModelProto model_proto{onnx_common::parse_from_istream(stream)}; - - return detail::import_onnx_model(model_proto, model_path); + auto model_proto = std::make_shared(onnx_common::parse_from_istream(stream)); + return detail::import_onnx_model(model_proto, model_path, decode_only); } - std::shared_ptr import_onnx_model(const std::string& file_path) + std::shared_ptr import_onnx_model(const std::string& file_path, bool decode_only) { std::ifstream model_stream{file_path, std::ios::in | std::ios::binary}; @@ -34,7 +39,7 @@ namespace ngraph file_path + ". Could not open the file."); }; - return import_onnx_model(model_stream, file_path); + return import_onnx_model(model_stream, file_path, decode_only); } std::set get_supported_operators(std::int64_t version, @@ -58,6 +63,51 @@ namespace ngraph op_name, version, domain == "ai.onnx" ? "" : domain); } + + /// Convert nGraph function with ONNXNode inclusions finally to regular opset + void convert_onnx_nodes (std::shared_ptr f) + { + auto ops = f->get_ordered_ops(); + for(auto node: ops) + { + if(auto raw_node = std::dynamic_pointer_cast(node)) + { + // Update cache to make sure that all inputs are properly registered + // based on proto names + + const auto& onnx_node = raw_node->get_onnx_node(); + auto input_names = onnx_node.get_input_names(); + assert(raw_node->get_input_size() == input_names.size()); + for(size_t i = 0; i < input_names.size(); ++i) + { + raw_node->get_onnx_graph()->update_node_input_cache(input_names[i], raw_node->get_input_source_output(i)); + } + // TODO: Configure it in a proper way -- don't modify graph properties (don't even keep decode_only as a graph property) + raw_node->get_onnx_graph()->set_decode_only(false); + OutputVector ng_nodes{onnx_node.get_ng_nodes()}; + // Filter out null outputs + while(!ng_nodes.empty()) + { + if(dynamic_cast(ng_nodes.back().get_node())) + { + ng_nodes.pop_back(); + } + else break; + } + std::cerr << "[ INFO ] Translating " << raw_node->get_onnx_node().op_type() << "\n"; + for(const auto& output: ng_nodes) + { + std::cerr << " output: " << output.get_partial_shape() << "\n"; + } + replace_node(raw_node, ng_nodes); + } + else + { + // Have to revalidate node because new intpus can affect shape/type propagation for already translated nodes + node->revalidate_and_infer_types(); + } + } + } } // namespace onnx_import } // namespace ngraph diff --git a/ngraph/frontend/onnx_import/src/onnx_node.cpp b/ngraph/frontend/onnx_import/src/onnx_node.cpp new file mode 100644 index 00000000000000..373c7f1e85ec61 --- /dev/null +++ b/ngraph/frontend/onnx_import/src/onnx_node.cpp @@ -0,0 +1,33 @@ +//***************************************************************************** +// 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 + +namespace ngraph +{ +namespace frontend +{ + +NGRAPH_RTTI_DEFINITION(ONNXNode, "__ONNXNode", 1); + +std::shared_ptr ONNXNode::clone_with_new_inputs(const OutputVector& inputs) const +{ + return std::make_shared(inputs, node); +} + + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/onnx_import/src/utils/onnx_internal.cpp b/ngraph/frontend/onnx_import/src/utils/onnx_internal.cpp index 00544c1fabfc2b..66348fafc015d7 100644 --- a/ngraph/frontend/onnx_import/src/utils/onnx_internal.cpp +++ b/ngraph/frontend/onnx_import/src/utils/onnx_internal.cpp @@ -8,6 +8,7 @@ #include "core/model.hpp" #include "core/transform.hpp" #include "onnx_import/utils/onnx_internal.hpp" +#include namespace ngraph { @@ -16,28 +17,39 @@ namespace ngraph namespace detail { std::shared_ptr - convert_to_ng_function(const ONNX_NAMESPACE::ModelProto& model_proto) + convert_to_ng_function(std::shared_ptr model_proto, bool decode_only) { - Model model{model_proto}; - Graph graph{model_proto.graph(), model}; + auto model = std::make_shared(*model_proto); + auto graph = std::make_shared(model_proto->graph(), *model, decode_only); auto function = std::make_shared( - graph.get_ng_outputs(), graph.get_ng_parameters(), graph.get_name()); + graph->get_ng_outputs(), graph->get_ng_parameters(), graph->get_name()); + + for(auto node: function->get_ops()) + { + if(auto raw_node = std::dynamic_pointer_cast(node)) + { + raw_node->set_onnx_graph(graph); + raw_node->set_onnx_model(model); + raw_node->set_onnx_model_proto(model_proto); + } + } + for (std::size_t i{0}; i < function->get_output_size(); ++i) { function->get_output_op(i)->set_friendly_name( - graph.get_outputs().at(i).get_name()); + graph->get_outputs().at(i).get_name()); } return function; } - std::shared_ptr import_onnx_model(ONNX_NAMESPACE::ModelProto& model_proto, - const std::string& model_path) + std::shared_ptr import_onnx_model(std::shared_ptr model_proto, + const std::string& model_path, bool decode_only) { - transform::expand_onnx_functions(model_proto); - transform::fixup_legacy_operators(model_proto); - transform::update_external_data_paths(model_proto, model_path); + transform::expand_onnx_functions(*model_proto); + transform::fixup_legacy_operators(*model_proto); + transform::update_external_data_paths(*model_proto, model_path); - return detail::convert_to_ng_function(model_proto); + return convert_to_ng_function(model_proto, decode_only); } } // namespace detail } // namespace onnx_import diff --git a/ngraph/frontend/paddlepaddle/CMakeLists.txt b/ngraph/frontend/paddlepaddle/CMakeLists.txt new file mode 100644 index 00000000000000..f41ccf36babffa --- /dev/null +++ b/ngraph/frontend/paddlepaddle/CMakeLists.txt @@ -0,0 +1,70 @@ +# ****************************************************************************** +# 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. +# ****************************************************************************** + +file(GLOB_RECURSE LIBRARY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) +file(GLOB_RECURSE LIBRARY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) +file(GLOB_RECURSE LIBRARY_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp) + +find_package(Protobuf REQUIRED IMPORTED) + +set(PADDLEPADDLE_FRONTEND_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Create named folders for the sources within the .vcproj +# Empty name lists them directly under the .vcproj + +source_group("src" FILES ${LIBRARY_SRC}) +source_group("include" FILES ${LIBRARY_HEADERS}) +source_group("public include" FILES ${LIBRARY_PUBLIC_HEADERS}) + +set(PROTOBUF_GENERATE_CPP_APPEND_PATH ON) +file(GLOB proto_files ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto) +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${proto_files}) + +include_directories(${Protobuf_INCLUDE_DIRS} ${PADDLEPADDLE_FRONTEND_INCLUDE_DIR}) + +# Create shared library +add_library(paddlepaddle_frontend SHARED ${LIBRARY_SRC} ${LIBRARY_HEADERS} ${LIBRARY_PUBLIC_HEADERS} ${PROTO_SRCS} ${PROTO_HDRS}) +add_library(ngraph::paddlepaddle_frontend ALIAS paddlepaddle_frontend) + +# TODO: fix relative include directory by moving +target_include_directories(paddlepaddle_frontend + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/../generic/include + ${CMAKE_CURRENT_BINARY_DIR}) + +if(COMMAND ie_add_vs_version_file) + ie_add_vs_version_file(NAME paddlepaddle_frontend + FILEDESCRIPTION "FrontEnd to load and convert PaddlePaddle file format") +endif() + +target_link_libraries(paddlepaddle_frontend PRIVATE ${Protobuf_LIBRARIES} PUBLIC ngraph) + +# TODO: Consider to remove the following block (inherited from onnx_import just in case). +if (CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$") + target_compile_options(paddlepaddle_frontend PRIVATE -Wno-undef -Wno-reserved-id-macro -Wno-switch-enum + -Wno-invalid-offsetof -Wno-shorten-64-to-32 -Wno-unused-macros -Wno-missing-variable-declarations + -Wno-unused-private-field -Wno-shadow -Wno-deprecated PUBLIC -Wno-undefined-func-template) +endif() + +install(TARGETS paddlepaddle_frontend EXPORT ngraphTargets + RUNTIME DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph + ARCHIVE DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph + LIBRARY DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph) + +if (NGRAPH_EXPORT_TARGETS_ENABLE) + export(TARGETS paddlepaddle_frontend NAMESPACE ngraph:: APPEND FILE "${NGRAPH_TARGETS_FILE}") +endif() diff --git a/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/frontend.hpp b/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/frontend.hpp new file mode 100644 index 00000000000000..3384a41e69a181 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/frontend.hpp @@ -0,0 +1,43 @@ +//***************************************************************************** +// 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 "model.hpp" + +namespace ngraph { +namespace frontend { + +class NGRAPH_API FrontEndPDPD : public FrontEnd +{ +public: + + FrontEndPDPD () + { + } + + virtual InputModel::Ptr loadFromFile (const std::string& path) const override + { + return std::make_shared(path); + } + + virtual std::shared_ptr convert (InputModel::Ptr model) const override; +}; + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/model.hpp b/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/model.hpp new file mode 100644 index 00000000000000..535888c5b6033e --- /dev/null +++ b/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/model.hpp @@ -0,0 +1,46 @@ +//***************************************************************************** +// 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 "place.hpp" + +namespace ngraph { +namespace frontend { + +class FrontEndPDPD; + +class NGRAPH_API InputModelPDPD : public InputModel +{ + // TODO: replace it by already deserialized proto hidden under some Impl class + // TODO: avoid using explicit format-dependent data stuctures here, hide it under some Impl class + + friend class FrontEndPDPD; + +public: + std::string path; + std::string model_file; + std::ifstream weights_stream; + bool weights_composed = false; + + InputModelPDPD (const std::string& _path) : path(_path) {} +}; + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/place.hpp b/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/place.hpp new file mode 100644 index 00000000000000..9455488fb53c69 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/include/paddlepaddle_frontend/place.hpp @@ -0,0 +1,30 @@ +//***************************************************************************** +// 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 frontend { + +class PlacePDPD : public Place +{ + // TODO +}; + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/paddlepaddle/src/decoder.cpp b/ngraph/frontend/paddlepaddle/src/decoder.cpp new file mode 100644 index 00000000000000..b3559f83ff8803 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/decoder.cpp @@ -0,0 +1,124 @@ +// Copyright (C) 2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "framework.pb.h" + +#include "decoder.hpp" + + +namespace ngraph { +namespace frontend { + +std::map TYPE_MAP{ + {proto::VarType_Type::VarType_Type_BOOL, ngraph::element::boolean}, + {proto::VarType_Type::VarType_Type_INT16, ngraph::element::i16}, + {proto::VarType_Type::VarType_Type_INT32, ngraph::element::i32}, + {proto::VarType_Type::VarType_Type_INT64, ngraph::element::i64}, + {proto::VarType_Type::VarType_Type_FP16, ngraph::element::f16}, + {proto::VarType_Type::VarType_Type_FP32, ngraph::element::f32}, + {proto::VarType_Type::VarType_Type_FP64, ngraph::element::f64}, + {proto::VarType_Type::VarType_Type_UINT8, ngraph::element::u8}, + {proto::VarType_Type::VarType_Type_INT8, ngraph::element::i8}, + {proto::VarType_Type::VarType_Type_BF16, ngraph::element::bf16} +}; + +std::vector DecoderPDPDProto::get_ints(const std::string& name, const std::vector& def) const +{ + std::cout << "Running get_ints" << std::endl; + std::vector attrs; + for (const auto &attr : op.attrs()) { + if (attr.name() == name) + attrs.push_back(attr); + } + if (attrs.size() == 0) { + return def; + } else if (attrs.size() > 1) { + // TODO: raise exception here + return def; + } else { + std::vector res; + std::copy(attrs[0].ints().begin(), attrs[0].ints().end(), std::back_inserter(res)); + return res; + } +} + +int DecoderPDPDProto::get_int(const std::string& name, int def) const +{ + std::vector attrs; + for (const auto &attr : op.attrs()) { + if (attr.name() == name) + attrs.push_back(attr); + } + if (attrs.size() == 0) { + return def; + } else if (attrs.size() > 1) { + // TODO: raise exception here + return def; + } else { + return attrs[0].i(); + } +} + +float DecoderPDPDProto::get_float(const std::string& name, float def) const +{ + std::vector attrs; + for (const auto &attr : op.attrs()) { + if (attr.name() == name) + attrs.push_back(attr); + } + if (attrs.size() == 0) { + return def; + } else if (attrs.size() > 1) { + // TODO: raise exception here + return def; + } else { + return attrs[0].f(); + } +} + +std::string DecoderPDPDProto::get_str(const std::string& name, const std::string& def) const +{ + std::vector attrs; + for (const auto &attr : op.attrs()) { + if (attr.name() == name) + attrs.push_back(attr); + } + if (attrs.size() == 0) { + return def; + } else if (attrs.size() > 1) { + // TODO: raise exception here + return def; + } else { + return attrs[0].s(); + } +} + +bool DecoderPDPDProto::get_bool(const std::string& name, bool def) const +{ + std::vector attrs; + for (const auto &attr : op.attrs()) { + if (attr.name() == name) + attrs.push_back(attr); + } + if (attrs.size() == 0) { + return def; + } else if (attrs.size() > 1) { + // TODO: raise exception here + return def; + } else { + return attrs[0].b(); + } +} + +} +} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/decoder.hpp b/ngraph/frontend/paddlepaddle/src/decoder.hpp new file mode 100644 index 00000000000000..f4075b586fd4df --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/decoder.hpp @@ -0,0 +1,66 @@ +//***************************************************************************** +// 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 +#include +#include +#include + +#include "framework.pb.h" + +#include "../include/paddlepaddle_frontend/frontend.hpp" + +#include +#include + +namespace ngraph { +namespace frontend { + +using namespace google; +using namespace paddle::framework; + +extern std::map TYPE_MAP; + +// TODO: Inherit from one of the ngraph classes +class AttributeNotFound : public std::exception +{}; + +class DecoderPDPDProto +{ + proto::OpDesc op; + +public: + + DecoderPDPDProto (proto::OpDesc _op) : op(_op) {} + + std::vector get_ints(const std::string& name, const std::vector& def = {}) const; + int get_int(const std::string& name, int def = 0) const; + float get_float(const std::string& name, float def = 0.) const; + std::string get_str(const std::string& name, const std::string& def = "") const; + bool get_bool (const std::string& name, bool def = false) const; + + // TODO: Further populate get_XXX methods on demand +}; + +} +} diff --git a/ngraph/frontend/paddlepaddle/src/frontend.cpp b/ngraph/frontend/paddlepaddle/src/frontend.cpp new file mode 100644 index 00000000000000..2dd37a4282ab26 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/frontend.cpp @@ -0,0 +1,256 @@ +//***************************************************************************** +// 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 +#include +#include + +#include "framework.pb.h" + +#include "../include/paddlepaddle_frontend/model.hpp" + +#include +#include + +#include "utility.hpp" +#include "decoder.hpp" +#include "node_context.hpp" +#include "op_table.hpp" + +#include + + +namespace ngraph { +namespace frontend { +namespace pdpd { + +std::shared_ptr +make_ng_node(std::map> &inputs, + std::map> &nodes, + const paddle::framework::proto::OpDesc &op, + const paddle::framework::proto::BlockDesc &block, + const std::map& CREATORS_MAP) { + std::cout << "Making node: " << op.type() << std::endl; + + MY_ASSERT(CREATORS_MAP.find(op.type()) != CREATORS_MAP.end(), "No creator found"); + std::map>> inputs_preproc; + for (const auto &item : inputs) { + inputs_preproc[item.first] = std::vector>(); + for (const auto &input_name : item.second) { + // TODO: refactor to not search every time + inputs_preproc[item.first].push_back(nodes[input_name]); + } + } + + // TODO: Temporary repacking data to fit new creator API based on OutputVector instead of direct + // TODO: nodes manipulation. + + NamedInputs named_inputs; + for(const auto& input: inputs_preproc) + { + for(auto node: input.second) + named_inputs[input.first].push_back(node); + } + + OutputVector outputs = CREATORS_MAP.at(op.type())(NodeContext(op, named_inputs)); + MY_ASSERT(outputs.size() == 1); + return outputs[0].get_node_shared_ptr(); +} + +std::shared_ptr read_tensor(const paddle::framework::proto::VarDesc& var, + std::shared_ptr model) +{ + std::cout << "Reading tensor " << var.name() << std::endl; + MY_ASSERT(var.type().type() == paddle::framework::proto::VarType::LOD_TENSOR); + auto tensor = var.type().lod_tensor().tensor(); + auto tensor_length = std::accumulate( + tensor.dims().cbegin(), tensor.dims().cend(), 1, std::multiplies()); + std::vector tensor_data(tensor_length, 0); + + std::ifstream is; + std::ifstream* stream_ptr; + if (model->weights_composed) { + stream_ptr = &model->weights_stream; + } else { + is = std::ifstream(model->path + "/" + var.name(), std::ios::in | std::ifstream::binary); + if (!is || !is.is_open()) + { + std::cout << "File not opened" << std::endl; + } + stream_ptr = &is; + } + std::vector leading_zeros(16, 0); + stream_ptr->read(&leading_zeros[0], 16); + uint32_t dims_len = 0; + stream_ptr->read(reinterpret_cast(&dims_len), 4); + std::vector dims_struct(dims_len, 0); + stream_ptr->read(&dims_struct[0], dims_len); + stream_ptr->read(reinterpret_cast(&tensor_data[0]), tensor_length * 4); + + auto shape = std::vector(tensor.dims().cbegin(), tensor.dims().cend()); + return ngraph::opset6::Constant::create( + ngraph::element::f32, ngraph::Shape(shape), tensor_data); +} + +bool endsWith(const std::string &str, const std::string &suffix) { + if (str.length() >= suffix.length()) { + return (0 == str.compare(str.length() - suffix.length(), suffix.length(), suffix)); + } + return false; +} + +std::shared_ptr + convert_model(std::shared_ptr model) +{ + std::cout << "Convert Model Start" << std::endl; + + paddle::framework::proto::ProgramDesc fw_model; + std::ifstream pb_stream(model->model_file, std::ios::binary); + std::cout << "Model Parsed: " << fw_model.ParseFromIstream(&pb_stream) << std::endl; + + std::map> nodes_dict; + ngraph::ParameterVector parameter_nodes; + ngraph::ResultVector result_nodes; + + std::cout << "Blocks number: " << fw_model.blocks().size() << std::endl; + const auto& global_block = fw_model.blocks()[0]; + // We need to read variables in sorted by name order. This is the order variables written in composed file. + std::map sorted_vars; + for (auto& var : global_block.vars()) + { + sorted_vars[var.name()] = var; + } + for (const auto& name_var : sorted_vars) + { + if (endsWith(name_var.first, "feed") || endsWith(name_var.first, "fetch")) + continue; + if (!name_var.second.persistable()) + continue; + nodes_dict[name_var.first] = read_tensor(name_var.second, model); + } + std::cout << "Reading consts finished" << std::endl; + + std::map CREATORS_MAP = get_supported_ops(); + + for (const auto& block : fw_model.blocks()) { + std::map vars_dict; + for (const auto &var : block.vars()) { + vars_dict[var.name()] = var.type(); + } + for (int i = 0; i < block.ops_size(); i++) { + std::cerr << "Observing index i = " << i << "\n"; + const auto &op = block.ops()[i]; + std::cerr << "Observing " << op.type() << "\n"; + std::map> outputs_dict; + for (const auto &output : op.outputs()) { + outputs_dict[output.parameter()] = output.arguments(); + std::cerr << output.parameter() << "\n"; + } + std::map> inputs_dict; + for (const auto &input : op.inputs()) { + inputs_dict[input.parameter()] = input.arguments(); + } + if (op.type() == "feed") { + auto layer_name = outputs_dict["Out"][0]; + std::cout << "Creating parameter: " << layer_name << std::endl; + auto var = vars_dict[layer_name]; + MY_ASSERT(var.type() == paddle::framework::proto::VarType::LOD_TENSOR); + auto tensor_desc = var.lod_tensor().tensor(); + auto dtype = tensor_desc.data_type(); + std::vector shape; + // set all -1 dims to 1 + // TODO: remove when input shape can be specified + for (auto dim : tensor_desc.dims()) { + if (dim >= 0) { + shape.push_back(dim); + } else { + shape.push_back(1); + } + } + auto param = std::make_shared(TYPE_MAP[dtype], + ngraph::Shape(shape)); + param->set_friendly_name(layer_name); + nodes_dict[layer_name] = param; + parameter_nodes.push_back(param); + std::cout << "Parameter created" << std::endl; + } else if (op.type() == "fetch") { + auto input_node = inputs_dict["X"][0]; + MY_ASSERT(nodes_dict.find(input_node) != nodes_dict.end()); + auto result = std::make_shared(nodes_dict[input_node]); + result->set_friendly_name(input_node); + result_nodes.push_back(result); + } else { + auto node = make_ng_node(inputs_dict, nodes_dict, op, block, CREATORS_MAP); + std::string layer_name; + for (const auto& outs : outputs_dict) { + for (const auto& fieldName : outs.second) { + layer_name += fieldName; + } + } + node->set_friendly_name(layer_name); + + std::cerr << "Named with " << node->get_friendly_name() << "\n"; + for (const auto &item : outputs_dict) { + MY_ASSERT(item.second.size() <= 1); + if (item.second.size() == 1) { + nodes_dict[item.second[0]] = node; + } + } + } + } + } + return std::make_shared(result_nodes, parameter_nodes); +} + +} + +std::shared_ptr ngraph::frontend::FrontEndPDPD::convert(InputModel::Ptr model) const { + std::cerr << "[ INFO ] PFrontEndPDPD::convert invoked\n"; + auto pdpd_model = std::dynamic_pointer_cast(model); + std::string ext = ".pdmodel"; + if (pdpd_model->path.length() >= ext.length() && + (0 == + pdpd_model->path.compare(pdpd_model->path.length() - ext.length(), ext.length(), ext))) + { + pdpd_model->weights_composed = true; + pdpd_model->model_file = pdpd_model->path; + auto weights_file = + pdpd_model->path.replace(pdpd_model->path.size() - ext.size(), ext.size(), ".pdiparams"); + pdpd_model->weights_stream = std::ifstream(weights_file, std::ios::binary); + if (!pdpd_model->weights_stream || !pdpd_model->weights_stream.is_open()) + { + std::cout << "File not opened" << std::endl; + } + } + else + { + pdpd_model->weights_composed = false; + pdpd_model->model_file = pdpd_model->path + "/__model__"; + } + auto f = pdpd::convert_model(pdpd_model); + std::cerr << "[ INFO ] Resulting nGraph function contains " << f->get_ops().size() << "\n"; + return f; +} + +} +} diff --git a/ngraph/frontend/paddlepaddle/src/node_context.hpp b/ngraph/frontend/paddlepaddle/src/node_context.hpp new file mode 100644 index 00000000000000..539b16049cb7d2 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/node_context.hpp @@ -0,0 +1,98 @@ +//***************************************************************************** +// 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 "decoder.hpp" +#include "utility.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { + +typedef std::map NamedInputs; + +/// Keep necessary data for a single node in the original FW graph to facilitate conversion process in the rules code. +class NodeContext +{ + const DecoderPDPDProto& node; + NamedInputs& name_map; + +public: + + NodeContext (const DecoderPDPDProto& _node, NamedInputs& _name_map) : node(_node), name_map(_name_map) {} + + /// Detects if there is at least one input attached with a given name + bool has_ng_input (const std::string& name) const + { + auto found = name_map.find(name); + if(found != name_map.end()) + return !found->second.empty(); + return false; + } + + size_t get_ng_input_size (const std::string& name) const { return name_map.at(name).size(); } + + /// Returns exactly one input with a given name; throws if there is no inputs or there are more than one input + Output get_ng_input (const std::string& name) const + { + MY_ASSERT(name_map.at(name).size() == 1); + return name_map.at(name).at(0); + } + + /// Returns all inputs with a given name + OutputVector get_ng_inputs (const std::string& name) const { return name_map.at(name); } + + template + T get_attribute (const std::string& name, const T& def = T()) const; + + template + bool has_attribute (const std::string& name) const + { + // TODO: Rework this hack + try { + get_attribute(name); + return true; + } + catch(const AttributeNotFound&) { + return false; + } + } +}; + +template <> +inline int32_t NodeContext::get_attribute (const std::string& name, const int32_t& def) const +{ return node.get_int(name, def); } + +template <> +inline float NodeContext::get_attribute (const std::string& name, const float& def) const +{ return node.get_float(name, def); } + +template <> +inline std::string NodeContext::get_attribute (const std::string& name, const std::string& def) const +{ return node.get_str(name, def); } + +template <> +inline std::vector NodeContext::get_attribute (const std::string& name, const std::vector& def) const +{ return node.get_ints(name, def); } + +template <> +inline bool NodeContext::get_attribute (const std::string& name, const bool& def) const +{ return node.get_bool(name, def); } + + +} +} +} diff --git a/ngraph/frontend/paddlepaddle/src/op/batch_norm.cpp b/ngraph/frontend/paddlepaddle/src/op/batch_norm.cpp new file mode 100644 index 00000000000000..33a5e356f8f2c2 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/batch_norm.cpp @@ -0,0 +1,35 @@ +//***************************************************************************** +// 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 "batch_norm.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector batch_norm (const NodeContext& node) { + auto data = node.get_ng_input("X"); + auto gamma = node.get_ng_input("Scale"); + auto beta = node.get_ng_input("Bias"); + auto mean = node.get_ng_input("Mean"); + auto variance = node.get_ng_input("Variance"); + return {std::make_shared( + data, gamma, beta, mean, variance, node.get_attribute("epsilon"))}; +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/batch_norm.hpp b/ngraph/frontend/paddlepaddle/src/op/batch_norm.hpp new file mode 100644 index 00000000000000..fbdf9aaa83f2f0 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/batch_norm.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector batch_norm (const NodeContext& node); + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/conv2d.cpp b/ngraph/frontend/paddlepaddle/src/op/conv2d.cpp new file mode 100644 index 00000000000000..d3dd4bd898a6a9 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/conv2d.cpp @@ -0,0 +1,41 @@ +//***************************************************************************** +// 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 "conv2d.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector conv2d (const NodeContext& node) { + auto data = node.get_ng_input("Input"); + auto filter = node.get_ng_input("Filter"); + // TODO: resolve padding according to spec + auto strides = node.get_attribute>("strides"); + auto paddings = node.get_attribute>("paddings"); + auto dilations = node.get_attribute>("dilations"); + return {std::make_shared( + data, + filter, + ngraph::Strides(strides.begin(), strides.end()), + ngraph::CoordinateDiff(paddings.begin(), paddings.end()), + ngraph::CoordinateDiff(paddings.begin(), paddings.end()), + ngraph::Strides(dilations.begin(), dilations.end()))}; +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/conv2d.hpp b/ngraph/frontend/paddlepaddle/src/op/conv2d.hpp new file mode 100644 index 00000000000000..331c57248e36c0 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/conv2d.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector conv2d (const NodeContext& node_context); + +}}}} diff --git a/ngraph/frontend/paddlepaddle/src/op/elementwise_add.cpp b/ngraph/frontend/paddlepaddle/src/op/elementwise_add.cpp new file mode 100644 index 00000000000000..4bf230f4914ed1 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/elementwise_add.cpp @@ -0,0 +1,32 @@ +//***************************************************************************** +// 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 "elementwise_add.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector elementwise_add (const NodeContext& node) { + auto x = node.get_ng_input("X"); + auto y = node.get_ng_input("Y"); + // TODO : resolve broadcast + return {std::make_shared(x, y)}; +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/elementwise_add.hpp b/ngraph/frontend/paddlepaddle/src/op/elementwise_add.hpp new file mode 100644 index 00000000000000..7c5a919942ced0 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/elementwise_add.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector elementwise_add (const NodeContext& node_context); + +}}}} diff --git a/ngraph/frontend/paddlepaddle/src/op/matmul.cpp b/ngraph/frontend/paddlepaddle/src/op/matmul.cpp new file mode 100644 index 00000000000000..dd32d4310882c8 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/matmul.cpp @@ -0,0 +1,40 @@ +//***************************************************************************** +// 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 "matmul.hpp" +#include "utility.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + + OutputVector matmul(const NodeContext& node) { + auto x = node.get_ng_input("X"); + auto y = node.get_ng_input("Y"); + auto alpha = node.get_attribute("alpha"); + auto transpose_a = node.get_attribute("transpose_a"); + auto transpose_b = node.get_attribute("transpose_b"); + auto mm = std::make_shared(x, y, transpose_a, transpose_b); + auto alpha_node = ngraph::opset6::Constant::create(ngraph::element::f32, {1}, {alpha}); + return {std::make_shared(mm, alpha_node)}; + } + +} // namespace op +} // namespace pdpd +} // namespace frontend +} // namespace ngraph \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/matmul.hpp b/ngraph/frontend/paddlepaddle/src/op/matmul.hpp new file mode 100644 index 00000000000000..4b6c6737cfe8f0 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/matmul.hpp @@ -0,0 +1,30 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector matmul(const NodeContext& node); + +} // namespace op +} // namespace pdpd +} // namespace frontend +} // namespace ngraph \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/mul.cpp b/ngraph/frontend/paddlepaddle/src/op/mul.cpp new file mode 100644 index 00000000000000..1efc7bf103cf22 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/mul.cpp @@ -0,0 +1,59 @@ +//***************************************************************************** +// 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 "utility.hpp" +#include "mul.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector mul (const NodeContext& node) { + auto x = node.get_ng_input("X"); + auto y = node.get_ng_input("Y"); + MY_ASSERT(x.get_partial_shape().rank().is_static()); + int64_t x_rank = x.get_partial_shape().rank().get_length(); + MY_ASSERT(y.get_partial_shape().rank().is_static() && + y.get_partial_shape().rank().get_length() == 2); + if (x_rank > 2) { + auto shape = std::make_shared(x); + int64_t x_num_col_dims = node.get_attribute("x_num_col_dims"); + auto axis = ngraph::opset6::Constant::create(ngraph::element::i64, {}, {0}); + auto split_lengths = ngraph::opset6::Constant::create(ngraph::element::i64, {2}, + {x_num_col_dims, x_rank - x_num_col_dims}); + auto split = std::make_shared(shape, axis, split_lengths); + auto f_dim_red_axis = ngraph::opset6::Constant::create(ngraph::element::i64, {}, {0}); + auto first_dim_reduce = std::make_shared(split->output(0), + f_dim_red_axis); + auto f_dim_shape = ngraph::opset6::Constant::create(ngraph::element::i64, {1}, {1}); + auto first_dim = std::make_shared(first_dim_reduce, f_dim_shape, false); + auto s_dim_red_axis = ngraph::opset6::Constant::create(ngraph::element::i64, {}, {0}); + auto second_dim_reduce = std::make_shared(split->output(1), + s_dim_red_axis); + auto s_dim_shape = ngraph::opset6::Constant::create(ngraph::element::i64, {1}, {1}); + auto second_dim = std::make_shared(second_dim_reduce, s_dim_shape, false); + auto out_shape = std::make_shared(ngraph::NodeVector{first_dim, second_dim}, + 0); + auto x_reshaped = std::make_shared(x, out_shape, false); + return {std::make_shared(x_reshaped, y)}; + } + return {std::make_shared(x, y)}; + +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/mul.hpp b/ngraph/frontend/paddlepaddle/src/op/mul.hpp new file mode 100644 index 00000000000000..2c93cf07ea76b1 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/mul.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector mul (const NodeContext& node); + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/pool2d.cpp b/ngraph/frontend/paddlepaddle/src/op/pool2d.cpp new file mode 100644 index 00000000000000..ca2c82d895ef9c --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/pool2d.cpp @@ -0,0 +1,59 @@ +//***************************************************************************** +// 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 "pool2d.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector pool2d (const NodeContext& node) { + // TODO : resolve padding according to spec + auto data = node.get_ng_input("X"); + auto pooling_type = node.get_attribute("pooling_type"); + auto global_pooling = node.get_attribute("global_pooling"); + auto adaptive = node.get_attribute("adaptive"); + auto kernel_shape = node.get_attribute>("ksize"); + if (pooling_type == "max" && !global_pooling) { + auto strides = node.get_attribute>("strides"); + auto paddings = node.get_attribute>("paddings"); + auto rounding_type = node.get_attribute("ceil_mode") + ? ngraph::op::RoundingType::CEIL + : ngraph::op::RoundingType::FLOOR; + return {std::make_shared( + data, + ngraph::Strides(strides.begin(), strides.end()), + ngraph::Shape(paddings.begin(), paddings.end()), + ngraph::Shape(paddings.begin(), paddings.end()), + ngraph::Shape(kernel_shape.begin(), kernel_shape.end()), + rounding_type)}; + } + else if (pooling_type == "avg" && + (global_pooling || adaptive && all_of(kernel_shape.begin(), + kernel_shape.end(), + [](int32_t s) { return s == 1; }))) + { + // TODO : resolve axes according to rank + auto axes = ngraph::opset6::Constant::create(ngraph::element::i64, {2}, {2, 3}); + return {std::make_shared(data, axes, true)}; + } else { + throw std::runtime_error("Unsupported pooling type"); + } +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/pool2d.hpp b/ngraph/frontend/paddlepaddle/src/op/pool2d.hpp new file mode 100644 index 00000000000000..935d29479ba51c --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/pool2d.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector pool2d (const NodeContext& node); + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/relu.cpp b/ngraph/frontend/paddlepaddle/src/op/relu.cpp new file mode 100644 index 00000000000000..aa838dd3d9aa65 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/relu.cpp @@ -0,0 +1,29 @@ +//***************************************************************************** +// 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 "relu.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector relu (const NodeContext& node) { + return {std::make_shared(node.get_ng_input("X"))}; +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/relu.hpp b/ngraph/frontend/paddlepaddle/src/op/relu.hpp new file mode 100644 index 00000000000000..012e13de529b6e --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/relu.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector relu (const NodeContext& node); + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/reshape2.cpp b/ngraph/frontend/paddlepaddle/src/op/reshape2.cpp new file mode 100644 index 00000000000000..92b7bc891f4506 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/reshape2.cpp @@ -0,0 +1,41 @@ +//***************************************************************************** +// 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 "reshape2.hpp" +#include "utility.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector reshape2(const NodeContext& node) { + auto data = node.get_ng_input("X"); + if (!node.has_ng_input("Shape") && !node.has_ng_input("ShapeTensor")) + { + auto shape_attr = node.get_attribute>("shape"); + auto shape_node = ngraph::opset6::Constant::create(ngraph::element::i32, {shape_attr.size()}, shape_attr); + return {std::make_shared(data, shape_node, true)}; + } else { + NOT_IMPLEMENTED("reshape2 with shape as input"); + } +} + +} // namespace op +} // namespace pdpd +} // namespace frontend +} // namespace ngraph \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/reshape2.hpp b/ngraph/frontend/paddlepaddle/src/op/reshape2.hpp new file mode 100644 index 00000000000000..2c8f0c49580d55 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/reshape2.hpp @@ -0,0 +1,30 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector reshape2(const NodeContext& node); + +} // namespace op +} // namespace pdpd +} // namespace frontend +} // namespace ngraph \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/scale.cpp b/ngraph/frontend/paddlepaddle/src/op/scale.cpp new file mode 100644 index 00000000000000..24a2c7c8bc90ed --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/scale.cpp @@ -0,0 +1,31 @@ +//***************************************************************************** +// 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 "scale.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector scale (const NodeContext& node) { + auto data = node.get_ng_input("X"); + auto scale = ngraph::opset6::Constant::create(ngraph::element::f32, {1}, {node.get_attribute("scale")}); + return {std::make_shared(data, scale)}; +} + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/scale.hpp b/ngraph/frontend/paddlepaddle/src/op/scale.hpp new file mode 100644 index 00000000000000..9c9c1c3b4332e9 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/scale.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector scale (const NodeContext& node); + +}}}} \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/softmax.cpp b/ngraph/frontend/paddlepaddle/src/op/softmax.cpp new file mode 100644 index 00000000000000..a11cc13a750c1e --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/softmax.cpp @@ -0,0 +1,39 @@ +//***************************************************************************** +// 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 "softmax.hpp" +#include "utility.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + OutputVector softmax(const NodeContext& node) { + auto data = node.get_ng_input("X"); + auto axis = node.get_attribute("axis"); + if (axis < 0) + { + MY_ASSERT(data.get_partial_shape().rank().is_static(), "Softmax rank must be static"); + auto data_rank = data.get_partial_shape().rank().get_length(); + axis = data_rank + axis; + } + return {std::make_shared(data, axis)}; + } +} // namespace op +} // namespace pdpd +} // namespace frontend +} // namespace ngraph \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op/softmax.hpp b/ngraph/frontend/paddlepaddle/src/op/softmax.hpp new file mode 100644 index 00000000000000..d6732ba5cf7a3c --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op/softmax.hpp @@ -0,0 +1,30 @@ +//***************************************************************************** +// 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 "node_context.hpp" + +namespace ngraph { +namespace frontend { +namespace pdpd { +namespace op { + +OutputVector softmax(const NodeContext& node); + +} // namespace op +} // namespace pdpd +} // namespace frontend +} // namespace ngraph \ No newline at end of file diff --git a/ngraph/frontend/paddlepaddle/src/op_table.cpp b/ngraph/frontend/paddlepaddle/src/op_table.cpp new file mode 100644 index 00000000000000..4b52d487909644 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op_table.cpp @@ -0,0 +1,50 @@ +//***************************************************************************** +// 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 "op/batch_norm.hpp" +#include "op/conv2d.hpp" +#include "op/elementwise_add.hpp" +#include "op/matmul.hpp" +#include "op/mul.hpp" +#include "op/pool2d.hpp" +#include "op/relu.hpp" +#include "op/reshape2.hpp" +#include "op/scale.hpp" +#include "op/softmax.hpp" + +#include "op_table.hpp" + + +namespace ngraph { +namespace frontend { +namespace pdpd { + +std::map get_supported_ops() { + return { + {"batch_norm", op::batch_norm}, + {"conv2d", op::conv2d}, + {"elementwise_add", op::elementwise_add}, + {"matmul", op::matmul}, + {"mul", op::mul}, + {"pool2d", op::pool2d}, + {"relu", op::relu}, + {"reshape2", op::reshape2}, + {"scale", op::scale}, + {"softmax", op::softmax} + }; +}; + +}}} diff --git a/ngraph/frontend/paddlepaddle/src/op_table.hpp b/ngraph/frontend/paddlepaddle/src/op_table.hpp new file mode 100644 index 00000000000000..94101d33676075 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/op_table.hpp @@ -0,0 +1,36 @@ +//***************************************************************************** +// 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 "node_context.hpp" + + +namespace ngraph { +namespace frontend { +namespace pdpd { + +using CreatorFunction = std::function; + +std::map get_supported_ops(); + +}}} diff --git a/ngraph/frontend/paddlepaddle/src/proto/framework.proto b/ngraph/frontend/paddlepaddle/src/proto/framework.proto new file mode 100644 index 00000000000000..baaecb55d06ee3 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/proto/framework.proto @@ -0,0 +1,205 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. + +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. */ + +syntax = "proto2"; +package paddle.framework.proto; + +// Any incompatible changes to ProgramDesc and its dependencies should +// raise the version defined version.h. +// +// Serailization and Deserialization codes should be modified in a way +// that supports old versions following the version and compatibility policy. +message Version { optional int64 version = 1 [ default = 0 ]; } + +enum AttrType { + INT = 0; + FLOAT = 1; + STRING = 2; + INTS = 3; + FLOATS = 4; + STRINGS = 5; + BOOLEAN = 6; + BOOLEANS = 7; + BLOCK = 8; + LONG = 9; + BLOCKS = 10; + LONGS = 11; +} + +// OpDesc describes an instance of a C++ framework::OperatorBase +// derived class type. +message OpDesc { + + message Attr { + required string name = 1; + required AttrType type = 2; + optional int32 i = 3; + optional float f = 4; + optional string s = 5; + repeated int32 ints = 6; + repeated float floats = 7; + repeated string strings = 8; + optional bool b = 10; + repeated bool bools = 11; + optional int32 block_idx = 12; + optional int64 l = 13; + repeated int32 blocks_idx = 14; + repeated int64 longs = 15; + }; + + message Var { + required string parameter = 1; + repeated string arguments = 2; + }; + + required string type = 3; + repeated Var inputs = 1; + repeated Var outputs = 2; + repeated Attr attrs = 4; + optional bool is_target = 5 [ default = false ]; +}; + +// OpProto describes a C++ framework::OperatorBase derived class. +message OpProto { + + // VarProto describes the C++ type framework::Variable. + message Var { + required string name = 1; + required string comment = 2; + + optional bool duplicable = 3 [ default = false ]; + optional bool intermediate = 4 [ default = false ]; + optional bool dispensable = 5 [ default = false ]; + } + + // AttrProto describes the C++ type Attribute. + message Attr { + required string name = 1; + required AttrType type = 2; + required string comment = 3; + // If that attribute is generated, it means the Paddle third + // language binding has responsibility to fill that + // attribute. End-User should not set that attribute. + optional bool generated = 4 [ default = false ]; + } + + required string type = 1; + repeated Var inputs = 2; + repeated Var outputs = 3; + repeated Attr attrs = 4; + required string comment = 5; +} + +message VarType { + enum Type { + // Pod Types + BOOL = 0; + INT16 = 1; + INT32 = 2; + INT64 = 3; + FP16 = 4; + FP32 = 5; + FP64 = 6; + // Tensor is used in C++. + SIZE_T = 19; + UINT8 = 20; + INT8 = 21; + BF16 = 22; + COMPLEX64 = 23; + COMPLEX128 = 24; + + // Other types that may need additional descriptions + LOD_TENSOR = 7; + SELECTED_ROWS = 8; + FEED_MINIBATCH = 9; + FETCH_LIST = 10; + STEP_SCOPES = 11; + LOD_RANK_TABLE = 12; + LOD_TENSOR_ARRAY = 13; + PLACE_LIST = 14; + READER = 15; + // Any runtime decided variable type is raw + // raw variables should manage their own allocations + // in operators like nccl_op + RAW = 17; + TUPLE = 18; + } + + required Type type = 1; + + message TensorDesc { + // Should only be PODType. Is enforced in C++ + required Type data_type = 1; + repeated int64 dims = 2; // [UNK, 640, 480] is saved as [-1, 640, 480] + } + optional TensorDesc selected_rows = 2; + + message LoDTensorDesc { + required TensorDesc tensor = 1; + optional int32 lod_level = 2 [ default = 0 ]; + } + optional LoDTensorDesc lod_tensor = 3; + + message LoDTensorArrayDesc { + required TensorDesc tensor = 1; + optional int32 lod_level = 2 [ default = 0 ]; + } + optional LoDTensorArrayDesc tensor_array = 4; + + message ReaderDesc { repeated LoDTensorDesc lod_tensor = 1; } + optional ReaderDesc reader = 5; + + message Tuple { repeated Type element_type = 1; } + optional Tuple tuple = 7; +} + +message VarDesc { + required string name = 1; + required VarType type = 2; + optional bool persistable = 3 [ default = false ]; + // True if the variable is an input data and + // have to check the feed data shape and dtype + optional bool need_check_feed = 4 [ default = false ]; +} + +message BlockDesc { + required int32 idx = 1; + required int32 parent_idx = 2; + repeated VarDesc vars = 3; + repeated OpDesc ops = 4; + optional int32 forward_block_idx = 5 [ default = -1 ]; +} + +// In some cases, Paddle may perform operator definition iterations, +// and the operator uses OpVersionMap for compatibility testing. +message OpVersion { required int32 version = 1; } +message OpVersionMap { + message OpVersionPair { + required string op_name = 1; + required OpVersion op_version = 2; + } + repeated OpVersionPair pair = 1; +} + +// Please refer to +// https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/program.md +// for more details. +// TODO(panyx0718): A model can have multiple programs. Need a +// way to distinguish them. Maybe ID or name? +message ProgramDesc { + reserved 2, 3; // For backward compatibility. + repeated BlockDesc blocks = 1; + optional Version version = 4; + optional OpVersionMap op_version_map = 5; +} diff --git a/ngraph/frontend/paddlepaddle/src/utility.hpp b/ngraph/frontend/paddlepaddle/src/utility.hpp new file mode 100644 index 00000000000000..8c16273a7d3c80 --- /dev/null +++ b/ngraph/frontend/paddlepaddle/src/utility.hpp @@ -0,0 +1,36 @@ +//***************************************************************************** +// 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 + +namespace ngraph { +namespace frontend { + +inline void MY_ASSERT(bool ex, const std::string& msg = "Unspecified error.") { + if (!ex) throw std::runtime_error(msg); +} + +inline void NOT_IMPLEMENTED(const std::string& name = "Unspecified") +{ + throw std::runtime_error(name + " is not implemented"); +} + +} // namespace frontend +} // namespace ngraph diff --git a/ngraph/frontend/tensorflow/CMakeLists.txt b/ngraph/frontend/tensorflow/CMakeLists.txt new file mode 100644 index 00000000000000..be9b3a163fd37c --- /dev/null +++ b/ngraph/frontend/tensorflow/CMakeLists.txt @@ -0,0 +1,66 @@ +# ****************************************************************************** +# 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. +# ****************************************************************************** + +file(GLOB_RECURSE LIBRARY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) +file(GLOB_RECURSE LIBRARY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h) +file(GLOB_RECURSE LIBRARY_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp) + +find_package(Protobuf REQUIRED IMPORTED) +include_directories(${Protobuf_INCLUDE_DIRS}) + +set(TENSORFLOW_FRONTEND_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Create named folders for the sources within the .vcproj +# Empty name lists them directly under the .vcproj + +source_group("src" FILES ${LIBRARY_SRC}) +source_group("include" FILES ${LIBRARY_HEADERS}) +source_group("public include" FILES ${LIBRARY_PUBLIC_HEADERS}) + +set(PROTOBUF_GENERATE_CPP_APPEND_PATH ON) +file(GLOB proto_files ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto) +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${proto_files}) + +include_directories(${Protobuf_INCLUDE_DIRS}) + +# Create shared library +add_library(tensorflow_frontend SHARED ${LIBRARY_SRC} ${LIBRARY_HEADERS} ${LIBRARY_PUBLIC_HEADERS} ${PROTO_SRCS} ${PROTO_HDRS}) +add_library(ngraph::tensorflow_frontend ALIAS tensorflow_frontend) + +target_include_directories(tensorflow_frontend PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../generic/include ${CMAKE_CURRENT_BINARY_DIR}) + +if(COMMAND ie_add_vs_version_file) + ie_add_vs_version_file(NAME tensorflow_frontend + FILEDESCRIPTION "FrontEnd to load and convert Tensorflow file format") +endif() + +target_link_libraries(tensorflow_frontend PRIVATE ${Protobuf_LIBRARIES} PUBLIC ngraph) + +# TODO: Consider to remove the following block (inherited from onnx_import just in case). +if (CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$") + target_compile_options(tensorflow_frontend PRIVATE -Wno-undef -Wno-reserved-id-macro -Wno-switch-enum + -Wno-invalid-offsetof -Wno-shorten-64-to-32 -Wno-unused-macros -Wno-missing-variable-declarations + -Wno-unused-private-field -Wno-shadow -Wno-deprecated PUBLIC -Wno-undefined-func-template) +endif() + +install(TARGETS tensorflow_frontend EXPORT ngraphTargets + RUNTIME DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph + ARCHIVE DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph + LIBRARY DESTINATION ${NGRAPH_INSTALL_LIB} COMPONENT ngraph) + +if (NGRAPH_EXPORT_TARGETS_ENABLE) + export(TARGETS tensorflow_frontend NAMESPACE ngraph:: APPEND FILE "${NGRAPH_TARGETS_FILE}") +endif() diff --git a/ngraph/frontend/tensorflow/include/tensorflow_frontend/tensorflow.hpp b/ngraph/frontend/tensorflow/include/tensorflow_frontend/tensorflow.hpp new file mode 100644 index 00000000000000..33e7e06c32e983 --- /dev/null +++ b/ngraph/frontend/tensorflow/include/tensorflow_frontend/tensorflow.hpp @@ -0,0 +1,75 @@ +//***************************************************************************** +// 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 + +// TODO: include it by just frontend_manager.hpp without path +//#include "../../include/frontend_manager/frontend_manager.hpp" +#include "frontend_manager/frontend_manager.hpp" + +namespace tensorflow { class GraphDef; } + +namespace ngraph +{ + namespace frontend + { + class PlaceTensorflow : public Place + { + public: + + std::string name; + enum Kind { PORT_INPUT, PORT_OUTPUT, TENSOR, OP } kind; + size_t port; + + PlaceTensorflow (const std::string& _name, Kind _kind = OP, size_t _port = 0) : name(_name), kind(_kind), port(_port) {} + }; + + class NGRAPH_API InputModelTensorflow : public InputModel + { + public: + + std::shared_ptr graph_def; + std::string path; + + // TODO: map from PlaceTensorflow, not from name string + std::map partialShapes; + + InputModelTensorflow (const std::string& _path); + + std::vector getInputs () const override; + + void setPartialShape (Place::Ptr place, const ngraph::PartialShape& pshape) override; + }; + + class NGRAPH_API FrontEndTensorflow : public FrontEnd + { + public: + + FrontEndTensorflow () + { + } + + virtual InputModel::Ptr loadFromFile (const std::string& path) const override + { + return std::make_shared(path); + } + + virtual std::shared_ptr convert (InputModel::Ptr model) const override; + }; + + } // namespace frontend + +} // namespace ngraph diff --git a/ngraph/frontend/tensorflow/src/default_opset.h b/ngraph/frontend/tensorflow/src/default_opset.h new file mode 100644 index 00000000000000..bcd360e2261ef5 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/default_opset.h @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright 2017-2020 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. + *******************************************************************************/ + +#ifndef NGRAPH_TF_BRIDGE_DEFAULT_OPSET_H_ +#define NGRAPH_TF_BRIDGE_DEFAULT_OPSET_H_ +#pragma once + +#include "ngraph/opsets/opset5.hpp" + +namespace tensorflow { +namespace ngraph_bridge { + +namespace opset = ngraph::opset5; +namespace default_opset = ngraph::opset5; + +#define NGRAPH_TF_FE_NOT_IMPLEMENTED { std::cerr << "[ NOT IMPLEMENTED ] source: " << __FILE__ << ":" << __LINE__ << "\n"; throw "NOT IMPLEMENTED"; } + +} // namespace ngraph_bridge +} // namespace tensorflow + +#endif \ No newline at end of file diff --git a/ngraph/frontend/tensorflow/src/ngraph_builder.cpp b/ngraph/frontend/tensorflow/src/ngraph_builder.cpp new file mode 100644 index 00000000000000..c0fe882c0ea622 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/ngraph_builder.cpp @@ -0,0 +1,3216 @@ +/******************************************************************************* + * Copyright 2017-2020 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 "graph.pb.h" +#include "tensor.pb.h" + +#include "ngraph/op/util/logical_reduction.hpp" +#include "ngraph/pass/constant_folding.hpp" +#include "ngraph/pass/manager.hpp" +#include "ngraph/pass/pass_config.hpp" +#include "ngraph/slice_plan.hpp" + +#include "ngraph_builder.h" +#include "ngraph_conversions.h" +#include "default_opset.h" + + +using namespace std; +namespace ng = ngraph; + +namespace tensorflow { + +namespace ngraph_bridge { + +static bool VecStrCmp(const std::vector& a, + const std::vector& b) { + return a == b; +} + +static Status ValidateInputCount(const TFNodeDecoder* op, int32_t count) { + if (op->num_inputs() != count) { + std::ostringstream buf; + buf << "\"" << op->name() << "\" requires " << count << + " input(s), got " << op->num_inputs() << + " instead"; + return errors::InvalidArgument(buf.str()); + } + return Status::OK(); +} + +static Status ValidateInputCountMin(const TFNodeDecoder* op, int32_t count) { + if (op->num_inputs() < count) { + std::ostringstream buf; + buf << "\"" << op->name() << "\" requires at least " << + count << " input(s), got " << op->num_inputs() << + " instead"; + return errors::InvalidArgument(buf.str()); + } + return Status::OK(); +} + +// Check to make sure the axis dimension for reduction are in within range. +// Returns error if axis is out of range. Otherwise returns Status::OK(). +static Status CheckAxisDimInRange(std::vector axes, size_t rank) { + for (auto i : axes) { + if (i < (int)-rank || i >= (int)rank) { + std::ostringstream buf; + buf << "Axis Dimension is out of range. Got " << i << + ", should be in range [-" << rank << ", " << + rank << ")"; + return errors::InvalidArgument(buf.str()); + } + } + return Status::OK(); +} + +// +// Helper for storing ops in ng_op_map. +// For most of the cases, op would have one output so +// vector ng_op_map[op_name] would contain one element. +// +// If storing more than one output_nodes, make sure it's in +// the same order as tensorflow would do that. +// +// Parameters: +// Builder::OpMap& ng_op_map - The TF-to-nGraph op map. +// std::string op_name - Name of the op. +// +// ng::Output output_node - ng::Node to store +// + +static void SaveNgOp(Builder::OpMap& ng_op_map, const std::string& op_name, + ng::Output output_node) { + // no need to try-catch, map[key] will create vector object + // if not exists + ng_op_map[op_name].push_back(output_node); +} + +void Builder::SetTracingInfo(const std::string& op_name, + const ng::Output ng_node) { + auto node = ng_node.get_node_shared_ptr(); + node->set_friendly_name(op_name + "/" + node->get_name()); + node->add_provenance_tag(op_name); +#if 0 + if (api::IsLoggingPlacement()) { + cout << "TF_to_NG: " << op_name << " --> " << node << "\n"; + } +#endif +} + +template +ng::Output ConstructNgNode(const std::string& op_name, + TArg&&... Args) { + auto ng_node = std::make_shared(std::forward(Args)...); + Builder::SetTracingInfo(op_name, ng_node); + return ng_node; +} + +// Helper for fetching correct input node from ng_op_map. +// Handles edge checking to make sure correct input node is +// fetched. +// +// Reduces some boilerplate code (incorrect from now) like this: +// +// TFNodeDecoder* tf_input; +// TF_RETURN_IF_ERROR(op->input_node(0, &tf_input)); +// +// ng::Output ng_input; +// try { +// ng_input = ng_op_map.at(tf_input->name()); +// } catch (const std::out_of_range&) { +// return errors::NotFound(tf_input->name(), +// " is not found in the ng_op_map"); +// } +// +// Into 2 lines: +// +// ng::Output ng_input; +// TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, &ng_input)) +// +// +// +// Parameters: +// Builder::OpMap& ng_op_map - The TF-to-nGraph op map. +// TFNodeDecoder* op - TF op being translated. +// input_idx - index of input +// +// ng::Output *result - ng::Node pointer where result +// will be written +// +// + +static Status GetInputNode(const Builder::OpMap& ng_op_map, const TFNodeDecoder* op, + size_t input_idx, ng::Output& result) { + // Stub + #if 0 + // input op may have resulted in more than one ng::Node (eg. Split) + // we need to look at Edge to check index of the input op + std::vector edges; + TF_RETURN_IF_ERROR(op->input_edges(&edges)); + size_t src_output_idx; + try { + src_output_idx = edges.at(input_idx)->src_output(); + } catch (const out_of_range&) { + return Status(error::NOT_FOUND, "Edge not found"); + } + +#endif + + const TFNodeDecoder* tf_input; + size_t src_output_idx; + TF_RETURN_IF_ERROR(op->input_node(input_idx, &tf_input, &src_output_idx)); + std::vector> ng_op; + try { + ng_op = ng_op_map.at(tf_input->name()); + } catch (const out_of_range&) { + return Status(string("Ngraph op not found for ") + tf_input->name()); + } + try { + result = ng_op.at(src_output_idx); + } catch (const out_of_range&) { + return Status(string("Input node not found at index ") + + to_string(src_output_idx)); + } + return Status::OK(); + + + + NGRAPH_TF_FE_NOT_IMPLEMENTED +} + +namespace detail { +static Status GetInputNodes(const Builder::OpMap&, const TFNodeDecoder*, size_t) { + return Status::OK(); +} + +template +static Status GetInputNodes(const Builder::OpMap& ng_op_map, const TFNodeDecoder* op, + size_t index, ng::Output& result, + Arguments&... remaining) { + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, index, result)); + return GetInputNodes(ng_op_map, op, index + 1, remaining...); +} +} // namespace detail + +template +static Status GetInputNodes(const Builder::OpMap& ng_op_map, const TFNodeDecoder* op, + Arguments&... remaining) { + constexpr size_t args_len = sizeof...(Arguments); + TF_RETURN_IF_ERROR(ValidateInputCount(op, args_len)); + return detail::GetInputNodes(ng_op_map, op, 0, remaining...); +} + +static Status GetStaticNodeTensor( + const TFNodeDecoder* node, const std::vector& static_input_map, + TensorWrapper* result) { + if (node->IsArg()) { + int arg_index; + TF_RETURN_IF_ERROR(GetNodeAttr(node->attrs(), "index", &arg_index)); + const TensorWrapper* source_tensor = static_input_map[arg_index]; + if (source_tensor == nullptr) { + return errors::Internal( + "GetStaticNodeTensor called on _Arg but input tensor is missing from " + "static input map"); + } + *result = *source_tensor; + return Status::OK(); + } else if (node->type_string() == "Const") { + if (GetNodeAttr(node->attrs(), "value", &result).status != 0) { + return errors::Internal( + "GetStaticNodeTensor: Const tensor proto parsing failed"); + } + return Status::OK(); + } else { + return errors::Internal("GetStaticNodeTensor called on node with type " + + node->type_string() + "; _Arg or Const expected"); + } +} + +template +static void ConvertTensorDataToVector(const TensorWrapper& tensor, + std::vector* vector) { + const Ttensor* data = tensor.flat().data(); + vector->resize(tensor.NumElements()); + for (int64_t i = 0; i < tensor.NumElements(); i++) { + (*vector)[i] = Tvector(data[i]); + } +} + +template +static Status TensorDataToVector(const TensorWrapper& tensor, std::vector* vector) { +// stub + #if 0 + DataType dt = tensor.dtype(); + + // If dt and T match, we can just copy. + if (dt == DataTypeToEnum::value) { + *vector = std::vector(tensor.flat().data(), + tensor.flat().data() + tensor.NumElements()); + } + // Else we have to convert. + else { + switch (dt) { + case DT_FLOAT: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_DOUBLE: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_INT8: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_INT16: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_INT32: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_INT64: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_UINT8: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_UINT16: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_UINT32: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_UINT64: + ConvertTensorDataToVector(tensor, vector); + break; + case DT_BOOL: + ConvertTensorDataToVector(tensor, vector); + break; + default: + return errors::Internal("TensorDataToVector: tensor has element type ", + DataType_Name(dt), ", vector has type ", + DataType_Name(DataTypeToEnum::value), + "; don't know how to convert"); + } + } + return Status::OK(); + #endif + + NGRAPH_TF_FE_NOT_IMPLEMENTED +} + +template +static Status GetStaticInputVector( + Builder::OpMap& ng_op_map, + const TFNodeDecoder* op, int64_t input_index, + const std::vector& static_input_map, + std::vector* vector) { + ng::Output ng_input; + GetInputNode(ng_op_map, op, input_index, ng_input); + if(auto constant = std::dynamic_pointer_cast(ng_input.get_node_shared_ptr())) + { + *vector = constant->cast_vector(); + return Status::OK(); + } + + NGRAPH_TF_FE_NOT_IMPLEMENTED; +/* + TFNodeDecoder* input_node; + TF_RETURN_IF_ERROR(op->input_node(input_index, &input_node)); + TensorWrapper* input_tensor; + TF_RETURN_IF_ERROR( + GetStaticNodeTensor(input_node, static_input_map, &input_tensor)); + TF_RETURN_IF_ERROR(TensorDataToVector(input_tensor, vector)); + return Status::OK();*/ +} + +#if 0 +template +static Status GetStaticInputVector( + const TFNodeDecoder* op, int64_t input_index, + const std::vector& static_input_map, + std::vector* vector) { + TFNodeDecoder* input_node; + TF_RETURN_IF_ERROR(op->input_node(input_index, &input_node)); + TensorWrapper* input_tensor; + TF_RETURN_IF_ERROR( + GetStaticNodeTensor(input_node, static_input_map, &input_tensor)); + TF_RETURN_IF_ERROR(TensorDataToVector(input_tensor, vector)); + return Status::OK(); +} + +static Status GetStaticInputNode( + const TFNodeDecoder* op, int64_t input_index, + const std::vector& static_input_map, DataType dt, + ng::Output& node_) { + ng::element::Type type; + TF_RETURN_IF_ERROR(TFDataTypeToNGraphElementType(dt, &type)); + switch (dt) { + case DataType::DT_FLOAT: { + std::vector vec_float; + TF_RETURN_IF_ERROR( + GetStaticInputVector(op, input_index, static_input_map, &vec_float)); + node_ = ConstructNgNode(op->name(), type, ng::Shape{}, + vec_float[0]); + } break; + case DataType::DT_DOUBLE: { + std::vector vec_double; + TF_RETURN_IF_ERROR( + GetStaticInputVector(op, input_index, static_input_map, &vec_double)); + node_ = ConstructNgNode(op->name(), type, ng::Shape{}, + vec_double[0]); + } break; + case DataType::DT_INT32: { + std::vector vec_i32; + TF_RETURN_IF_ERROR( + GetStaticInputVector(op, input_index, static_input_map, &vec_i32)); + node_ = ConstructNgNode(op->name(), type, ng::Shape{}, + vec_i32[0]); + } break; + case DataType::DT_INT64: { + std::vector vec_i64; + TF_RETURN_IF_ERROR( + GetStaticInputVector(op, input_index, static_input_map, &vec_i64)); + node_ = ConstructNgNode(op->name(), type, ng::Shape{}, + vec_i64[0]); + } break; + default: + return errors::Internal("GetStaticInputNode: TF data type " + + DataType_Name(dt) + " not supported."); + break; + } + return Status::OK(); +} +#endif + +// Taken from: tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +// Extract values from a Const op to `values`. Returns true if succeeds. +// +// Modified with an extra `VecT` parameter to handle the case where the type +// in the vector does not match TensorFlow's notion of what the C++ type +// should be (e.g. when T is `bool`, we actually need a vector of `char` for +// compatibility with nGraph). +template +static Status ValuesFromConstNode(const TFNodeDecoder* node, + ngraph::Shape* const_tensor_shape, + std::vector* values) { +#if 1 + + if (node->op() != "Const") { + return errors::InvalidArgument("TFNodeDecoder not a Const"); + } + DataType dt; + node->getAttrValue("dtype", &dt); + + + /* + if (dt != DataTypeToEnum::value) { + std::stringstream ss; + ss << "Invalid data type defined for Const. Defined: " + << node.attr().at("dtype").type(); + return errors::InvalidArgument(ss.str()); + } + */ + + // TensorWrapper represents the content of the tensor in either _val or + // tensor_content. + TensorWrapper* tensor; + node->getAttrValue("value", &tensor); + //typename checkpoint::SaveTypeTraits::RepeatedField* tensor_values = + // checkpoint::MutableTensorProtoData(const_cast(&tensor)); + + const TensorShapeProto& shape = tensor->tensor_def->tensor_shape(); + ngraph::PartialShape pshape; + TFTensorShapeToNGraphShape(shape, &pshape); + *const_tensor_shape = pshape.get_shape(); + if(pshape.is_dynamic()) + NGRAPH_TF_FE_NOT_IMPLEMENTED; + auto tensor_content = tensor->tensor_def->tensor_content(); + std::vector tensor_values_plain(tensor_content.begin(), tensor_content.end()); + const T* tensor_values = reinterpret_cast(tensor_values_plain.data()); + + if (!tensor_values_plain.empty() && tensor->tensor_def->has_tensor_shape()) { + // When tensor_shape is set, theoretically the representation of the data + // could be compressed. So, before copying values to the returned vector, + // make sure no compression happens. + //if (shape.dim_size() == 1 && shape.dim(0).size() == tensor_values_plain.size()/sizeof(T)) { + values->insert(values->end(), tensor_values, + tensor_values + tensor_values_plain.size()/sizeof(T)); + return Status::OK(); + //} + } + + const auto tensor_content_size = tensor->tensor_def->tensor_content().size(); + if(tensor_content_size % sizeof(VecT)) { + std::cerr << "[ ERROR ] tensor_content_size (" << tensor_content_size + << ") is not a multiple of " << sizeof(VecT); + } + + // If tensor_content_size is zero, we'll have to take the values from + // int_val, float_val, etc. + if (tensor_content_size == 0) { + int64_t n_elements = 1; + for (auto i = 0; i < shape.dim_size(); i++) { + if (shape.dim(i).size() < 0) { + return errors::InvalidArgument( + "Const node has empty tensor and an unknown dimension size"); + } + n_elements *= shape.dim(i).size(); + } + values->resize(n_elements); + + auto val_lastsaved = (T)0; // cast + + for (auto i = 0; i < n_elements; i++) { + auto& tensor_proto = *tensor->tensor_def; + int64_t val_size = 0; + auto val_i = (T)0; // cast + switch (dt) { + // TODO(amprocte/NGRAPH-2502): there are more element types to support + // here + case DT_INT32: + val_size = tensor_proto.int_val_size(); + if (val_size > 0) val_i = tensor_proto.int_val()[i]; + break; + case DT_INT64: + val_size = tensor_proto.int64_val_size(); + if (val_size > 0) val_i = tensor_proto.int64_val()[i]; + break; + case DT_FLOAT: + val_size = tensor_proto.float_val_size(); + if (val_size > 0) val_i = tensor_proto.float_val()[i]; + break; + case DT_BOOL: + val_size = tensor_proto.bool_val_size(); + if (val_size > 0) val_i = tensor_proto.bool_val()[i]; + break; + case DT_DOUBLE: + val_size = tensor_proto.double_val_size(); + if (val_size > 0) val_i = tensor_proto.double_val()[i]; + break; + default: + NGRAPH_VLOG(0) + << "Const node has empty tensor_proto and we don't know how to " + "handle this element type"; + NGRAPH_VLOG(0) << node->DebugString(); + NGRAPH_VLOG(0) << shape.DebugString(); + return errors::Unimplemented("Encountered unknown element type " + + DataType_Name(dt) + + " on an empty tensor_proto"); + } + if (val_size == 0) { + return errors::InvalidArgument("Empty values vector"); + } else if (i < val_size) { + (*values)[i] = val_i; + val_lastsaved = val_i; + } else { + (*values)[i] = val_lastsaved; + } + } + } else { + + return Status::OK(); + //values->resize(tensor_content_size / sizeof(VecT)); + //port::CopyToArray(tensor.tensor_content(), + // reinterpret_cast(values->data())); + } + + return Status::OK(); +#endif + + +} + +// Helper for Builder::TranslateGraph ("Const" op) +template +static Status MakeConstOp(const TFNodeDecoder* op, ng::element::Type et, + ng::Output& ng_node) { + vector const_values; + ngraph::Shape ng_shape; + + TF_RETURN_IF_ERROR( + (ValuesFromConstNode(op, &ng_shape, &const_values))); + + ng_node = + ConstructNgNode(op->name(), et, ng_shape, const_values); + return Status::OK(); +} + +const Builder::ConstMap& Builder::TF_NGRAPH_CONST_MAP() { + static const Builder::ConstMap the_map = { + {DataType::DT_FLOAT, make_pair(MakeConstOp, ng::element::f32)}, + {DataType::DT_DOUBLE, make_pair(MakeConstOp, ng::element::f64)}, + {DataType::DT_INT8, make_pair(MakeConstOp, ng::element::i8)}, + {DataType::DT_INT16, make_pair(MakeConstOp, ng::element::i16)}, +#if 0 + {DataType::DT_QINT8, make_pair(MakeConstOp, ng::element::i8)}, + {DataType::DT_QUINT8, make_pair(MakeConstOp, ng::element::u8)}, + {DataType::DT_QUINT16, make_pair(MakeConstOp, ng::element::u16)}, +#endif + {DataType::DT_INT32, make_pair(MakeConstOp, ng::element::i32)}, + {DataType::DT_INT64, make_pair(MakeConstOp, ng::element::i64)}, + {DataType::DT_UINT8, make_pair(MakeConstOp, ng::element::u8)}, + {DataType::DT_UINT16, make_pair(MakeConstOp, ng::element::u16)}, + {DataType::DT_BOOL, + make_pair(MakeConstOp, ng::element::boolean)}}; + return the_map; +} + +// Helper function to translate a unary op. +// +// Parameters: +// +// TFNodeDecoder* op - TF op being translated. Must have one input. +// const std::vector& static_input_map +// - the static input map +// Builder::OpMap& ng_op_map - The TF-to-nGraph op map. +// +// std::function(ng::Output> +// create_unary_op - Function to construct the graph implementing +// the unary op, given the input to the unop +// as an argument. +// +// Example usage: +// +// if (n->type_string == "Square") { +// TF_RETURN_IF_ERROR(TranslateUnaryOp(n, static_input_map, ng_op_map, +// [] (ng::Output n) { +// return +// (ng::Output(n,n)); +// }); +// } +static Status TranslateUnaryOp( + const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map, + std::function(ng::Output)> create_unary_op) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + auto ng_node = create_unary_op(ng_input); + if (ng_node != ng_input) { + Builder::SetTracingInfo(op->name(), ng_node); + } + SaveNgOp(ng_op_map, op->name(), ng_node); + return Status::OK(); +} + +// Helper function to translate a unary op in cases where there is a one-to-one +// mapping from TensorFlow ops to nGraph ops. +// +// Example usage: +// +// if (n->type_string == "Abs") { +// TF_RETURN_IF_ERROR(TranslateUnaryOp(n, static_input_map, +// ng_op_map)); +// } +// +template +static Status TranslateUnaryOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return TranslateUnaryOp(op, static_input_map, ng_op_map, + [&op](ng::Output n) { + return ConstructNgNode(op->name(), n); + }); +} + +// Helper function to translate a binary op +// Parameters: +// +// TFNodeDecoder* op - TF op being translated. Must have only two +// inputs. +// const std::vector& static_input_map - the static input map +// Builder::OpMap& ng_op_map - The TF-to-nGraph op map. +// std::function(ng::Output, +// ng::Output)> +// create_binary_op - Function to construct the graph implementing +// the binary op, given the 2 ng_inputs to the +// binaryop +// Example Usage: +// +// if (op->type_string() == "SquaredDifference") { +// TF_RETURN_IF_ERROR(TranslateBinaryOp(op, ng_op_map, +// [](ng::Output ng_input1, ng::Output +// ng_input2) { +// auto ng_diff = ng::Output(input1, +// input2); +// return ng::Output(ng_diff,ng_diff); +// })); +// } +// + +static Status TranslateBinaryOp( + const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map, + std::function(ng::Output&, + ng::Output&)> + create_binary_op) { + ng::Output ng_lhs, ng_rhs; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_lhs, ng_rhs)); + auto ng_node = create_binary_op(ng_lhs, ng_rhs); + if (ng_node != ng_lhs && ng_node != ng_rhs) { + Builder::SetTracingInfo(op->name(), ng_node); + } + SaveNgOp(ng_op_map, op->name(), ng_node); + return Status::OK(); +} + +// Helper function to translate a binary op in cases where there is a one-to-one +// mapping from TensorFlow ops to nGraph ops. +// +// Example usage: +// +// if (n->type_string == "Add") { +// TF_RETURN_IF_ERROR(TranslateBinaryOp(op, +// static_input_map, +// ng_op_map)); +// } +// +template +static Status TranslateBinaryOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return TranslateBinaryOp( + op, static_input_map, ng_op_map, + [&op](ng::Output& ng_lhs, ng::Output& ng_rhs) { + return ConstructNgNode(op->name(), ng_lhs, ng_rhs); + }); +} + +static Status TranslateAddNOp(const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map) { + std::vector> ng_arg_vec(op->num_inputs()); + + for (int inp_idx = 0; inp_idx < op->num_inputs(); inp_idx++) + TF_RETURN_IF_ERROR( + GetInputNode(ng_op_map, op, inp_idx, ng_arg_vec[inp_idx])); + auto ng_addn = std::accumulate( + std::next(ng_arg_vec.begin()), ng_arg_vec.end(), ng_arg_vec.at(0), + [&op](ng::Output a, ng::Output b) { + return ConstructNgNode(op->name(), a, b); + }); // accumulation: start with + // first element. default op is + // addition + SaveNgOp(ng_op_map, op->name(), ng_addn); + return Status::OK(); +} +static Status TranslateArgMinMax( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map, std::string mode) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + + std::vector tf_dim; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &tf_dim)); + + ng::Shape input_shape = ng_input.get_shape(); + size_t input_rank = input_shape.size(); + + if (tf_dim.size() != 1) { + return errors::InvalidArgument( + "ArgMax Op: dimension must be scalar, operates on a single axis"); + } + + // If input dimension is negative, make it positive + if (tf_dim[0] < 0) { + NGRAPH_VLOG(3) << "Input dimension is negative, make it positive " + << tf_dim[0]; + tf_dim[0] = (int64_t)input_rank + tf_dim[0]; + } + NGRAPH_VLOG(3) << "Axis along which to compute " << tf_dim[0]; + size_t k_axis = tf_dim[0]; + + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "output_type", &dtype)); + + ng::element::Type ng_et; + TF_RETURN_IF_ERROR(TFDataTypeToNGraphElementType(dtype, &ng_et)); + + auto ng_k = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{}, std::vector({1})); + + std::string sort = "none"; + auto ng_topk = + std::make_shared(ng_input, ng_k, k_axis, mode, sort, ng_et); + auto ng_indices = ng_topk->output(1); + int axis = ng_topk->get_axis(); + auto axis_to_remove = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{1}, std::vector({axis})); + auto reshaped_indices = + ConstructNgNode(op->name(), ng_indices, axis_to_remove); + Builder::SetTracingInfo(op->name(), reshaped_indices); + SaveNgOp(ng_op_map, op->name(), reshaped_indices); + return Status::OK(); +} + +static Status TranslateArgMaxOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return (TranslateArgMinMax(op, static_input_map, ng_op_map, "max")); +} + +static Status TranslateArgMinOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return (TranslateArgMinMax(op, static_input_map, ng_op_map, "min")); +} + +static Status TranslateAvgPoolOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + std::vector tf_strides; + std::vector tf_ksize; + std::string tf_padding_type; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "ksize", &tf_ksize)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "AvgPool data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_ksize); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(2); + ng::Shape ng_image_shape(2); + ng::Shape ng_kernel_shape(2); + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_ksize, ng_kernel_shape); + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff padding_below; + ng::CoordinateDiff padding_above; + ng::Shape ng_dilations{1, 1}; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, padding_below, padding_above); + + // TODO: remove this once nGraph supports negative padding + // (CoordinateDiff) for AvgPool + ng::Shape ng_padding_below(padding_below.begin(), padding_below.end()); + ng::Shape ng_padding_above(padding_above.begin(), padding_above.end()); + + ng::Output ng_avgpool = ConstructNgNode( + op->name(), ng_input, ng_strides, ng_padding_below, ng_padding_above, + ng_kernel_shape, true, ng::op::RoundingType::FLOOR); + + NCHWtoNHWC(op->name(), is_nhwc, ng_avgpool); + NGRAPH_VLOG(3) << "avgpool outshape: {" << ng::join(ng_avgpool.get_shape()) + << "}"; + + SaveNgOp(ng_op_map, op->name(), ng_avgpool); + return Status::OK(); +} + +static Status TranslateBiasAddOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_bias; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_bias)); + + std::string tf_data_format; + if (GetNodeAttr(op->attrs(), "data_format", &tf_data_format) != + Status::OK()) { + tf_data_format = "NHWC"; + } + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "BiasAdd data format is neither NHWC nor NCHW"); + } + + auto ng_input_shape = ng_input.get_shape(); + auto ng_bias_shape = ng_bias.get_shape(); + if (ng_bias_shape.size() != 1) { + return errors::InvalidArgument( + "Bias argument to BiasAdd does not have one dimension"); + } + + // We'll choose reshape over broadcast + // Reshape the bias to (1, C, 1, ...) if input is channels-first. + ng::Output ng_bias_reshaped = ng_bias; + if (tf_data_format == "NCHW") { + auto channel_dim = ng_input_shape[1]; + std::vector target_shape(ng_input_shape.size()); + for (uint64_t i = 0; i < ng_input_shape.size(); i++) { + if (i == 1) { + target_shape[i] = channel_dim; + } else { + target_shape[i] = 1; + } + } + auto target_shape_node = make_shared( + ng::element::i64, ng::Shape{ng_input_shape.size()}, target_shape); + ng_bias_reshaped = ConstructNgNode( + op->name(), ng_bias, target_shape_node, false); + } + + ng::Output ng_add = + ConstructNgNode(op->name(), ng_input, ng_bias_reshaped); + + SaveNgOp(ng_op_map, op->name(), ng_add); + return Status::OK(); +} + +static Status TranslateCastOp(const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "DstT", &dtype)); + + ng::element::Type ng_et; + TF_RETURN_IF_ERROR(TFDataTypeToNGraphElementType(dtype, &ng_et)); + + try { + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, ng_et)); + } catch (const std::out_of_range&) { + return errors::Unimplemented("Failed to convert TF data type: " + + DataType_Name(dtype)); + } + return Status::OK(); +} + +static Status TranslateConcatV2Op( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + TF_RETURN_IF_ERROR(ValidateInputCountMin(op, 2)); + + std::vector tf_concat_axis_vec; + TF_RETURN_IF_ERROR(GetStaticInputVector( + ng_op_map, op, op->num_inputs() - 1, static_input_map, &tf_concat_axis_vec)); + + int64_t concat_axis = tf_concat_axis_vec[0]; + + if (concat_axis < 0) { + ng::Output ng_first_arg; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_first_arg)); + + concat_axis += int64_t(ng_first_arg.get_shape().size()); + } + + ng::OutputVector ng_args; + + for (int i = 0; i < op->num_inputs() - 1; i++) { + ng::Output ng_arg; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, i, ng_arg)); + ng_args.push_back(ng_arg); + } + + SaveNgOp( + ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_args, size_t(concat_axis))); + return Status::OK(); +} + +static Status TranslateConstOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "dtype", &dtype)); + + ng::Output ng_node; + + // For some reason the following do not work (no specialization of + // tensorflow::checkpoint::SavedTypeTraits...) + // case DataType::DT_UINT32: + // TF_RETURN_IF_ERROR(MakeConstOp(op, ng::element::u32, + // &ng_node)); + // break; + // case DataType::DT_UINT64: + // TF_RETURN_IF_ERROR(MakeConstOp(op, ng::element::u64, + // &ng_node)); + // break; + try { + const auto& func_param = Builder::TF_NGRAPH_CONST_MAP().at(dtype); + TF_RETURN_IF_ERROR(func_param.first(op, func_param.second, ng_node)); + } catch (const std::out_of_range&) { + return errors::Unimplemented("Failed to translate Constant with TF type:" + + DataType_Name(dtype)); + } + + SaveNgOp(ng_op_map, op->name(), ng_node); + return Status::OK(); +} + +static Status TranslateConv2DOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_filter; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_filter)); + + std::vector tf_strides; + std::vector tf_dilations; + std::string tf_padding_type; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "dilations", &tf_dilations)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "Conv2D data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + // TF Kernel Test Checks + // Strides in the batch and depth dimension is not supported + if (tf_strides[0] != 1 || tf_strides[is_nhwc ? 3 : 1] != 1) { + return errors::InvalidArgument( + "Strides in batch and depth dimensions is not supported: " + + op->type_string()); + } + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_dilations); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(2); + ng::Strides ng_dilations(2); + ng::Shape ng_image_shape(2); + ng::Shape ng_kernel_shape(2); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_dilations: " << ng::join(ng_dilations); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + Transpose<3, 2, 0, 1>(ng_filter); + Builder::SetTracingInfo(op->name(), ng_filter); + + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff ng_padding_below; + ng::CoordinateDiff ng_padding_above; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, ng_padding_below, + ng_padding_above); + + ng::Output ng_conv = ConstructNgNode( + op->name(), ng_input, ng_filter, ng_strides, ng_padding_below, + ng_padding_above, ng_dilations); + + NCHWtoNHWC(op->name(), is_nhwc, ng_conv); + SaveNgOp(ng_op_map, op->name(), ng_conv); + return Status::OK(); +} + +static Status TranslateConv2DBackpropInputOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_filter, ng_out_backprop, ng_unused; + TF_RETURN_IF_ERROR( + GetInputNodes(ng_op_map, op, ng_unused, ng_filter, ng_out_backprop)); + + // TODO: refactor me to be less redundant with other convolution ops + std::vector tf_strides; + std::vector tf_dilations; + std::string tf_padding_type; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "dilations", &tf_dilations)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "Conv2DBackpropInput data format is neither NHWC nor NCHW: %s" + + tf_data_format); + } + + std::vector tf_input_sizes; + TF_RETURN_IF_ERROR( + GetStaticInputVector(ng_op_map, op, 0, static_input_map, &tf_input_sizes)); + + if (std::any_of(tf_input_sizes.begin(), tf_input_sizes.end(), + [](int32_t size) { return size <= 0; })) { + return errors::InvalidArgument( + "Conv2DBackpropInput input sizes must be positive integers"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_dilations); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(2); + ng::Strides ng_dilations(2); + ng::Shape ng_image_shape(2); + ng::Shape ng_kernel_shape(2); + ng::Shape ng_batch_shape(4); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoHW(is_nhwc, tf_input_sizes, ng_image_shape); + NHWCtoNCHW(op->name(), is_nhwc, ng_out_backprop); + if (is_nhwc) { + ng_batch_shape = {static_cast(tf_input_sizes[0]), + static_cast(tf_input_sizes[3]), + static_cast(tf_input_sizes[1]), + static_cast(tf_input_sizes[2])}; + } else { + ng_batch_shape = {static_cast(tf_input_sizes[0]), + static_cast(tf_input_sizes[1]), + static_cast(tf_input_sizes[2]), + static_cast(tf_input_sizes[3])}; + } + + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_dilations: " << ng::join(ng_dilations); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + Transpose<3, 2, 0, 1>(ng_filter); + Builder::SetTracingInfo(op->name(), ng_filter); + + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff ng_padding_below; + ng::CoordinateDiff ng_padding_above; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, ng_padding_below, + ng_padding_above); + + auto ng_output_shape = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{ng_batch_shape.size() - 2}, + vector(ng_batch_shape.begin() + 2, ng_batch_shape.end())); + + auto ng_data = ConstructNgNode( + op->name(), ng_out_backprop, ng_filter, ng_output_shape, ng_strides, + ng_padding_below, ng_padding_above, ng_dilations); + + NCHWtoNHWC(op->name(), is_nhwc, ng_data); + SaveNgOp(ng_op_map, op->name(), ng_data); + return Status::OK(); +} + +// Translate Conv3D Op +static Status TranslateConv3DOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_filter; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_filter)); + + std::vector tf_strides; + std::vector tf_dilations; + std::string tf_padding_type; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "dilations", &tf_dilations)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NDHWC" && tf_data_format != "NCDHW") { + return errors::InvalidArgument( + "Conv3D data format is neither NDHWC nor NCDHW"); + } + + bool is_ndhwc = (tf_data_format == "NDHWC"); + + // TODO: in 3D + // TF Kernel Test Checks + // // Strides in the batch and depth dimension is not supported + // if (tf_strides[0] != 1 || tf_strides[is_nhwc ? 3 : 1] != 1) { + // return errors::InvalidArgument( + // "Strides in batch and depth dimensions is not supported: ", + // op->type_string()); + // } + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_dilations); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(3); + ng::Strides ng_dilations(3); + ng::Shape ng_image_shape(3); + ng::Shape ng_kernel_shape(3); + + NHWCtoHW(is_ndhwc, tf_strides, ng_strides); + NHWCtoHW(is_ndhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_ndhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(op->name(), is_ndhwc, ng_input); + + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_dilations: " << ng::join(ng_dilations); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + ng_kernel_shape[2] = ng_filter_shape[2]; + Transpose3D<4, 3, 0, 1, 2>(ng_filter); + Builder::SetTracingInfo(op->name(), ng_filter); + + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff ng_padding_below; + ng::CoordinateDiff ng_padding_above; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, ng_padding_below, + ng_padding_above); + + ng::Output ng_conv = ConstructNgNode( + op->name(), ng_input, ng_filter, ng_strides, ng_padding_below, + ng_padding_above, ng_dilations); + + NCHWtoNHWC(op->name(), is_ndhwc, ng_conv); + SaveNgOp(ng_op_map, op->name(), ng_conv); + return Status::OK(); +} + +static Status TranslateCumsumOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_x, ng_axis; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_x, ng_axis)); + bool exclusive, reverse; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "exclusive", &exclusive)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "reverse", &reverse)); + + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_x, ng_axis, exclusive, + reverse)); + return Status::OK(); +} + +// Translate DepthToSpace op +static Status TranslateDepthToSpaceOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + // Get the attributes + int64_t block_size; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "block_size", &block_size)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "DepthToSpace data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + auto ng_mode = opset::DepthToSpace::DepthToSpaceMode::BLOCKS_FIRST; + ng::Output depth_to_space = ConstructNgNode( + op->name(), ng_input, ng_mode, block_size); + NCHWtoNHWC(op->name(), is_nhwc, depth_to_space); + SaveNgOp(ng_op_map, op->name(), depth_to_space); + return Status::OK(); +} + +static Status TranslateDepthwiseConv2dNativeOp( + const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_filter; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_filter)); + + std::vector tf_strides; + std::vector tf_dilations; + std::string tf_padding_type; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "dilations", &tf_dilations)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "DepthwiseConv2D data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_dilations); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(2); + ng::Strides ng_dilations(2); + ng::Shape ng_image_shape(2); + ng::Shape ng_kernel_shape(2); + + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_dilations: " << ng::join(ng_dilations); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff ng_padding_below; + ng::CoordinateDiff ng_padding_above; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, ng_padding_below, + ng_padding_above); + + // H W I M -> H W I 1 M + auto filter_shape = ConstructNgNode( + op->name(), ng::element::u64, ng::Shape{5}, + ngraph::Shape{ng_filter_shape[0], ng_filter_shape[1], ng_filter_shape[2], + 1, ng_filter_shape[3]}); + auto reshaped_filter = ConstructNgNode(op->name(), ng_filter, + filter_shape, false); + + // H W I 1 M -> I M 1 H W + auto order = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{5}, vector{2, 4, 3, 0, 1}); + auto transposed_filter = + ConstructNgNode(op->name(), reshaped_filter, order); + + auto ng_conv = ConstructNgNode( + op->name(), ng_input, transposed_filter, ng_strides, ng_padding_below, + ng_padding_above, ng_dilations); + + NCHWtoNHWC(op->name(), is_nhwc, ng_conv); + SaveNgOp(ng_op_map, op->name(), ng_conv); + return Status::OK(); +} + +static Status TranslateExpandDimsOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + std::vector dims; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &dims)); + auto ng_dims = ConstructNgNode( + op->name(), ng::element::i64, ngraph::Shape{dims.size()}, dims); + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, ng_dims)); + return Status::OK(); +} + +static Status TranslateFillOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_value, ng_dims; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_dims, ng_value)); + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_value, ng_dims)); + return Status::OK(); +} + +static Status TranslateFloorDivOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + auto floordiv_fn = [&op](ng::Output x, ng::Output y) { + return ConstructNgNode( + op->name(), ConstructNgNode(op->name(), x, y)); + }; + return TranslateBinaryOp(op, static_input_map, ng_op_map, floordiv_fn); +} + +static Status TranslateFusedBatchNormOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_scale, ng_offset, ng_mean, ng_variance; + bool is_v3 = op->type_string() == "FusedBatchNormV3"; + + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_scale, ng_offset, + ng_mean, ng_variance)); + + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "Conv2D data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NGRAPH_VLOG(3) << "data_format: " << tf_data_format; + + float tf_epsilon; + if (GetNodeAttr(op->attrs(), "epsilon", &tf_epsilon) != Status::OK()) { + NGRAPH_VLOG(3) << "epsilon attribute not present, setting to 0.0001"; + // TensorFlow default + tf_epsilon = 0.0001; + } + + NGRAPH_VLOG(3) << "epsilon: " << tf_epsilon; + + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + + auto ng_batch_norm = ConstructNgNode( + op->name(), ng_input, ng_scale, ng_offset, ng_mean, ng_variance, + tf_epsilon); + NCHWtoNHWC(op->name(), is_nhwc, ng_batch_norm); + SaveNgOp(ng_op_map, op->name(), ng_batch_norm); + SaveNgOp(ng_op_map, op->name(), ng_mean); + SaveNgOp(ng_op_map, op->name(), ng_variance); + SaveNgOp(ng_op_map, op->name(), ng_mean); // reserve_space_1 + SaveNgOp(ng_op_map, op->name(), ng_variance); // reserve_space_2 + if (is_v3) { + // FusedBatchNormV3 has 6 outputs + SaveNgOp(ng_op_map, op->name(), ng_mean); // reserve_space_3 + } + return Status::OK(); +} + +static Status TranslateFusedMatMulOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + int num_args; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "num_args", &num_args)); + + std::vector fused_ops; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "fused_ops", &fused_ops)); + + // Transpose arguments if requested. + bool transpose_a = false; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "transpose_a", &transpose_a)); + + bool transpose_b = false; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "transpose_b", &transpose_b)); + + ng::Output ng_lhs, ng_rhs, ng_bias, ng_matmul; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_lhs, ng_rhs, ng_bias)); + ng_matmul = ConstructNgNode(op->name(), ng_lhs, ng_rhs, + transpose_a, transpose_b); + + auto ng_matmul_shape = ng_matmul.get_shape(); + auto ng_bias_shape = ng_bias.get_shape(); + + if (ng_bias_shape.size() != 1) { + return errors::InvalidArgument( + "Bias argument to BiasAdd does not have one dimension"); + } + + auto ng_add = ConstructNgNode(op->name(), ng_matmul, ng_bias); + if (fused_ops.size() == 1) { // Only fusing BiasAdd + SaveNgOp(ng_op_map, op->name(), ng_add); + } else if (fused_ops.size() == 2) { // Also has activation + if (fused_ops[1] == "Relu") { + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_add)); + } else if (fused_ops[1] == "Relu6") { + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_add, 0, 6)); + } else { + return errors::Internal( + "Expected activation to be Relu or Relu6 but got " + fused_ops[1]); + } + } else { + // Adding this here to catch future changes in _FusedMatMul + return errors::Internal("Unsupported combination"); + } + + return Status::OK(); +} + +// See .../tensorflow/include/tensorflow/cc/ops/array_ops.h +// and .../openvino/ngraph/core/include/ngraph/op/gather.hpp +static Status TranslateGatherOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_input_indices; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_input_indices)); + + auto ng_axis = ConstructNgNode(op->name(), ng::element::i64, + ng::Shape{}, 0); + + auto gather_op = ConstructNgNode(op->name(), ng_input, + ng_input_indices, ng_axis); + + SaveNgOp(ng_op_map, op->name(), gather_op); + return Status::OK(); +} + +static Status TranslateGatherV2Op( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_input_coords, ng_unused; + TF_RETURN_IF_ERROR( + GetInputNodes(ng_op_map, op, ng_input, ng_input_coords, ng_unused)); + + std::vector tf_axis; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 2, static_input_map, &tf_axis)); + + if (tf_axis.size() > 1) { + std::ostringstream buf; + buf << "Found axis in GatherV2 op (" << op->name() << + ") translation to be non scalar, of size " << + tf_axis.size(); + return errors::Internal(buf.str()); + } + + // Negative axis is supported. Accounting for that + auto ng_input_shape = ng_input.get_shape(); + size_t ng_input_rank = ng_input_shape.size(); + int axis; + if (tf_axis[0] >= 0) { + axis = tf_axis[0]; + } else { + axis = tf_axis[0] + ng_input_rank; + } + if (axis < 0 || axis >= int32_t(ng_input_rank)) { + std:ostringstream buf; + buf << "Expected axis in the range [-" << + ng_input_rank << ", " << ng_input_rank << + "), but got " << tf_axis[0]; + return errors::InvalidArgument(buf.str()); + } + + auto ng_axis = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{tf_axis.size()}, tf_axis); + + auto gather_op = ConstructNgNode(op->name(), ng_input, + ng_input_coords, ng_axis); + + SaveNgOp(ng_op_map, op->name(), gather_op); + return Status::OK(); +} + +static Status TranslateFusedConv2DOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + int num_args; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "num_args", &num_args)); + + std::vector fused_ops; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "fused_ops", &fused_ops)); + + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + bool is_nhwc = (tf_data_format == "NHWC"); + + auto CreateNgConv = [&](ng::Output& ng_input, + ng::Output& ng_filter, + ng::Output& ng_conv) { + std::vector tf_strides; + std::vector tf_dilations; + std::string tf_padding_type; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "dilations", &tf_dilations)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "Conv2D data format is neither NHWC nor NCHW"); + } + + // TF Kernel Test Checks + // Strides in the batch and depth dimension is not supported + if (tf_strides[0] != 1 || tf_strides[is_nhwc ? 3 : 1] != 1) { + return errors::InvalidArgument( + "Strides in batch and depth dimensions is not supported: " + + op->type_string()); + } + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_dilations); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(2); + ng::Strides ng_dilations(2); + ng::Shape ng_image_shape(2); + ng::Shape ng_kernel_shape(2); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_dilations, ng_dilations); + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_dilations: " << ng::join(ng_dilations); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + + auto& ng_filter_shape = ng_filter.get_shape(); + ng_kernel_shape[0] = ng_filter_shape[0]; + ng_kernel_shape[1] = ng_filter_shape[1]; + Transpose<3, 2, 0, 1>(ng_filter); + Builder::SetTracingInfo(op->name(), ng_filter); + + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff ng_padding_below; + ng::CoordinateDiff ng_padding_above; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, ng_padding_below, + ng_padding_above); + + ng_conv = ConstructNgNode( + op->name() + "_FusedConv2D_Conv", ng_input, ng_filter, ng_strides, + ng_padding_below, ng_padding_above, ng_dilations); + + return Status::OK(); + }; + + if (VecStrCmp(fused_ops, {"BiasAdd"}) || + VecStrCmp(fused_ops, {"BiasAdd", "Relu"}) || + VecStrCmp(fused_ops, {"BiasAdd", "Relu6"})) { + if (num_args != 1) { + return errors::InvalidArgument( + "FusedConv2DBiasAdd has incompatible num_args"); + } + + ng::Output ng_input, ng_filter, ng_bias, ng_conv; + TF_RETURN_IF_ERROR( + GetInputNodes(ng_op_map, op, ng_input, ng_filter, ng_bias)); + + TF_RETURN_IF_ERROR(CreateNgConv(ng_input, ng_filter, ng_conv)); + + auto ng_conv_shape = ng_conv.get_shape(); + auto ng_bias_shape = ng_bias.get_shape(); + if (ng_bias_shape.size() != 1) { + return errors::InvalidArgument( + "Bias argument to BiasAdd does not have one dimension"); + } + + std::vector reshape_pattern_values(ng_conv_shape.size(), 1U); + reshape_pattern_values[1] = ng_bias.get_shape().front(); + auto reshape_pattern = make_shared( + ng::element::u64, ng::Shape{reshape_pattern_values.size()}, + reshape_pattern_values); + auto ng_bias_reshaped = ConstructNgNode( + op->name(), ng_bias, reshape_pattern, false); + + auto ng_add = ConstructNgNode( + op->name() + "_FusedConv2D_BiasAdd", ng_conv, ng_bias_reshaped); + + if (VecStrCmp(fused_ops, {"BiasAdd", "Relu"})) { + auto ng_relu = ConstructNgNode( + op->name() + "_FusedConv2D_Relu", ng_add); + NCHWtoNHWC(op->name(), is_nhwc, ng_relu); + SaveNgOp(ng_op_map, op->name(), ng_relu); + } else if (VecStrCmp(fused_ops, {"BiasAdd", "Relu6"})) { + auto ng_relu6 = ConstructNgNode( + op->name() + "_FusedConv2D_Relu6", ng_add, 0, 6); + NCHWtoNHWC(op->name(), is_nhwc, ng_relu6); + SaveNgOp(ng_op_map, op->name(), ng_relu6); + } else { + NCHWtoNHWC(op->name(), is_nhwc, ng_add); + SaveNgOp(ng_op_map, op->name(), ng_add); + } + } else if (VecStrCmp(fused_ops, {"FusedBatchNorm"}) || + VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu"}) || + VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu6"})) { + if (num_args != 4) { + return errors::InvalidArgument( + "FusedConv2D with FusedBatchNorm has incompatible num_args"); + } + + ng::Output ng_input, ng_filter, ng_conv, ng_scale, ng_offset, + ng_mean, ng_variance; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_filter, + ng_scale, ng_offset, ng_mean, + ng_variance)); + TF_RETURN_IF_ERROR(CreateNgConv(ng_input, ng_filter, ng_conv)); + + float tf_epsilon; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "epsilon", &tf_epsilon)); + + auto ng_batch_norm = ConstructNgNode( + op->name() + "_FusedConv2D_BatchNorm", ng_conv, ng_scale, ng_offset, + ng_mean, ng_variance, tf_epsilon); + + if (VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu"})) { + auto ng_relu = ConstructNgNode( + op->name() + "_FusedConv2D_BatchNormRelu", ng_batch_norm); + NCHWtoNHWC(op->name(), is_nhwc, ng_relu); + SaveNgOp(ng_op_map, op->name(), ng_relu); + } else if (VecStrCmp(fused_ops, {"FusedBatchNorm", "Relu6"})) { + auto ng_relu6 = ConstructNgNode( + op->name() + "_FusedConv2D_BatchNormRelu", ng_batch_norm, 0, 6); + NCHWtoNHWC(op->name(), is_nhwc, ng_relu6); + SaveNgOp(ng_op_map, op->name(), ng_relu6); + } else { + NCHWtoNHWC(op->name(), is_nhwc, ng_batch_norm); + SaveNgOp(ng_op_map, op->name(), ng_batch_norm); + } + } else { + return errors::Unimplemented("Unsupported _FusedConv2D " + + StrJoin(fused_ops, ",")); + } + return Status::OK(); +} + +static Status TranslateIdentityOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_arg; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_arg)); + SaveNgOp(ng_op_map, op->name(), ng_arg); + return Status::OK(); +} + +static Status TranslateIsFiniteOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + // Implemented tf.is_finite by checking: + // (in != inf) && (in != -inf) && (in == in) + // ^^^^^^^^ checks for NaN's + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + auto const_inf = ConstructNgNode( + op->name(), ng_input.get_element_type(), ng::Shape{}, + std::vector{std::numeric_limits::infinity()}); + + auto const_neg_inf = ConstructNgNode( + op->name(), ng_input.get_element_type(), ng::Shape{}, + std::vector{-std::numeric_limits::infinity()}); + + auto neq_inf = + ConstructNgNode(op->name(), ng_input, const_inf); + auto neq_neg_inf = + ConstructNgNode(op->name(), ng_input, const_neg_inf); + auto eq_nan = ConstructNgNode(op->name(), ng_input, ng_input); + + auto neq_inf_and_neq_neg_inf = + ConstructNgNode(op->name(), neq_inf, neq_neg_inf); + auto is_finite = ConstructNgNode( + op->name(), neq_inf_and_neq_neg_inf, eq_nan); + + SaveNgOp(ng_op_map, op->name(), is_finite); + return Status::OK(); +} + +static Status TranslateL2LossOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + std::vector val; + val.push_back(2.0); + auto const_2 = ConstructNgNode( + op->name(), ng_input.get_element_type(), ng::Shape{}, val[0]); + + auto ng_pow = + ConstructNgNode(op->name(), ng_input, ng_input); + + size_t input_rank = ng_input.get_shape().size(); + std::vector axes; + for (size_t i = 0; i < input_rank; ++i) { + axes.push_back(i); + } + + auto ng_reduction_axes = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{axes.size()}, axes); + auto ng_sum = + ConstructNgNode(op->name(), ng_pow, ng_reduction_axes); + auto ng_l2loss = ConstructNgNode(op->name(), ng_sum, const_2); + SaveNgOp(ng_op_map, op->name(), ng_l2loss); + return Status::OK(); +} + +static Status TranslateLog1pOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return TranslateUnaryOp( + op, static_input_map, ng_op_map, [&op](ng::Output n) { + auto et = n.get_element_type(); + auto shape = n.get_shape(); + std::vector val_1(ng::shape_size(shape), "1"); + auto ng_const1 = + ConstructNgNode(op->name(), et, shape, val_1); + auto ng_add = ConstructNgNode(op->name(), ng_const1, n); + return ConstructNgNode(op->name(), ng_add); + }); +} + +static Status TranslateLRNOp(const TFNodeDecoder* op, + const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_inp; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_inp)); + + float alpha; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "alpha", &alpha)); + float beta; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "beta", &beta)); + float bias; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "bias", &bias)); + int64_t depth_radius; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "depth_radius", &depth_radius)); + + // OV: Each input value is divided by (bias+(alpha/size)*sum(xi^2 for every xi + // in the local region))^beta + // TF: sqr_sum[a, b, c, d] = sum(input[a, b, c, d - depth_radius : d + + // depth_radius + 1] ** 2) + // output = input / (bias + alpha * sqr_sum) ** beta + int64_t size = depth_radius * 2 + 1; + alpha = alpha * size; + // nGraph expects the input to be in NCHW format + NHWCtoNCHW(op->name(), true, ng_inp); + auto ng_output = ConstructNgNode(op->name(), ng_inp, alpha, beta, + bias, (size_t)size); + NCHWtoNHWC(op->name(), true, ng_output); + SaveNgOp(ng_op_map, op->name(), ng_output); + return Status::OK(); +} + +static Status TranslateLogSoftmaxOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_inp; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_inp)); + auto inp_shape = ng_inp.get_shape(); + size_t rank = inp_shape.size(); + int64_t axes = rank - 1; + + auto ng_output = ConstructNgNode(op->name(), ng_inp, axes); + SaveNgOp(ng_op_map, op->name(), ng_output); + return Status::OK(); +} + +static Status TranslateMatMulOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_lhs, ng_rhs; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_lhs, ng_rhs)); + + // Transpose arguments if requested. + bool transpose_a = false; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "transpose_a", &transpose_a)); + + bool transpose_b = false; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "transpose_b", &transpose_b)); + + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_lhs, ng_rhs, + transpose_a, transpose_b)); + return Status::OK(); +} + +template +static Status TranslateMaxPoolOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + std::vector tf_strides; + std::vector tf_ksize; + std::string tf_padding_type; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "strides", &tf_strides)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "ksize", &tf_ksize)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "padding", &tf_padding_type)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + bool is_nhwc = (tf_data_format == "NHWC") || (tf_data_format == "NDHWC"); + + NGRAPH_VLOG(3) << ng::join(tf_strides); + NGRAPH_VLOG(3) << ng::join(tf_ksize); + NGRAPH_VLOG(3) << tf_padding_type; + NGRAPH_VLOG(3) << tf_data_format; + + ng::Strides ng_strides(N); + ng::Shape ng_image_shape(N); + ng::Shape ng_kernel_shape(N); + ng::Shape ng_dilations(N, 1); + + NHWCtoHW(is_nhwc, tf_strides, ng_strides); + NHWCtoHW(is_nhwc, ng_input.get_shape(), ng_image_shape); + NHWCtoHW(is_nhwc, tf_ksize, ng_kernel_shape); + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + NGRAPH_VLOG(3) << "ng_strides: " << ng::join(ng_strides); + NGRAPH_VLOG(3) << "ng_image_shape: " << ng::join(ng_image_shape); + NGRAPH_VLOG(3) << "ng_kernel_shape: " << ng::join(ng_kernel_shape); + + ng::CoordinateDiff padding_below; + ng::CoordinateDiff padding_above; + Builder::MakePadding(tf_padding_type, ng_image_shape, ng_kernel_shape, + ng_strides, ng_dilations, padding_below, padding_above); + + // TODO: remove this once nGraph supports negative padding + // (CoordinateDiff) for MaxPool + ng::Shape ng_padding_below(padding_below.begin(), padding_below.end()); + ng::Shape ng_padding_above(padding_above.begin(), padding_above.end()); + + auto ng_maxpool = ConstructNgNode( + op->name(), ng_input, ng_strides, ng_padding_below, ng_padding_above, + ng_kernel_shape, ng::op::RoundingType::FLOOR); + + NCHWtoNHWC(op->name(), is_nhwc, ng_maxpool); + + NGRAPH_VLOG(3) << "maxpool outshape: {" << ng::join(ng_maxpool.get_shape()) + << "}"; + + SaveNgOp(ng_op_map, op->name(), ng_maxpool); + return Status::OK(); +} + +static Status TranslateNonMaxSuppressionV2Op( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_boxes, ng_scores, ng_unused, ng_iou_threshold; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_boxes, ng_scores, + ng_unused, ng_iou_threshold)); + + auto ng_axis_boxes = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{1}, std::vector({0})); + auto ng_boxes_unsqueezed = + ConstructNgNode(op->name(), ng_boxes, ng_axis_boxes); + + auto ng_axis_scores = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{1}, std::vector({0})); + auto ng_scores_unsqueezed1 = + ConstructNgNode(op->name(), ng_scores, ng_axis_scores); + auto ng_scores_unsqueezed2 = ConstructNgNode( + op->name(), ng_scores_unsqueezed1, ng_axis_scores); + + std::vector max_output_size; + TF_RETURN_IF_ERROR( + GetStaticInputVector(ng_op_map, op, 2, static_input_map, &max_output_size)); + + // max_output_size must be scalar + if (max_output_size.size() != 1) { + return errors::InvalidArgument( + "NonMaxSuppression Op: max_output_size of nms must be scalar " + + to_string(max_output_size.size())); + } + + auto ng_max_output_size = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{}, max_output_size[0]); + NGRAPH_VLOG(5) << "ng_max_output_size " << max_output_size[0]; + + auto ng_nmsv = ConstructNgNode( + op->name(), ng_boxes_unsqueezed, ng_scores_unsqueezed2, + ng_max_output_size, ng_iou_threshold, + opset::NonMaxSuppression::BoxEncodingType::CORNER, false, + ngraph::element::Type_t::i32); + + auto begin = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{2}, std::vector({0, 2})); + auto end = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{2}, + std::vector({max_output_size[0], 3})); + auto ng_nmsv_slice = ConstructNgNode( + op->name(), ng_nmsv, begin, end, std::vector{0, 0}, + std::vector{0, 0}, std::vector{0, 0}, + std::vector{0, 1}); + + Builder::SetTracingInfo(op->name(), ng_nmsv_slice); + SaveNgOp(ng_op_map, op->name(), ng_nmsv_slice); + return Status::OK(); +} + +static Status TranslateReduceOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map, + std::function(ng::Output, + ng::Output, const bool)> + create_ng_node) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + bool tf_keep_dims; + if (GetNodeAttr(op->attrs(), "keep_dims", &tf_keep_dims) != Status::OK()) { + tf_keep_dims = false; + } + + std::vector axes; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &axes)); + + ng::Shape input_shape = ng_input.get_shape(); + size_t input_rank = input_shape.size(); + + TF_RETURN_IF_ERROR(CheckAxisDimInRange(axes, input_rank)); + + std::vector ng_reduction_axes_vect(axes.size()); + std::transform( + axes.begin(), axes.end(), ng_reduction_axes_vect.begin(), + [input_rank](int idx) { return idx + (idx < 0 ? (int)input_rank : 0); }); + auto ng_reduction_axes = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{ng_reduction_axes_vect.size()}, + ng_reduction_axes_vect); + + ng::Output ng_node = + create_ng_node(ng_input, ng_reduction_axes, tf_keep_dims); + + SaveNgOp(ng_op_map, op->name(), ng_node); + return Status::OK(); +} + +template +static Status TranslateDirectReduceOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + // ensure its either an arithmetic or a logical reduction + if (!(std::is_base_of::value || + std::is_base_of::value)) { + return errors::InvalidArgument( + "Expected node to be either a valid logical or arithmetic reduction " + "type"); + } + return TranslateReduceOp( + op, static_input_map, ng_op_map, + [&op](ng::Output ng_input, + ng::Output ng_reduction_axes, const bool keep_dims) { + return ConstructNgNode(op->name(), ng_input, ng_reduction_axes, + keep_dims); + }); +} + +static Status TranslateOneHotOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_features, ng_unused, ng_on, ng_off, ng_depth; + TF_RETURN_IF_ERROR( + GetInputNodes(ng_op_map, op, ng_features, ng_unused, ng_on, ng_off)); + + auto ng_features_shape = ng_features.get_shape(); + std::vector depth; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &depth)); + + // Depth must be scalar + if (depth.size() != 1) { + return errors::InvalidArgument( + "OneHot Op: depth of one hot dimension must be scalar " + to_string(depth.size())); + } + + auto const_depth = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{}, depth); + + int one_hot_axis; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "axis", &one_hot_axis)); + + auto ng_onehot = ConstructNgNode( + op->name(), ng_features, const_depth, ng_on, ng_off, one_hot_axis); + SaveNgOp(ng_op_map, op->name(), ng_onehot); + return Status::OK(); +} + +static Status TranslatePackOp(const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map) { + TF_RETURN_IF_ERROR(ValidateInputCountMin(op, 1)); + + int32_t tf_axis; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "axis", &tf_axis)); + auto ng_axis = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{1}, + std::vector({tf_axis})); + + ng::OutputVector ng_concat_inputs; + for (int32_t i = 0; i < op->num_inputs(); ++i) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, i, ng_input)); + auto unsqueezed_input = + ConstructNgNode(op->name(), ng_input, ng_axis); + ng_concat_inputs.push_back(unsqueezed_input); + } + + // if inputs shape is (2, 3, 4), and axis is 1, then we want + // to create output_shape (2, num_inputs, 3, 4) + SaveNgOp(ng_op_map, op->name(), ConstructNgNode( + op->name(), ng_concat_inputs, tf_axis)); + return Status::OK(); +} + +// 3 different Pad Ops: Pad, PadV2, MirrorPad +// See https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/pad +// See https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/pad-v2 +// See https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/mirror-pad +static Status TranslatePadOp(const TFNodeDecoder* op, + const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_paddings_op, pad_val_op, result_pad_op; + + // Set inputs and pad_val_op + if (op->type_string() == "Pad" || op->type_string() == "MirrorPad") { + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_paddings_op)); + pad_val_op = ConstructNgNode( + op->name(), ng_input.get_element_type(), ng::Shape(), + std::vector({0})); + } else if (op->type_string() == "PadV2") { + TF_RETURN_IF_ERROR( + GetInputNodes(ng_op_map, op, ng_input, ng_paddings_op, pad_val_op)); + } else { + return errors::InvalidArgument("Incorrect TF Pad OpType: " + + op->type_string()); + } + + // Set pad_mode + auto pad_mode = ng::op::PadMode::CONSTANT; + if (op->type_string() == "MirrorPad") { + std::string pad_mode_str; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "mode", &pad_mode_str)); + if (pad_mode_str == "REFLECT") { + pad_mode = ng::op::PadMode::REFLECT; + } else if (pad_mode_str == "SYMMETRIC") { + pad_mode = ng::op::PadMode::SYMMETRIC; + } else { + return errors::InvalidArgument(pad_mode_str + + " is not an allowed padding mode."); + } + } + + // Set pads_begin & pads_end (from the pad_val_op) + std::vector paddings; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &paddings)); + NGRAPH_VLOG(3) << op->name() << " pads {" << ng::join(paddings) << "}"; + if (paddings.size() % 2 != 0) { + return errors::InvalidArgument( + "Constant node for paddings does not have an even number of " + "elements"); + } + std::vector pad_begin(paddings.size() / 2); + std::vector pad_end(paddings.size() / 2); + for (size_t i = 0; i < paddings.size() / 2; i++) { + pad_begin[i] = paddings[2 * i]; + pad_end[i] = paddings[2 * i + 1]; + } + auto pads_begin_node = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{pad_begin.size()}, pad_begin); + auto pads_end_node = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{pad_end.size()}, pad_end); + + // Create final Op + result_pad_op = + ConstructNgNode(op->name(), ng_input, pads_begin_node, + pads_end_node, pad_val_op, pad_mode); + + SaveNgOp(ng_op_map, op->name(), result_pad_op); + return Status::OK(); +} + +static Status TranslateRangeOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_start, ng_stop, ng_step; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_start, ng_stop, ng_step)); + + //DataType start_type = op->input_type(0); + //DataType stop_type = op->input_type(1); + //DataType step_type = op->input_type(2); + ng::element::Type out_type; + TF_RETURN_IF_ERROR( + TFDataTypeToNGraphElementType(op->output_type(0), &out_type)); + //ng::Output start_node, stop_node, step_node; + //TF_RETURN_IF_ERROR( + // GetStaticInputNode(op, 0, static_input_map, start_type, start_node)); + //TF_RETURN_IF_ERROR( + // GetStaticInputNode(op, 1, static_input_map, stop_type, stop_node)); + //TF_RETURN_IF_ERROR( + // GetStaticInputNode(op, 2, static_input_map, step_type, step_node)); + auto ng_range = ConstructNgNode(op->name(), ng_start, + ng_stop, ng_step, out_type); + + SaveNgOp(ng_op_map, op->name(), ng_range); + return Status::OK(); +} + +static Status TranslateRankOp(const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + ng::Shape input_shape = ng_input.get_shape(); + auto input_rank = static_cast(input_shape.size()); + + auto ng_rank = ConstructNgNode( + op->name(), ng::element::i32, ng::Shape(), + std::vector({input_rank})); + + SaveNgOp(ng_op_map, op->name(), ng_rank); + return Status::OK(); +} + +static Status TranslateReciprocalOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return TranslateUnaryOp( + op, static_input_map, ng_op_map, [&op](ng::Output n) { + // Create a constant tensor populated with the value -1. + // (1/x = x^(-1)) + auto et = n.get_element_type(); + auto shape = n.get_shape(); + std::vector constant_values(ng::shape_size(shape), "-1"); + auto ng_exponent = ConstructNgNode( + op->name(), et, shape, constant_values); + + // Raise each element of the input to the power -1. + return ConstructNgNode(op->name(), n, ng_exponent); + }); +} + +static Status TranslateRelu6Op(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, 0, 6)); + return Status::OK(); +} + +static Status TranslateReshapeOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_shape_op; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_shape_op)); + + NGRAPH_VLOG(3) << "Input shape: " << ng::join(ng_input.get_shape()); + + std::vector shape; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &shape)); + + NGRAPH_VLOG(3) << "Requested result shape: " << ng::join(shape); + + auto ng_shape = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{shape.size()}, shape); + SaveNgOp(ng_op_map, op->name(), ConstructNgNode( + op->name(), ng_input, ng_shape, false)); + return Status::OK(); +} + +static Status TranslateRsqrtOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return TranslateUnaryOp( + op, static_input_map, ng_op_map, [&op](ng::Output n) { + // Create a constant tensor populated with the value -1/2. + // (1/sqrt(x) = x^(-1/2)) + auto et = n.get_element_type(); + auto shape = n.get_shape(); + std::vector constant_values(ng::shape_size(shape), "-0.5"); + auto ng_exponent = ConstructNgNode( + op->name(), et, shape, constant_values); + + // Raise each element of the input to the power -0.5. + return ConstructNgNode(op->name(), n, ng_exponent); + }); +} + +static Status TranslateShapeOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "out_type", &dtype)); + + ng::element::Type type; + TF_RETURN_IF_ERROR(TFDataTypeToNGraphElementType(dtype, &type)); + + // default output_type = element::i64 + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, type)); + return Status::OK(); +} + +static Status TranslateSizeOp(const TFNodeDecoder* op, const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "out_type", &dtype)); + + // Size has an attribute to specify output, int32_t or int64_t + ng::element::Type type; + TF_RETURN_IF_ERROR(TFDataTypeToNGraphElementType(dtype, &type)); + + auto ng_input_shape = ng_input.get_shape(); + int64_t result = 1; + for (auto dim : ng_input_shape) { + result *= dim; + } + + // make a scalar with value equals to result + auto ng_result = ConstructNgNode( + op->name(), type, ng::Shape(0), std::vector({result})); + + SaveNgOp(ng_op_map, op->name(), ng_result); + return Status::OK(); +} + +static Status TranslateSliceOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_begin, ng_size; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_begin, ng_size)); + + std::vector begin_vec; + std::vector size_vec; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &begin_vec)); + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 2, static_input_map, &size_vec)); + + if (begin_vec.size() != size_vec.size()) + return errors::InvalidArgument( + "Cannot translate slice op: size of begin = " + to_string(begin_vec.size()) + + ", size of size_vec = " + to_string(size_vec.size()) + ". Expected them to match."); + + NGRAPH_VLOG(3) << "Begin input for Slice: " << ng::join(begin_vec); + NGRAPH_VLOG(3) << "Size input for Slice: " << ng::join(size_vec); + + std::vector end_vec(begin_vec.size()); + const auto ng_input_shape = ng_input.get_shape(); + stringstream err_stream; + string err_msg; + for (size_t i = 0; i < size_vec.size(); i++) { + if (size_vec[i] != -1) { + end_vec[i] = begin_vec[i] + size_vec[i]; + } else { + // support -1 for size_vec, to the end of the tensor + end_vec[i] = ng_input_shape[i]; + } + + // check for this condition: 0 <= begin[i] <= begin[i] + size[i] <= Di + if (0 > begin_vec[i]) + err_stream << "lower < 0: " << begin_vec[i] + << ". It should have been positive.\n"; + if (begin_vec[i] > end_vec[i]) + err_stream << "upper < lower: upper = " << end_vec[i] + << ", lower = " << begin_vec[i] << "\n"; + if (begin_vec[i] > int32_t(ng_input_shape[i])) + err_stream << "dim < upper: dim = " << ng_input_shape[i] + << ", upper = " << end_vec[i] << "\n"; + + err_msg = err_stream.str(); + if (!err_msg.empty()) + return errors::InvalidArgument("Cannot translate slice op at position " + + to_string(i) + " of " + to_string(size_vec.size()) + + ". The reasons are:\n" + err_msg); + } + + auto begin = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{begin_vec.size()}, begin_vec); + auto end = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{end_vec.size()}, end_vec); + + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, begin, + end, std::vector{}, + std::vector{})); + return Status::OK(); +} + +static Status TranslateSoftmaxOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + auto input_shape = ng_input.get_shape(); + auto rank = input_shape.size(); + if (rank < 1) { + return errors::InvalidArgument("TF Softmax logits must be >=1 dimension"); + } + + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, rank - 1)); + return Status::OK(); +} + +// Translate SpaceToDepthOp +static Status TranslateSpaceToDepthOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + // Get the attributes + int64_t block_size; + std::string tf_data_format; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "block_size", &block_size)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "data_format", &tf_data_format)); + + if (tf_data_format != "NHWC" && tf_data_format != "NCHW") { + return errors::InvalidArgument( + "DepthToSpace data format is neither NHWC nor NCHW"); + } + + bool is_nhwc = (tf_data_format == "NHWC"); + + NHWCtoNCHW(op->name(), is_nhwc, ng_input); + auto ng_mode = opset::SpaceToDepth::SpaceToDepthMode::BLOCKS_FIRST; + auto space_to_depth = ConstructNgNode( + op->name(), ng_input, ng_mode, block_size); + NCHWtoNHWC(op->name(), is_nhwc, space_to_depth); + SaveNgOp(ng_op_map, op->name(), space_to_depth); + return Status::OK(); +} + +static Status TranslateSplitOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 1, ng_input)); + // num_split : The number of ways to split. Must evenly divide + // value.shape[split_dim] + int32_t num_split; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "num_split", &num_split)); + + ng::Shape shape = ng_input.get_shape(); + int rank = shape.size(); + + std::vector split_dim_vec; + TF_RETURN_IF_ERROR( + GetStaticInputVector(ng_op_map, op, 0, static_input_map, &split_dim_vec)); + int split_dim = split_dim_vec[0] + (split_dim_vec[0] < 0 ? (int64_t)rank : 0); + auto ng_split_dim = ConstructNgNode( + op->name(), ng::element::u64, ng::Shape{}, split_dim); + auto ng_split = make_shared(ng_input, ng_split_dim, num_split); + + for (int i = 0; i < num_split; ++i) { + auto out = ng_split->output(i); + Builder::SetTracingInfo(op->name(), out); + SaveNgOp(ng_op_map, op->name(), out); + } + return Status::OK(); +} + +static Status TranslateSplitVOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_split_length, ng_split_dim; + + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + + ng::Shape shape = ng_input.get_shape(); + int rank = shape.size(); + + std::vector split_dim_vec; + TF_RETURN_IF_ERROR( + GetStaticInputVector(ng_op_map, op, 2, static_input_map, &split_dim_vec)); + // there should be at least one element specified as axis and not more than + // one as axis is 0-D + if (split_dim_vec.size() != 1) { + return errors::InvalidArgument( + "split_dim_tensor must have " + "exactly one element."); + } + TF_RETURN_IF_ERROR(CheckAxisDimInRange(split_dim_vec, rank)); + int split_dim = split_dim_vec[0] + (split_dim_vec[0] < 0 ? (int64_t)rank : 0); + ng_split_dim = ConstructNgNode(op->name(), ng::element::i32, + ng::Shape{}, split_dim); + + std::vector split_lengths_vec; + TF_RETURN_IF_ERROR( + GetStaticInputVector(ng_op_map, op, 1, static_input_map, &split_lengths_vec)); + + // length: Length of size_splits + int length = 0; + int idx = -1; + + // Find out the total length of the splits and locate -1 's index, if any + bool has_one_neg = false; + for (size_t i = 0; i < split_lengths_vec.size(); ++i) { + if (split_lengths_vec[i] != -1) { + length += split_lengths_vec[i]; + } else { + if (has_one_neg) { + return errors::InvalidArgument("size_splits can only have one -1"); + } else { + idx = i; + has_one_neg = true; + } + } + } + + // Size splits must sum to the dimension of value along split_dim + if (idx > 0) { + split_lengths_vec[idx] = shape[split_dim] - length; + } + + if ((!has_one_neg && length != int32_t(shape[split_dim])) || + (has_one_neg && split_lengths_vec[idx] < 0)) { + return errors::InvalidArgument( + "The length of size_splits must sum to the value of the dimension " + "along split_dim"); + } + + ng_split_length = ConstructNgNode( + op->name(), ng::element::i32, ng::Shape{split_lengths_vec.size()}, + split_lengths_vec); + + if (split_lengths_vec.size() != 1) { + auto ng_split = make_shared(ng_input, ng_split_dim, + ng_split_length); + for (size_t i = 0; i < split_lengths_vec.size(); ++i) { + auto out = ng_split->output(i); + Builder::SetTracingInfo(op->name(), out); + SaveNgOp(ng_op_map, op->name(), out); + } + } else { + SaveNgOp(ng_op_map, op->name(), ng_input); + } + + return Status::OK(); +} + +static Status TranslateSquareOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + return TranslateUnaryOp( + op, static_input_map, ng_op_map, [&op](ng::Output n) { + return ConstructNgNode(op->name(), n, n); + }); +} + +static Status TranslateSqueezeOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + size_t input_dims = ng_input.get_shape().size(); + + std::vector tf_axis; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "squeeze_dims", &tf_axis)); + + // If input dimension is negative, make it positive + for (size_t i = 0; i < tf_axis.size(); i++) { + tf_axis[i] = tf_axis[i] < 0 ? (int32_t)(input_dims) + tf_axis[i] : tf_axis[i]; + } + + auto ng_const = ConstructNgNode( + op->name(), ng::element::i32, ng::Shape{tf_axis.size()}, tf_axis); + + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, ng_const)); + return Status::OK(); +} + +static Status TranslateStridedSliceOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + + int32_t begin_mask, end_mask, new_axis_mask, shrink_axis_mask, ellipsis_mask; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "begin_mask", &begin_mask)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "end_mask", &end_mask)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "new_axis_mask", &new_axis_mask)); + TF_RETURN_IF_ERROR( + GetNodeAttr(op->attrs(), "shrink_axis_mask", &shrink_axis_mask)); + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "ellipsis_mask", &ellipsis_mask)); + + NGRAPH_VLOG(5) << "strided slice attributes: " + << " begin mask: " << begin_mask << " end mask: " << end_mask + << " new axis mask: " << new_axis_mask + << " shrink axis mask: " << shrink_axis_mask + << " ellipsis mask: " << ellipsis_mask; + + std::vector begin_vec; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &begin_vec)); + std::vector end_vec; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 2, static_input_map, &end_vec)); + std::vector stride_vec; + TF_RETURN_IF_ERROR( + GetStaticInputVector(ng_op_map, op, 3, static_input_map, &stride_vec)); + + auto begin = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{begin_vec.size()}, begin_vec); + auto end = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{end_vec.size()}, end_vec); + auto strides = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{stride_vec.size()}, stride_vec); + + auto mask_to_vec = [](int32_t mask) { + auto length = sizeof(mask) * CHAR_BIT; + std::vector vec(length, 0); + if (mask == 0) { + return vec; + } + for (size_t i = 0; i < length; ++i) { + if ((unsigned char)(mask >> i & 0x01) == 1) { + vec[i] = 1; + } + } + return vec; + }; + + SaveNgOp( + ng_op_map, op->name(), + ConstructNgNode( + op->name(), ng_input, begin, end, strides, mask_to_vec(begin_mask), + mask_to_vec(end_mask), mask_to_vec(new_axis_mask), + mask_to_vec(shrink_axis_mask), mask_to_vec(ellipsis_mask))); + return Status::OK(); +} + +static Status TranslateTileOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_multiples; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_multiples)); + + std::vector multiples; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &multiples)); + + auto ng_repeats = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{multiples.size()}, multiples); + SaveNgOp(ng_op_map, op->name(), + ConstructNgNode(op->name(), ng_input, ng_repeats)); + return Status::OK(); +} + +// Translate TopKV2 Op using ngraph core op TopK +static Status TranslateTopKV2Op( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + + TF_RETURN_IF_ERROR(ValidateInputCount(op, 2)); + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + + // axis along which to compute top k indices + int64_t k_axis = ng_input.get_shape().size() - 1; + + // scalar input tensor specifying how many max/min elts should be computed + // CPU backend only supports element type i64 + std::vector ng_k_vec; + TF_RETURN_IF_ERROR(GetStaticInputVector(ng_op_map, op, 1, static_input_map, &ng_k_vec)); + auto ng_k = ConstructNgNode(op->name(), ng::element::i64, + ng::Shape{}, ng_k_vec[0]); + + std::string mode = "max"; + + std::string sort = "value"; + bool sorted = true; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "sorted", &sorted)); + if (!sorted) { + sort = "index"; + } + + auto ng_result = + std::make_shared(ng_input, ng_k, k_axis, mode, sort); + + ng::Output ng_values = ng_result->output(0); + Builder::SetTracingInfo(op->name(), ng_values); + ng::Output ng_indices = ng_result->output(1); + Builder::SetTracingInfo(op->name(), ng_indices); + + SaveNgOp(ng_op_map, op->name(), ng_values); + SaveNgOp(ng_op_map, op->name(), ng_indices); + + return Status::OK(); +} + +static Status TranslateTransposeOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_input, ng_permutation; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input, ng_permutation)); + SaveNgOp(ng_op_map, op->name(), ConstructNgNode( + op->name(), ng_input, ng_permutation)); + return Status::OK(); +} + +static Status TranslateUnpackOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + TF_RETURN_IF_ERROR(ValidateInputCount(op, 1)); + + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, op, 0, ng_input)); + int32_t tf_axis; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "axis", &tf_axis)); + int32_t num_outputs; + TF_RETURN_IF_ERROR(GetNodeAttr(op->attrs(), "num", &num_outputs)); + + auto input_shape = ng_input.get_shape(); + auto rank = input_shape.size(); + for (int i = 0; i < num_outputs; ++i) { + std::vector begin(rank, 0); + std::vector end(rank, 0); + begin[tf_axis] = i; + end[tf_axis] = i + 1; + auto ng_begin = ConstructNgNode( + op->name(), ng::element::i64, ng::Shape{begin.size()}, begin); + auto ng_end = ConstructNgNode(op->name(), ng::element::i64, + ng::Shape{end.size()}, end); + std::vector begin_mask(rank, 1); + begin_mask[tf_axis] = 0; + std::vector end_mask(rank, 1); + end_mask[tf_axis] = 0; + std::vector new_axis_mask(rank, 0); + std::vector shrink_axis_mask(rank, 0); + shrink_axis_mask[tf_axis] = 1; + auto slice = ConstructNgNode( + op->name(), ng_input, ng_begin, ng_end, begin_mask, end_mask, + new_axis_mask, shrink_axis_mask); + SaveNgOp(ng_op_map, op->name(), slice); + } + return Status::OK(); +} + +static Status TranslateXdivyOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_x, ng_y; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_x, ng_y)); + auto zero = + ConstructNgNode(op->name(), ng_x.get_element_type(), + ngraph::Shape{}, std::vector({0})); + auto x_is_zero = ConstructNgNode(op->name(), ng_x, zero); + auto ng_xdivy = ConstructNgNode(op->name(), ng_x, ng_y); + SaveNgOp(ng_op_map, op->name(), ConstructNgNode( + op->name(), x_is_zero, ng_x, ng_xdivy)); + return Status::OK(); +} + +static Status TranslateSelectOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input1, ng_input2, ng_input3; + TF_RETURN_IF_ERROR( + GetInputNodes(ng_op_map, op, ng_input1, ng_input2, ng_input3)); + auto ng_select = ConstructNgNode(op->name(), ng_input1, + ng_input2, ng_input3); + SaveNgOp(ng_op_map, op->name(), ng_select); + return Status::OK(); +} + +static Status TranslateWhereOp( + const TFNodeDecoder* op, const std::vector& static_input_map, + Builder::OpMap& ng_op_map) { + ng::Output ng_cond; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_cond)); + auto non_zero = ConstructNgNode(op->name(), ng_cond); + auto transpose_order = ConstructNgNode( + op->name(), ngraph::element::i64, ngraph::Shape{2}, + std::vector({1, 0})); + SaveNgOp(ng_op_map, op->name(), ConstructNgNode( + op->name(), non_zero, transpose_order)); + return Status::OK(); +} + +static Status TranslateZerosLikeOp(const TFNodeDecoder* op, + const std::vector&, + Builder::OpMap& ng_op_map) { + ng::Output ng_input; + TF_RETURN_IF_ERROR(GetInputNodes(ng_op_map, op, ng_input)); + + ng::Shape input_shape = ng_input.get_shape(); + std::vector const_values(ng::shape_size(input_shape), "0"); + auto ng_result = ConstructNgNode( + op->name(), ng_input.get_element_type(), input_shape, const_values); + SaveNgOp(ng_op_map, op->name(), ng_result); + return Status::OK(); +} + +const static std::map< + const string, + const function&, + Builder::OpMap&)>> + TRANSLATE_OP_MAP{ + {"Abs", TranslateUnaryOp}, + {"Acos", TranslateUnaryOp}, + {"Acosh", TranslateUnaryOp}, + {"Add", TranslateBinaryOp}, + {"AddN", TranslateAddNOp}, + {"AddV2", TranslateBinaryOp}, + {"Any", TranslateDirectReduceOp}, + {"All", TranslateDirectReduceOp}, + {"ArgMax", TranslateArgMaxOp}, + {"ArgMin", TranslateArgMinOp}, + {"Asin", TranslateUnaryOp}, + {"Asinh", TranslateUnaryOp}, + {"Atan", TranslateUnaryOp}, + {"Atanh", TranslateUnaryOp}, + {"AvgPool", TranslateAvgPoolOp}, + {"BiasAdd", TranslateBiasAddOp}, + {"Cast", TranslateCastOp}, + {"Ceil", TranslateUnaryOp}, + {"ConcatV2", TranslateConcatV2Op}, + {"Const", TranslateConstOp}, + {"Conv2D", TranslateConv2DOp}, + {"Conv2DBackpropInput", TranslateConv2DBackpropInputOp}, + {"Conv3D", TranslateConv3DOp}, + {"Cos", TranslateUnaryOp}, + {"Cosh", TranslateUnaryOp}, + {"Cumsum", TranslateCumsumOp}, + {"DepthToSpace", TranslateDepthToSpaceOp}, + {"DepthwiseConv2dNative", TranslateDepthwiseConv2dNativeOp}, + {"Equal", TranslateBinaryOp}, + {"Exp", TranslateUnaryOp}, + {"ExpandDims", TranslateExpandDimsOp}, + {"Fill", TranslateFillOp}, + {"Floor", TranslateUnaryOp}, + {"FloorDiv", TranslateFloorDivOp}, + {"FloorMod", TranslateBinaryOp}, + {"FusedBatchNorm", TranslateFusedBatchNormOp}, + {"FusedBatchNormV2", TranslateFusedBatchNormOp}, + {"FusedBatchNormV3", TranslateFusedBatchNormOp}, + {"Gather", TranslateGatherOp}, + {"GatherV2", TranslateGatherV2Op}, + {"_FusedConv2D", TranslateFusedConv2DOp}, + {"_FusedMatMul", TranslateFusedMatMulOp}, + {"Greater", TranslateBinaryOp}, + {"GreaterEqual", TranslateBinaryOp}, + {"Identity", TranslateIdentityOp}, + {"IsFinite", TranslateIsFiniteOp}, + {"L2Loss", TranslateL2LossOp}, + {"LogSoftmax", TranslateLogSoftmaxOp}, + {"Less", TranslateBinaryOp}, + {"LessEqual", TranslateBinaryOp}, + {"Log", TranslateUnaryOp}, + {"Log1p", TranslateLog1pOp}, + {"LogicalAnd", TranslateBinaryOp}, + {"LogicalNot", TranslateUnaryOp}, + {"LogicalOr", TranslateBinaryOp}, + {"LRN", TranslateLRNOp}, + {"MatMul", TranslateMatMulOp}, + {"Max", TranslateDirectReduceOp}, + {"Maximum", TranslateBinaryOp}, + {"MaxPool", TranslateMaxPoolOp<2>}, + {"MaxPool3D", TranslateMaxPoolOp<3>}, + {"NonMaxSuppressionV2", TranslateNonMaxSuppressionV2Op}, + {"Mean", TranslateDirectReduceOp}, + {"Min", TranslateDirectReduceOp}, + {"Minimum", TranslateBinaryOp}, + {"MirrorPad", TranslatePadOp}, + {"Mul", TranslateBinaryOp}, + {"Mod", TranslateBinaryOp}, + {"Neg", TranslateUnaryOp}, + {"NotEqual", TranslateBinaryOp}, + // Do nothing! NoOps sometimes get placed on nGraph for bureaucratic + // reasons, but they have no data flow inputs or outputs. + {"NoOp", [](const TFNodeDecoder*, const std::vector&, + Builder::OpMap&) { return Status::OK(); }}, + {"OneHot", TranslateOneHotOp}, + {"Pack", TranslatePackOp}, + {"Pad", TranslatePadOp}, + {"PadV2", TranslatePadOp}, + {"Pow", TranslateBinaryOp}, + // PreventGradient is just Identity in dataflow terms, so reuse that. + {"PreventGradient", TranslateIdentityOp}, + {"Prod", TranslateDirectReduceOp}, + {"Range", TranslateRangeOp}, + {"Rank", TranslateRankOp}, + {"RealDiv", TranslateBinaryOp}, + {"Reciprocal", TranslateReciprocalOp}, + {"Relu", TranslateUnaryOp}, + {"Relu6", TranslateRelu6Op}, + {"Reshape", TranslateReshapeOp}, + {"Rsqrt", TranslateRsqrtOp}, + {"Select", TranslateSelectOp}, + {"SelectV2", TranslateSelectOp}, + {"Shape", TranslateShapeOp}, + {"Sigmoid", TranslateUnaryOp}, + {"Sin", TranslateUnaryOp}, + {"Sinh", TranslateUnaryOp}, + {"Size", TranslateSizeOp}, + {"Sign", TranslateUnaryOp}, + {"Slice", TranslateSliceOp}, + {"Snapshot", TranslateIdentityOp}, + {"Softmax", TranslateSoftmaxOp}, + {"Softplus", TranslateUnaryOp}, + {"SpaceToDepth", TranslateSpaceToDepthOp}, + {"Split", TranslateSplitOp}, + {"SplitV", TranslateSplitVOp}, + {"Sqrt", TranslateUnaryOp}, + {"Square", TranslateSquareOp}, + {"SquaredDifference", TranslateBinaryOp}, + {"Squeeze", TranslateSqueezeOp}, + {"StridedSlice", TranslateStridedSliceOp}, + {"Sub", TranslateBinaryOp}, + {"Sum", TranslateDirectReduceOp}, + {"Tan", TranslateUnaryOp}, + {"Tanh", TranslateUnaryOp}, + {"Tile", TranslateTileOp}, + {"TopKV2", TranslateTopKV2Op}, + {"Transpose", TranslateTransposeOp}, + {"Unpack", TranslateUnpackOp}, + {"Where", TranslateWhereOp}, + {"Xdivy", TranslateXdivyOp}, + {"ZerosLike", TranslateZerosLikeOp}}; + + + +class NodeProtoWrapper : public TFNodeDecoder +{ + const NodeDef* node_def; + const GraphDef* graph_def; + std::vector* nodes; +public: + + NodeProtoWrapper(const NodeDef* _node_def, const GraphDef* _graph_def, std::vector* _nodes) : + node_def(_node_def), graph_def(_graph_def), nodes(_nodes) {} + +#define GET_ATTR_VALUE(TYPE, FIELD) virtual void getAttrValue (const char* name, TYPE* x) const override \ + { *x = node_def->attr().at(name).FIELD(); } +#define GET_ATTR_VALUE_VECTOR(TYPE, FIELD) virtual void getAttrValue (const char* name, std::vector* x) const override \ + {\ + const auto& list = node_def->attr().at(name).list();\ + x->reserve(/*node_def->attr().at(name).FIELD##_size()*/list.FIELD##_size());\ + for(int i = 0; i < list.FIELD##_size(); ++i)\ + {\ + x->push_back(list.FIELD(i));\ + }\ + } + + GET_ATTR_VALUE_VECTOR(int32_t, i) + GET_ATTR_VALUE_VECTOR(float, f) + //virtual void getAttrValue (const char* name, std::vector* x) const override { NGRAPH_TF_FE_NOT_IMPLEMENTED; } + //virtual void getAttrValue (const char* name, std::vector* x) const override { NGRAPH_TF_FE_NOT_IMPLEMENTED; } + GET_ATTR_VALUE(int32_t, i) + + virtual void getAttrValue (const char* name, DataType* x) const override + { + *x = node_def->attr().at(name).type(); + } + + virtual void getAttrValue (const char* name, ngraph::PartialShape* x) const override { + TFTensorShapeToNGraphShape(node_def->attr().at(name).shape(), x); + } + + GET_ATTR_VALUE(std::string, s) + GET_ATTR_VALUE(bool, b) + GET_ATTR_VALUE(long int, i) + GET_ATTR_VALUE(float, f) + + virtual void getAttrValue (const char* name, std::vector* x) const override { NGRAPH_TF_FE_NOT_IMPLEMENTED; } + + // a way to read Const value as a tensor + virtual void getAttrValue (const char* name, TensorWrapper** x) const override + { + // TODO: use std::shared_ptr! memory is lost! + *x = new TensorWrapper(&node_def->attr().at(name).tensor()); + } + + virtual std::string op () const override + { + return node_def->op(); + } + + virtual int32_t num_inputs () const override { return node_def->input_size(); } + + virtual std::string name () const override + { + return node_def->name(); + } + + virtual std::string type_string () const override + { + return node_def->op(); + } + + virtual Status input_node (size_t index, TFNodeDecoder const * *) const override { NGRAPH_TF_FE_NOT_IMPLEMENTED; } + + virtual Status input_node (size_t index, TFNodeDecoder const * * retnode, size_t* outputPortIndex) const override + { + std::string input_name = node_def->input(index); + if(input_name.find(':') != std::string::npos) { + NGRAPH_TF_FE_NOT_IMPLEMENTED; + } + // TODO: don't search linearly every time!!! + for(auto node: *nodes) + { + if(node->name() == input_name) + { + *retnode = node; + *outputPortIndex = 0; + return Status::OK(); + } + } + return Status("Node is not found " + input_name + " when searched as an input for node " + name()); + } + + virtual DataType input_type (size_t index) const override { NGRAPH_TF_FE_NOT_IMPLEMENTED; } + virtual DataType output_type (size_t index) const override { NGRAPH_TF_FE_NOT_IMPLEMENTED; } + + virtual bool IsSink () const override + { + // TODO: recognize special op in TF runtime; don't know similar node for proto graph representation + return false; + } + + virtual bool IsSource () const override + { + // TODO: populate with other source operation types + return node_def->op() == "Placeholder"; + } + + virtual bool IsControlFlow () const override + { + // TODO + return false; + } + + virtual std::string DebugString () const override + { + return node_def->op() + "(with name " + node_def->name() + ")"; + } + + virtual bool IsArg () const override + { + // TODO + return IsSource(); + } + + virtual bool IsRetval () const override + { + // TODO + return IsSink(); + } +}; + +void PopulateNodesTopologicallySorted (const GraphDef* input_graph, std::vector* result) +{ + // WARNING! We suppose that input_graph contains nodes in topologically sorted order + // TODO: sort it if it is not the case + + result->reserve(input_graph->node_size()); + for(int i = 0; i < input_graph->node_size(); ++i) + { + result->push_back(new NodeProtoWrapper(&input_graph->node(i), input_graph, result)); + } +} + +Status Builder::TranslateGraph( + const std::map& inputs, + const std::vector& static_input_map, const GraphDef* input_graph, + const std::string name, std::shared_ptr& ng_function) { + // + // We will visit ops in topological order. + // + // ought to be `const TFNodeDecoder*`, but GetReversePostOrder doesn't use `const` + + std::vector ordered; + //GetReversePostOrder(*input_graph, &ordered, NodeComparatorName()); + PopulateNodesTopologicallySorted(input_graph, &ordered); + + // + // Split ops into params, retvals, and all others. + // + vector tf_params; + vector tf_ret_vals; + vector tf_ops; + + for (const auto n : ordered) { +#if 0 + // TODO: Investigate why do we need it + if (n->IsSink() || n->IsSource()) { + continue; + } +#endif + + if (n->IsControlFlow()) { + return errors::Unimplemented( + "Encountered a control flow op in the nGraph bridge: " + + n->DebugString()); + } + + if (n->IsArg()) { + tf_params.push_back(n); + } else if (n->IsRetval()) { + tf_ret_vals.push_back(n); + } else { + tf_ops.push_back(n); + } + } + + // + // The op map holds a mapping from TensorFlow op names (strings) to + // vector of generated nGraph Output. + // + Builder::OpMap ng_op_map; + + // + // Populate the parameter list, and also put parameters into the op map. + // + std::cerr << "[ INFO ] Detected " << tf_params.size() << " parameters\n"; + ng::ParameterVector ng_parameter_list(tf_params.size()); + // enumerate placeholders in some random order, count them and use counter as an index + int index = 0; + + for (auto parm : tf_params) { + DataType dtype; + // TODO: replace dtype by T when converting Arg + if (GetNodeAttr(parm->attrs(), "dtype", &dtype) != Status::OK()) { + return errors::InvalidArgument("No data type defined for _Arg"); + } + + // TODO: use this code for Arg + //if (GetNodeAttr(parm->attrs(), "index", &index) != Status::OK()) { + // return errors::InvalidArgument("No index defined for _Arg"); + //} + + ng::element::Type ng_et; + TF_RETURN_IF_ERROR(TFDataTypeToNGraphElementType(dtype, &ng_et)); + + ng::PartialShape ng_shape; + auto overridenInputShape = inputs.find(parm->name()); + if(overridenInputShape == inputs.end()) { + try { + GetNodeAttr(parm->attrs(), "shape", &ng_shape); + } + catch (google::protobuf::FatalException) { + // suppose there is no shape + // TODO: do it in a good way + } + } else { + ng_shape = overridenInputShape->second; + } + +#if 0 + string prov_tag; + GetNodeAttr(parm->attrs(), "_prov_tag", &prov_tag); +#endif + auto ng_param = + ConstructNgNode(parm->name(), ng_et, ng_shape); + SaveNgOp(ng_op_map, parm->name(), ng_param); + ng_parameter_list[index] = + ngraph::as_type_ptr(ng_param.get_node_shared_ptr()); + + index++; + } + + // + // Now create the nGraph ops from TensorFlow ops. + // + for (auto op : tf_ops) { + NGRAPH_VLOG(2) << "Constructing op " << op->name() << " which is " + << op->type_string() << "\n"; + + const function&, + Builder::OpMap&)>* op_fun; + + try { + op_fun = &(TRANSLATE_OP_MAP.at(op->type_string())); + } catch (const std::out_of_range&) { + // ----------------------------- + // Catch-all for unsupported ops + // ----------------------------- + NGRAPH_VLOG(3) << "No translation handler registered for op: " + << op->name() << " (" << op->type_string() << ")"; + NGRAPH_VLOG(3) << op->DebugString(); + return errors::InvalidArgument( + "No translation handler registered for op: " + op->name() + " (" + + op->type_string() + ")\n" + op->DebugString()); + } + + try { + TF_RETURN_IF_ERROR((*op_fun)(op, static_input_map, ng_op_map)); + } catch (const std::exception& e) { + return errors::Internal("Unhandled exception in op handler: " + op->name() + + " (" + op->type_string() + ")\n" + + op->DebugString() + "\n" + "what(): " + + e.what()); + } + } + + // + // Populate the result list. + // + ng::ResultVector ng_result_list(tf_ret_vals.size()); + + for (auto n : tf_ret_vals) { + // Make sure that this _Retval only has one input node. + if (n->num_inputs() != 1) { + return errors::InvalidArgument("_Retval has " + to_string(n->num_inputs()) + + " inputs, should have 1"); + } + + int index; + if (GetNodeAttr(n->attrs(), "index", &index) != Status::OK()) { + return errors::InvalidArgument("No index defined for _Retval"); + } + + ng::Output result; + TF_RETURN_IF_ERROR(GetInputNode(ng_op_map, n, 0, result)); + auto ng_result = ConstructNgNode(n->name(), result); + ng_result_list[index] = + ngraph::as_type_ptr(ng_result.get_node_shared_ptr()); + } + + // Find all terminal nodes in ngraph graph to complete list of results + for(auto op: tf_ops) + { + auto p = ng_op_map.find(op->name()); + if(p != ng_op_map.end()) + { + for(auto output: p->second) + { + if(output.get_target_inputs().empty()) + ng_result_list.push_back(std::make_shared(output)); + } + } + } + + // + // Create the nGraph function. + // + ng_function = + make_shared(ng_result_list, ng_parameter_list, name); + + // + // Apply additional passes on the nGraph function here. + // + { +#if 0 + ngraph::pass::Manager passes; + if (util::GetEnv("NGRAPH_TF_CONSTANT_FOLDING") == "1") { + passes.register_pass(); + } + if (util::GetEnv("NGRAPH_TF_TRANSPOSE_SINKING") != "0") { + passes.register_pass(); + } + passes.run_passes(ng_function); +#endif + } + NGRAPH_VLOG(5) << "Done with passes"; + // + // Request row-major layout on results. + // + for (auto result : ng_function->get_results()) { + result->set_needs_default_layout(true); + } + NGRAPH_VLOG(5) << "Done with translations"; + return Status::OK(); +} + +} // namespace ngraph_bridge +} // namespace tensorflow diff --git a/ngraph/frontend/tensorflow/src/ngraph_builder.h b/ngraph/frontend/tensorflow/src/ngraph_builder.h new file mode 100644 index 00000000000000..a090de273e731a --- /dev/null +++ b/ngraph/frontend/tensorflow/src/ngraph_builder.h @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright 2017-2020 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. + *******************************************************************************/ +#ifndef NGRAPH_TF_BRIDGE_BUILDER_H_ +#define NGRAPH_TF_BRIDGE_BUILDER_H_ + +#include +#include +#include +#include +#include +#include + +// TODO: remove explicit proto dependency from this common header +#include "graph.pb.h" + +#include "ngraph/ngraph.hpp" + +namespace tensorflow { + +// Stub for TF class +class Status +{ +public: + int status = 0; + std::string message; + + static Status OK () { return Status(); } + + Status (const std::string& x) : message(x), status(1) {} + Status () {} +}; + +inline bool operator!= (const Status& x, const Status& y) +{ + return x.status != y.status; +} + +inline std::ostream& operator<< (std::ostream& out, const Status& s) +{ + return out << s.message; +} + +#define TF_RETURN_IF_ERROR(S) if((S).status != 0)return S; + +// Stub for tf error system +class errors +{ +public: + + static Status InvalidArgument (const std::string& x) + { + return Status("InvalidArgument: " + x); + } + + static Status Internal (const std::string& x) + { + return Status("Internal: " + x); + } + + static Status Unimplemented (const std::string& x) + { + return Status("Unimplemented: " + x); + } +}; + + +namespace ngraph_bridge { + + class TensorWrapper; + +// ABI-free wrapper for TF node +class TFNodeDecoder +{ +public: + + // a hack to minimize amount of code + TFNodeDecoder& attrs () const { return const_cast(*this); } + virtual void getAttrValue (const char* name, std::vector* x) const = 0; + virtual void getAttrValue (const char* name, std::vector* x) const = 0; + virtual void getAttrValue (const char* name, int32_t* x) const = 0; + virtual void getAttrValue (const char* name, DataType* x) const = 0; + virtual void getAttrValue (const char* name, std::string* x) const = 0; + virtual void getAttrValue (const char* name, bool* x) const = 0; + virtual void getAttrValue (const char* name, long int* x) const = 0; + virtual void getAttrValue (const char* name, float* x) const = 0; + virtual void getAttrValue (const char* name, std::vector* x) const = 0; + virtual void getAttrValue (const char* name, ngraph::PartialShape* x) const = 0; + + virtual std::string op () const = 0; + + // a way to read Const value as a tensor + virtual void getAttrValue (const char* name, TensorWrapper** x) const = 0; + + // Signed integer as an output type to avoid massive warnings about signed/unsided comparison + virtual int32_t num_inputs () const = 0; + virtual std::string name () const = 0; + virtual bool IsArg () const = 0; + virtual std::string type_string () const = 0; + + virtual Status input_node (size_t index, TFNodeDecoder const * *) const = 0; + virtual Status input_node (size_t index, TFNodeDecoder const * *, size_t* outputPortIndex) const = 0; + virtual DataType input_type (size_t index) const = 0; + virtual DataType output_type (size_t index) const = 0; + + virtual bool IsSink () const = 0; + virtual bool IsSource () const = 0; + virtual bool IsControlFlow () const = 0; + virtual std::string DebugString () const = 0; + virtual bool IsRetval () const = 0; +}; + +// TODO: separate interface from proto implementation; here is a proto implementation +class TensorWrapper +{ +public: + + const TensorProto* tensor_def; + + TensorWrapper (const TensorProto* _tensor_def) : tensor_def(_tensor_def) {} + + // a hack to minimize amount of code + TensorWrapper &attrs() const { return const_cast(*this); } + + //virtual void getAttrValue(const char *name, std::vector &x) = 0; + + template + std::vector flat () const; + + size_t NumElements () const; + + DataType dtype () const; +}; + +template +Status GetNodeAttr (TFNodeDecoder& attrs, const char* attr_name, T* result) +{ + attrs.getAttrValue(attr_name, result); + return Status::OK(); +} + +#if 0 +#define NGRAPH_VLOG(I) std::cerr +#else +#define NGRAPH_VLOG(I) std::ostringstream() +#endif + + + class Builder { + public: + static Status TranslateGraph( + const std::map& inputs, + const std::vector& static_input_map, const GraphDef* tf_graph, + const std::string name, std::shared_ptr& ng_function); + + using OpMap = std::unordered_map>>; + using ConstMap = std::map< + DataType, + std::pair&)>, + const ngraph::element::Type>>; + static const Builder::ConstMap& TF_NGRAPH_CONST_MAP(); + + template + static void MakePadding(const std::string& tf_padding_type, + const ngraph::Shape& ng_image_shape, + const ngraph::Shape& ng_kernel_shape, + const ngraph::Strides& ng_strides, + const ngraph::Shape& ng_dilations, + T& ng_padding_below, T& ng_padding_above) { + if (tf_padding_type == "SAME") { + ngraph::Shape img_shape = {0, 0}; + img_shape.insert(img_shape.end(), ng_image_shape.begin(), + ng_image_shape.end()); + ngraph::infer_auto_padding(img_shape, ng_kernel_shape, ng_strides, + ng_dilations, ngraph::op::PadType::SAME_UPPER, + ng_padding_above, ng_padding_below); + } else if (tf_padding_type == "VALID") { + ng_padding_below.assign(ng_image_shape.size(), 0); + ng_padding_above.assign(ng_image_shape.size(), 0); + } + } + + // This function is used to trace which ng node came from which tf node + // It does 3 things: + // 1. Attaches provenance tags. This is guaranteed to propagate the tag info + // to all nodes. + // The next 2 are not guaranteed to be present for all nodes. + // But when present they are correct and agree with provenance tags + // 2. Attaches friendly names. + // 3. Prints a log if NGRAPH_TF_LOG_PLACEMENT=1 + static void SetTracingInfo(const std::string& op_name, + const ngraph::Output ng_node); +}; + +inline std::string StrJoin (const std::vector& strs, const char* sep) +{ + std::ostringstream str; + std::copy(strs.begin(), strs.end(), std::ostream_iterator(str, sep)); + return str.str(); +} + +} // namespace ngraph_bridge +} // namespace tensorflow + +#endif diff --git a/ngraph/frontend/tensorflow/src/ngraph_conversions.cpp b/ngraph/frontend/tensorflow/src/ngraph_conversions.cpp new file mode 100644 index 00000000000000..b018041bf5e1e5 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/ngraph_conversions.cpp @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright 2017-2020 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 "ngraph_conversions.h" + +namespace tensorflow { +namespace ngraph_bridge { + +void NHWCtoNCHW(const std::string& op_name, bool is_nhwc, + ngraph::Output& node) { + if (is_nhwc) { + auto rank = node.get_shape().size(); + if (rank == 4) { + Transpose<0, 3, 1, 2>(node); + } else if (rank == 5) { + Transpose3D<0, 4, 1, 2, 3>(node); + } + Builder::SetTracingInfo(op_name, node); + } +} + +void NCHWtoNHWC(const std::string& op_name, bool is_nhwc, + ngraph::Output& node) { + if (is_nhwc) { + auto rank = node.get_shape().size(); + if (rank == 4) { + Transpose<0, 2, 3, 1>(node); + } else if (rank == 5) { + Transpose3D<0, 2, 3, 4, 1>(node); + } + Builder::SetTracingInfo(op_name, node); + } +} + + + Status TFDataTypeToNGraphElementType(DataType tf_dt, + ngraph::element::Type* ng_et) { + switch (tf_dt) { + case DataType::DT_FLOAT: + *ng_et = ngraph::element::f32; + break; + case DataType::DT_DOUBLE: + *ng_et = ngraph::element::f64; + break; + case DataType::DT_INT32: + *ng_et = ngraph::element::i32; + break; + case DataType::DT_UINT8: + *ng_et = ngraph::element::u8; + break; + case DataType::DT_INT8: + *ng_et = ngraph::element::i8; + break; + case DataType::DT_UINT16: + *ng_et = ngraph::element::u16; + break; + case DataType::DT_INT64: + *ng_et = ngraph::element::i64; + break; + case DataType::DT_UINT32: + *ng_et = ngraph::element::u32; + break; + case DataType::DT_UINT64: + *ng_et = ngraph::element::u64; + break; + case DataType::DT_BOOL: + *ng_et = ngraph::element::boolean; + break; + case DataType::DT_QINT8: + *ng_et = ngraph::element::i8; + break; + case DataType::DT_QUINT8: + *ng_et = ngraph::element::u8; + break; + case DataType::DT_QINT32: + *ng_et = ngraph::element::i32; + break; + case DataType::DT_BFLOAT16: + *ng_et = ngraph::element::bf16; + break; + case DataType::DT_HALF: + *ng_et = ngraph::element::f16; + break; + default: + return errors::Unimplemented("Unsupported TensorFlow data type: " + + DataType_Name(tf_dt)); + } + return Status::OK(); + } + + Status TFTensorShapeToNGraphShape(const ::tensorflow::TensorShapeProto& tf_shape, + ngraph::PartialShape* ng_shape) { + std::vector dims; + for (int i = 0; i < tf_shape.dim_size(); i++) { + dims.push_back(tf_shape.dim(i).size()); + } + *ng_shape = ngraph::PartialShape(dims); + return Status::OK(); + } + +} // namespace ngraph_bridge +} // namespace tensorflow diff --git a/ngraph/frontend/tensorflow/src/ngraph_conversions.h b/ngraph/frontend/tensorflow/src/ngraph_conversions.h new file mode 100644 index 00000000000000..b7f456b66f4c3f --- /dev/null +++ b/ngraph/frontend/tensorflow/src/ngraph_conversions.h @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright 2017-2020 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. + *******************************************************************************/ + +#ifndef NGRAPH_TF_BRIDGE_CONVERSIONS_H_ +#define NGRAPH_TF_BRIDGE_CONVERSIONS_H_ +#pragma once + +#include + +//#include "logging/ngraph_log.h" +#include "default_opset.h" +#include "ngraph_builder.h" + +namespace tensorflow { +namespace ngraph_bridge { + + // Converts a TensorFlow DataType to an nGraph element::Type. Returns +// errors::Unimplemented if the element type is not supported by nGraph +// Core. Otherwise returns Status::OK(). + Status TFDataTypeToNGraphElementType(DataType tf_dt, + ngraph::element::Type* ng_et); + + Status TFTensorShapeToNGraphShape(const ::tensorflow::TensorShapeProto& tf_shape, + ngraph::PartialShape* ng_shape); + +template +void Transpose(ngraph::Output& node) { + static_assert(a < 4 && b < 4 && c < 4 && d < 4, + "Number of dimensions cannot exceed 4"); + static_assert(a != b && a != c && a != d && b != c && b != d && c != d, + "Dimensions indices cannot be equal"); + auto& s = node.get_shape(); + ngraph::Shape reshaped_shape{s[a], s[b], s[c], s[d]}; + ngraph::Shape transpose_order{a, b, c, d}; + NGRAPH_VLOG(3) << "transposing " << ngraph::join(s) << " to " + << ngraph::join(reshaped_shape) << " axis-order " + << ngraph::join(transpose_order); + auto input_order = std::make_shared( + ngraph::element::u64, ngraph::Shape{transpose_order.size()}, + transpose_order); + node = std::make_shared(node, input_order); +} + +template +void Transpose(std::shared_ptr& node) { + Transpose(node->get_default_output()); +} + +template +void Transpose3D(ngraph::Output& node) { + static_assert(a < 5 && b < 5 && c < 5 && d < 5 && e < 5, + "Number of dimensions cannot exceed 5"); + static_assert(a != b && a != c && a != d && a != e && b != c && b != d && + b != e && c != d && c != e && d != e, + "Dimensions indices cannot be equal"); + auto& s = node.get_shape(); + ngraph::Shape reshaped_shape{s[a], s[b], s[c], s[d], s[e]}; + ngraph::Shape transpose_order{a, b, c, d, e}; + NGRAPH_VLOG(3) << "transposing " << ngraph::join(s) << " to " + << ngraph::join(reshaped_shape) << "axis-order " + << ngraph::join(transpose_order); + auto input_order = std::make_shared( + ngraph::element::u64, ngraph::Shape{transpose_order.size()}, + transpose_order); + node = std::make_shared(node, input_order); +} + +template +void Transpose3D(std::shared_ptr& node) { + Transpose3D(node->get_default_output()); +} + +namespace detail { +template +void NHWCtoHW(const std::vector& src, std::vector& dst) { + if (dst.size() >= 2) { + dst[0] = src[1]; + dst[1] = src[2]; + } + if (dst.size() >= 3) { + dst[2] = src[3]; + } +} + +template +void NCHWtoHW(const std::vector& src, std::vector& dst) { + if (dst.size() >= 2) { + dst[0] = src[2]; + dst[1] = src[3]; + } + if (dst.size() >= 3) { + dst[2] = src[4]; + } +} +} + +void NHWCtoNCHW(const std::string& op_name, bool is_nhwc, + ngraph::Output& ng_input); + +void NCHWtoNHWC(const std::string& op_name, bool is_nhwc, + ngraph::Output& ng_node); + +template +void NHWCtoHW(bool is_nhwc, const std::vector& src, + std::vector& dst) { + if (is_nhwc) { + detail::NHWCtoHW(src, dst); + } else { + detail::NCHWtoHW(src, dst); + } +} + +} // namespace ngraph_bridge +} // namespace tensorflow + +#endif // NGRAPH_TF_BRIDGE_CONVERSIONS_H_ diff --git a/ngraph/frontend/tensorflow/src/proto/allocation_description.proto b/ngraph/frontend/tensorflow/src/proto/allocation_description.proto new file mode 100644 index 00000000000000..f18caa40b2bde7 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/allocation_description.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "AllocationDescriptionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/allocation_description_go_proto"; + +message AllocationDescription { + // Total number of bytes requested + int64 requested_bytes = 1; + + // Total number of bytes allocated if known + int64 allocated_bytes = 2; + + // Name of the allocator used + string allocator_name = 3; + + // Identifier of the allocated buffer if known + int64 allocation_id = 4; + + // Set if this tensor only has one remaining reference + bool has_single_reference = 5; + + // Address of the allocation. + uint64 ptr = 6; +} diff --git a/ngraph/frontend/tensorflow/src/proto/api_def.proto b/ngraph/frontend/tensorflow/src/proto/api_def.proto new file mode 100644 index 00000000000000..4d5fedd74ad5c1 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/api_def.proto @@ -0,0 +1,136 @@ +// Defines the text format for including per-op API definition and +// overrides for client language op code generators. + +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "ApiDefProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/api_def_go_proto"; +import "attr_value.proto"; + +// Used to specify and override the default API & behavior in the +// generated code for client languages, from what you would get from +// the OpDef alone. There will be a set of ApiDefs that are common +// to all client languages, and another set per client language. +// The per-client-language ApiDefs will inherit values from the +// common ApiDefs which it can either replace or modify. +// +// We separate the API definition from the OpDef so we can evolve the +// API while remaining backwards compatible when interpretting old +// graphs. Overrides go in an "api_def.pbtxt" file with a text-format +// ApiDefs message. +// +// WARNING: Be *very* careful changing the API for any existing op -- +// you can change the semantics of existing code. These changes may +// need to wait until a major release of TensorFlow to avoid breaking +// our compatibility promises. +message ApiDef { + // Name of the op (in the OpDef) to specify the API for. + string graph_op_name = 1; + // If this op is deprecated, set deprecation message to the message + // that should be logged when this op is used. + // The message should indicate alternative op to use, if any. + string deprecation_message = 12; + // Major version when the op will be deleted. For e.g. set this + // value to 2 if op API should be removed in TensorFlow 2.0 and + // deprecated in versions before that. + int32 deprecation_version = 13; + + enum Visibility { + // Normally this is "VISIBLE" unless you are inheriting a + // different value from another ApiDef. + DEFAULT_VISIBILITY = 0; + // Publicly visible in the API. + VISIBLE = 1; + // Do not include this op in the generated API. If visibility is + // set to 'SKIP', other fields are ignored for this op. + SKIP = 2; + // Hide this op by putting it into an internal namespace (or whatever + // is appropriate in the target language). + HIDDEN = 3; + } + Visibility visibility = 2; + + // If you specify any endpoint, this will replace all of the + // inherited endpoints. The first endpoint should be the + // "canonical" endpoint, and should not be deprecated (unless all + // endpoints are deprecated). + message Endpoint { + // Name should be either like "CamelCaseName" or + // "Package.CamelCaseName". Client-language-specific ApiDefs may + // use a snake_case convention instead of CamelCase. + string name = 1; + + // Set if this endpoint is deprecated. If set to true, a message suggesting + // to use a non-deprecated endpoint instead will be printed. If all + // endpoints are deprecated, set deprecation_message in ApiDef instead. + bool deprecated = 3; + + // Major version when an endpoint will be deleted. For e.g. set this + // value to 2 if endpoint should be removed in TensorFlow 2.0 and + // deprecated in versions before that. + int32 deprecation_version = 4; + } + repeated Endpoint endpoint = 3; + + message Arg { + string name = 1; + + // Change the name used to access this arg in the API from what + // is used in the GraphDef. Note that these names in `backticks` + // will also be replaced in the summary & description fields. + string rename_to = 2; + + // Note: this will replace any inherited arg doc. There is no + // current way of modifying arg descriptions (other than replacing + // them entirely) as can be done with op descriptions. + string description = 3; + } + repeated Arg in_arg = 4; + repeated Arg out_arg = 5; + // List of original in_arg names to specify new argument order. + // Length of arg_order should be either empty to keep current order + // or match size of in_arg. + repeated string arg_order = 11; + + // Description of the graph-construction-time configuration of this + // Op. That is to say, this describes the attr fields that will + // be specified in the NodeDef. + message Attr { + string name = 1; + + // Change the name used to access this attr in the API from what + // is used in the GraphDef. Note that these names in `backticks` + // will also be replaced in the summary & description fields. + string rename_to = 2; + + // Specify a new default value to use for this attr. This default + // will be used when creating new graphs, as opposed to the + // default in the OpDef, which will be used when interpreting old + // GraphDefs. + AttrValue default_value = 3; + + // Note: this will replace any inherited attr doc, there is no current + // way of modifying attr descriptions as can be done with op descriptions. + string description = 4; + } + repeated Attr attr = 6; + + // One-line human-readable description of what the Op does. + string summary = 7; + + // Additional, longer human-readable description of what the Op does. + string description = 8; + + // Modify an existing/inherited description by adding text to the beginning + // or end. + string description_prefix = 9; + string description_suffix = 10; +} + +message ApiDefs { + repeated ApiDef op = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/attr_value.proto b/ngraph/frontend/tensorflow/src/proto/attr_value.proto new file mode 100644 index 00000000000000..ddf134b239cb70 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/attr_value.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package tensorflow; + +import "tensor.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "AttrValueProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/attr_value_go_proto"; + +// Protocol buffer representing the value for an attr used to configure an Op. +// Comment indicates the corresponding attr type. Only the field matching the +// attr type may be filled. +message AttrValue { + // LINT.IfChange + message ListValue { + repeated bytes s = 2; // "list(string)" + repeated int64 i = 3 [packed = true]; // "list(int)" + repeated float f = 4 [packed = true]; // "list(float)" + repeated bool b = 5 [packed = true]; // "list(bool)" + repeated DataType type = 6 [packed = true]; // "list(type)" + repeated TensorShapeProto shape = 7; // "list(shape)" + repeated TensorProto tensor = 8; // "list(tensor)" + repeated NameAttrList func = 9; // "list(attr)" + } + // LINT.ThenChange(https://www.tensorflow.org/code/tensorflow/c/c_api.cc) + + oneof value { + bytes s = 2; // "string" + int64 i = 3; // "int" + float f = 4; // "float" + bool b = 5; // "bool" + DataType type = 6; // "type" + TensorShapeProto shape = 7; // "shape" + TensorProto tensor = 8; // "tensor" + ListValue list = 1; // any "list(...)" + + // "func" represents a function. func.name is a function's name or + // a primitive op's name. func.attr.first is the name of an attr + // defined for that function. func.attr.second is the value for + // that attr in the instantiation. + NameAttrList func = 10; + + // This is a placeholder only used in nodes defined inside a + // function. It indicates the attr value will be supplied when + // the function is instantiated. For example, let us suppose a + // node "N" in function "FN". "N" has an attr "A" with value + // placeholder = "foo". When FN is instantiated with attr "foo" + // set to "bar", the instantiated node N's attr A will have been + // given the value "bar". + string placeholder = 9; + } +} + +// A list of attr names and their values. The whole list is attached +// with a string name. E.g., MatMul[T=float]. +message NameAttrList { + string name = 1; + map attr = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/cost_graph.proto b/ngraph/frontend/tensorflow/src/proto/cost_graph.proto new file mode 100644 index 00000000000000..166c130df5fcbc --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/cost_graph.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package tensorflow; + +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "CostGraphProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/cost_graph_go_proto"; + +message CostGraphDef { + message Node { + // The name of the node. Names are globally unique. + string name = 1; + + // The device of the node. Can be empty if the node is mapped to the + // default partition or partitioning hasn't been run yet. + string device = 2; + + // The id of the node. Node ids are only unique inside a partition. + int32 id = 3; + + // Inputs of this node. They must be executed before this node can be + // executed. An input is a particular output of another node, specified + // by the node id and the output index. + message InputInfo { + int32 preceding_node = 1; + int32 preceding_port = 2; + } + repeated InputInfo input_info = 4; + + // Outputs of this node. + message OutputInfo { + int64 size = 1; + // If >= 0, the output is an alias of an input. Note that an alias input + // may itself be an alias. The algorithm will therefore need to follow + // those pointers. + int64 alias_input_port = 2; + TensorShapeProto shape = 3; + DataType dtype = 4; + } + repeated OutputInfo output_info = 5; + + // Temporary memory used by this node. + int64 temporary_memory_size = 6; + + // Persistent memory used by this node. + int64 persistent_memory_size = 12; + + int64 host_temp_memory_size = 10 [deprecated = true]; + int64 device_temp_memory_size = 11 [deprecated = true]; + int64 device_persistent_memory_size = 16 [deprecated = true]; + + // Estimate of the computational cost of this node, in microseconds. + int64 compute_cost = 9; + + // Analytical estimate of the computational cost of this node, in + // microseconds. + int64 compute_time = 14; + + // Analytical estimate of the memory access cost of this node, in + // microseconds. + int64 memory_time = 15; + + // If true, the output is permanent: it can't be discarded, because this + // node is part of the "final output". Nodes may depend on final nodes. + bool is_final = 7; + + // Ids of the control inputs for this node. + repeated int32 control_input = 8; + + // Are the costs inaccurate? + bool inaccurate = 17; + } + repeated Node node = 1; + + // Total cost of this graph, typically used for balancing decisions. + message AggregatedCost { + // Aggregated cost value. + float cost = 1; + + // Aggregated cost dimension (e.g. 'memory', 'compute', 'network'). + string dimension = 2; + } + repeated AggregatedCost cost = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/dataset_options.proto b/ngraph/frontend/tensorflow/src/proto/dataset_options.proto new file mode 100644 index 00000000000000..05e15e156254e7 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/dataset_options.proto @@ -0,0 +1,179 @@ +syntax = "proto3"; + +package tensorflow.data; + +// Represents the type of auto-sharding we enable. +enum AutoShardPolicy { + AUTO = 0; + FILE = 1; + DATA = 2; + OFF = -1; +} + +message DistributeOptions { + // The type of sharding that auto-shard should attempt. If this is set to + // FILE, then we will attempt to shard by files (each worker will get a set of + // files to process). If we cannot find a set of files to shard for at least + // one file per worker, we will error out. When this option is selected, make + // sure that you have enough files so that each worker gets at least one file. + // There will be a runtime error thrown if there are insufficient files. If + // this is set to DATA, then we will shard by elements produced by the + // dataset, and each worker will process the whole dataset and discard the + // portion that is not for itself. If this is set to OFF, then we will not + // autoshard, and each worker will receive a copy of the full dataset. This + // option is set to AUTO by default, AUTO will attempt to first shard by FILE, + // and fall back to sharding by DATA if we cannot find a set of files to + // shard. + AutoShardPolicy auto_shard_policy = 1; + // The number of devices attached to this input pipeline. + oneof optional_num_devices { + int32 num_devices = 2; + } +} + +message MapVectorization { + // Whether to vectorize map transformations. + oneof optional_enabled { + bool enabled = 1; + } + // Whether to use ChooseFastestBranchDataset with this transformation. If + // True, the pipeline picks between the vectorized and original segment at + // runtime based on their iterations speed. + oneof optional_use_choose_fastest { + bool use_choose_fastest = 2; + } +} + +message OptimizationOptions { + // Whether to apply default graph optimizations. If False, only graph + // optimizations that have been explicitly enabled will be applied. + oneof optional_apply_default_optimizations { + bool apply_default_optimizations = 1; + } + // Whether to automatically tune performance knobs. + oneof optional_autotune { + bool autotune = 2; + } + // When autotuning is enabled (through autotune), determines whether to also + // autotune buffer sizes for datasets with parallelism. + oneof optional_autotune_buffers { + bool autotune_buffers = 3; + } + // When autotuning is enabled (through autotune), determines the CPU budget to + // use. Values greater than the number of schedulable CPU cores are allowed + // but may result in CPU contention. + oneof optional_autotune_cpu_budget { + int32 autotune_cpu_budget = 4; + } + // When autotuning is enabled (through autotune), determines the RAM budget to + // use. Values greater than the available RAM in bytes may result in OOM. If + // 0, defaults to half of the available RAM in bytes. + oneof optional_autotune_ram_budget { + int32 autotune_ram_budget = 5; + } + // Whether to fuse filter transformations. + oneof optional_filter_fusion { + bool filter_fusion = 6; + } + // Whether to fuse filter dataset that predicts random_uniform < rate into a + // sampling dataset. + oneof optional_filter_with_random_uniform_fusion { + bool filter_with_random_uniform_fusion = 7; + } + // Whether to hoist tf.random_uniform() ops out of map transformations. + oneof optional_hoist_random_uniform { + bool hoist_random_uniform = 8; + } + // Whether to fuse map and batch transformations. + oneof optional_map_and_batch_fusion { + bool map_and_batch_fusion = 9; + } + // Whether to fuse map and filter transformations. + oneof optional_map_and_filter_fusion { + bool map_and_filter_fusion = 10; + } + // Whether to fuse map transformations. + oneof optional_map_fusion { + bool map_fusion = 11; + } + // Whether to parallelize stateless map transformations. + oneof optional_map_parallelization { + bool map_parallelization = 12; + } + // The map vectorization options associated with the dataset. + MapVectorization map_vectorization = 13; + // Whether to eliminate no-op transformations. + oneof optional_noop_elimination { + bool noop_elimination = 14; + } + // Whether to parallelize copying of batch elements. This optimization is + // highly experimental and can cause performance degradation (e.g. when the + // parallelization overhead exceeds the benefits of performing the data copies + // in parallel). You should only enable this optimization if a) your input + // pipeline is bottlenecked on batching and b) you have validated that this + // optimization improves performance. + oneof optional_parallel_batch { + bool parallel_batch = 15; + } + // Whether to reorder ops that will discard data to the front of unary + // cardinality preserving transformations, e.g. dataset.map(...).take(3) will + // be optimized to dataset.take(3).map(...). For now this optimization will + // move `skip`, `shard` and `take` to the front of `map` and `prefetch`. This + // optimization is only for performance; it will not affect the output of the + // dataset. + oneof optional_reorder_data_discarding_ops { + bool reorder_data_discarding_ops = 16; + } + // Whether to fuse shuffle and repeat transformations. + oneof optional_shuffle_and_repeat_fusion { + bool shuffle_and_repeat_fusion = 17; + } +} + +message ThreadingOptions { + // If set, it overrides the maximum degree of intra-op parallelism. + oneof optional_max_intra_op_parallelism { + int32 max_intra_op_parallelism = 1; + } + // If set, the dataset will use a private threadpool of the given size. + oneof optional_private_threadpool_size { + int32 private_threadpool_size = 2; + } +} + +// Represents how to handle external state during serialization. +enum ExternalStatePolicy { + WARN = 0; + IGNORE = 1; + FAIL = 2; +} + +// Message stored with Dataset objects to control how datasets are processed and +// optimized. +message Options { + // Whether the outputs need to be produced in deterministic order. + oneof optional_deterministic { + bool deterministic = 1; + } + // The distribution strategy options associated with the dataset. + DistributeOptions distribute_options = 2; + // The optimization options associated with the dataset. + OptimizationOptions optimization_options = 3; + // Whether to introduce 'slack' in the last `prefetch` of the input pipeline, + // if it exists. This may reduce CPU contention with accelerator host-side + // activity at the start of a step. The slack frequency is determined by the + // number of devices attached to this input pipeline. + oneof optional_slack { + bool slack = 4; + } + // The threading options associated with the dataset. + ThreadingOptions threading_options = 5; + // This option can be used to override the default policy for how to handle + // external state when serializing a dataset or checkpointing its iterator. + // There are three settings available - IGNORE: External state is ignored + // without a warning; WARN: External state is ignored and a warning is logged; + // FAIL: External state results in an error. + oneof optional_external_state_policy { + ExternalStatePolicy external_state_policy = 6; + } +} diff --git a/ngraph/frontend/tensorflow/src/proto/device_attributes.proto b/ngraph/frontend/tensorflow/src/proto/device_attributes.proto new file mode 100644 index 00000000000000..4c7a2b87e4f5d7 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/device_attributes.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "DeviceAttributesProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/device_attributes_go_proto"; + +message InterconnectLink { + int32 device_id = 1; + string type = 2; + int32 strength = 3; +} + +message LocalLinks { + repeated InterconnectLink link = 1; +} + +message DeviceLocality { + // Optional bus locality of device. Default value of 0 means + // no specific locality. Specific localities are indexed from 1. + int32 bus_id = 1; + + // Optional NUMA locality of device. + int32 numa_node = 2; + + // Optional local interconnect links to other devices. + LocalLinks links = 3; +} + +message DeviceAttributes { + // Fully specified name of the device within a cluster. + string name = 1; + + // String representation of device_type. + string device_type = 2; + + // Memory capacity of device in bytes. + int64 memory_limit = 4; + + // Platform-specific data about device that may be useful + // for supporting efficient data transfers. + DeviceLocality locality = 5; + + // A device is assigned a global unique number each time it is + // initialized. "incarnation" should never be 0. + fixed64 incarnation = 6; + + // String representation of the physical device that this device maps to. + string physical_device_desc = 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/function.proto b/ngraph/frontend/tensorflow/src/proto/function.proto new file mode 100644 index 00000000000000..8502ae5c494b0b --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/function.proto @@ -0,0 +1,126 @@ +syntax = "proto3"; + +package tensorflow; + +import "attr_value.proto"; +import "node_def.proto"; +import "op_def.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "FunctionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/function_go_proto"; + +// A library is a set of named functions. +message FunctionDefLibrary { + repeated FunctionDef function = 1; + repeated GradientDef gradient = 2; +} + +// A function can be instantiated when the runtime can bind every attr +// with a value. When a GraphDef has a call to a function, it must +// have binding for every attr defined in the signature. +// +// TODO(zhifengc): +// * device spec, etc. +message FunctionDef { + // The definition of the function's name, arguments, return values, + // attrs etc. + OpDef signature = 1; + + // Attributes specific to this function definition. + map attr = 5; + + // Attributes for function arguments. These attributes are the same set of + // valid attributes as to _Arg nodes. + message ArgAttrs { + map attr = 1; + } + map arg_attr = 7; + + // Unique IDs for each resource argument, used to track aliasing resources. If + // Argument A and Argument B alias each other, then + // resource_arg_unique_ids[A.index] == resource_arg_unique_ids[B.index]. + // + // If this field is empty, none of the arguments could alias; otherwise, every + // resource argument should have an entry in this field. + // + // When instantiated, the unique IDs will be attached to the _Arg nodes' + // "_resource_arg_unique_id" attribute. + map resource_arg_unique_id = 8; + + // NOTE: field id 2 deleted on Jan 11, 2017, GraphDef version 21. + reserved 2; + + // In both of the following fields, there is the need to specify an + // output that is used as either the input to another node (in + // `node_def`) or as a return value of the function (in `ret`). + // Unlike the NodeDefs in GraphDef, we need to be able to specify a + // list in some cases (instead of just single outputs). Also, we + // need to be able to deal with lists of unknown length (so the + // output index may not be known at function definition time). So + // we use the following format instead: + // * "fun_in" where "fun_in" is the name of a function input arg in + // the `signature` field above. This represents that input, whether + // it is a single tensor or a list. + // * "fun_in:0" gives the first element of a function input arg (a + // non-list input is considered a list of length 1 for these + // purposes). + // * "node:out" where "node" is the name of a node in `node_def` and + // "out" is the name one of its op's output arguments (the name + // comes from the OpDef of the node's op). This represents that + // node's output, whether it is a single tensor or a list. + // Note: We enforce that an op's output arguments are never + // renamed in the backwards-compatibility test. + // * "node:out:0" gives the first element of a node output arg (a + // non-list output is considered a list of length 1 for these + // purposes). + // + // NOT CURRENTLY SUPPORTED (but may be in the future): + // * "node:out:-1" gives last element in a node output list + // * "node:out:1:" gives a list with all but the first element in a + // node output list + // * "node:out::-1" gives a list with all but the last element in a + // node output list + + // The body of the function. Unlike the NodeDefs in a GraphDef, attrs + // may have values of type `placeholder` and the `input` field uses + // the "output" format above. + + // By convention, "op" in node_def is resolved by consulting with a + // user-defined library first. If not resolved, "func" is assumed to + // be a builtin op. + repeated NodeDef node_def = 3; + + // A mapping from the output arg names from `signature` to the + // outputs from `node_def` that should be returned by the function. + map ret = 4; + + // A mapping from control output names from `signature` to node names in + // `node_def` which should be control outputs of this function. + map control_ret = 6; +} + +// GradientDef defines the gradient function of a function defined in +// a function library. +// +// A gradient function g (specified by gradient_func) for a function f +// (specified by function_name) must follow the following: +// +// The function 'f' must be a numerical function which takes N inputs +// and produces M outputs. Its gradient function 'g', which is a +// function taking N + M inputs and produces N outputs. +// +// I.e. if we have +// (y1, y2, ..., y_M) = f(x1, x2, ..., x_N), +// then, g is +// (dL/dx1, dL/dx2, ..., dL/dx_N) = g(x1, x2, ..., x_N, +// dL/dy1, dL/dy2, ..., dL/dy_M), +// where L is a scalar-value function of (x1, x2, ..., xN) (e.g., the +// loss function). dL/dx_i is the partial derivative of L with respect +// to x_i. +message GradientDef { + string function_name = 1; // The function name. + string gradient_func = 2; // The gradient function's name. +} diff --git a/ngraph/frontend/tensorflow/src/proto/graph.proto b/ngraph/frontend/tensorflow/src/proto/graph.proto new file mode 100644 index 00000000000000..76bdf43c02ae83 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/graph.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package tensorflow; + +import "function.proto"; +import "node_def.proto"; +import "versions.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "GraphProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/graph_go_proto"; + +// Represents the graph of operations +message GraphDef { + repeated NodeDef node = 1; + + // Compatibility versions of the graph. See core/public/version.h for version + // history. The GraphDef version is distinct from the TensorFlow version, and + // each release of TensorFlow will support a range of GraphDef versions. + VersionDef versions = 4; + + // Deprecated single version field; use versions above instead. Since all + // GraphDef changes before "versions" was introduced were forward + // compatible, this field is entirely ignored. + int32 version = 3 [deprecated = true]; + + // "library" provides user-defined functions. + // + // Naming: + // * library.function.name are in a flat namespace. + // NOTE: We may need to change it to be hierarchical to support + // different orgs. E.g., + // { "/google/nn", { ... }}, + // { "/google/vision", { ... }} + // { "/org_foo/module_bar", { ... }} + // map named_lib; + // * If node[i].op is the name of one function in "library", + // node[i] is deemed as a function call. Otherwise, node[i].op + // must be a primitive operation supported by the runtime. + // + // + // Function call semantics: + // + // * The callee may start execution as soon as some of its inputs + // are ready. The caller may want to use Tuple() mechanism to + // ensure all inputs are ready in the same time. + // + // * The consumer of return values may start executing as soon as + // the return values the consumer depends on are ready. The + // consumer may want to use Tuple() mechanism to ensure the + // consumer does not start until all return values of the callee + // function are ready. + FunctionDefLibrary library = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/graph_transfer_info.proto b/ngraph/frontend/tensorflow/src/proto/graph_transfer_info.proto new file mode 100644 index 00000000000000..bb6af6e990c3f8 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/graph_transfer_info.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; + +package tensorflow; + +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "GraphTransferInfoProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/graph_transfer_info_go_proto"; + +message GraphTransferNodeInput { + int32 node_id = 1; + int32 output_port = 2; +} +message GraphTransferNodeInfo { + string name = 1; + int32 node_id = 2; + string type_name = 3; + int32 soc_op_id = 4; + int32 padding_id = 5; + int32 input_count = 6; + int32 output_count = 7; +} +message GraphTransferConstNodeInfo { + string name = 1; + int32 node_id = 2; + repeated int64 shape = 3; + bytes data = 4; + DataType dtype = 5; +} +message GraphTransferNodeInputInfo { + int32 node_id = 1; + repeated GraphTransferNodeInput node_input = 2; +} +message GraphTransferNodeOutputInfo { + int32 node_id = 1; + repeated int32 max_byte_size = 2; +} +message GraphTransferGraphInputNodeInfo { + string name = 1; + repeated int64 shape = 2; + DataType dtype = 3; +} + +message GraphTransferGraphOutputNodeInfo { + string name = 1; + repeated int64 shape = 2; + DataType dtype = 3; +} + +// Protocol buffer representing a handle to a tensorflow resource. Handles are +// not valid across executions, but can be serialized back and forth from within +// a single run. +message GraphTransferInfo { + enum Destination { + NOP = 0; + HEXAGON = 1; + } + + repeated GraphTransferNodeInfo node_info = 1; + repeated GraphTransferConstNodeInfo const_node_info = 2; + repeated GraphTransferNodeInputInfo node_input_info = 3; + repeated GraphTransferNodeOutputInfo node_output_info = 4; + // Input Node parameters of transferred graph + repeated GraphTransferGraphInputNodeInfo graph_input_node_info = 5; + repeated GraphTransferGraphOutputNodeInfo graph_output_node_info = 6; + // Destination of graph transfer + Destination destination = 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/kernel_def.proto b/ngraph/frontend/tensorflow/src/proto/kernel_def.proto new file mode 100644 index 00000000000000..de76a496d8cf19 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/kernel_def.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package tensorflow; + +import "attr_value.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "KernelDefProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/kernel_def_go_proto"; + +message KernelDef { + // Must match the name of an Op. + string op = 1; + + // Type of device this kernel runs on. + string device_type = 2; + + message AttrConstraint { + // Name of an attr from the Op. + string name = 1; + + // A list of values that this kernel supports for this attr. + // Like OpDef.AttrDef.allowed_values, except for kernels instead of Ops. + AttrValue allowed_values = 2; + } + repeated AttrConstraint constraint = 3; + + // Names of the Op's input_/output_args that reside in host memory + // instead of device memory. + repeated string host_memory_arg = 4; + + // This allows experimental kernels to be registered for an op that + // won't be used unless the user specifies a "_kernel" attr with + // value matching this. + string label = 5; + + // Prioritization of kernel amongst different devices. By default we assume + // priority is 0. The higher the priority the better. By default (i.e. if + // this is not set), we prefer GPU kernels over CPU. + int32 priority = 6; +} + +// A collection of KernelDefs +message KernelList { + repeated KernelDef kernel = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/log_memory.proto b/ngraph/frontend/tensorflow/src/proto/log_memory.proto new file mode 100644 index 00000000000000..aa30cef2aeaf4c --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/log_memory.proto @@ -0,0 +1,95 @@ +syntax = "proto3"; + +package tensorflow; + +import "tensor_description.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "LogMemoryProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/log_memory_go_proto"; + +message MemoryLogStep { + // Process-unique step id. + int64 step_id = 1; + + // Handle describing the feeds and fetches of the step. + string handle = 2; +} + +message MemoryLogTensorAllocation { + // Process-unique step id. + int64 step_id = 1; + + // Name of the kernel making the allocation as set in GraphDef, + // e.g., "affine2/weights/Assign". + string kernel_name = 2; + + // Allocated tensor details. + TensorDescription tensor = 3; +} + +message MemoryLogTensorDeallocation { + // Id of the tensor buffer being deallocated, used to match to a + // corresponding allocation. + int64 allocation_id = 1; + + // Name of the allocator used. + string allocator_name = 2; +} + +message MemoryLogTensorOutput { + // Process-unique step id. + int64 step_id = 1; + + // Name of the kernel producing an output as set in GraphDef, e.g., + // "affine2/weights/Assign". + string kernel_name = 2; + + // Index of the output being set. + int32 index = 3; + + // Output tensor details. + TensorDescription tensor = 4; +} + +message MemoryLogRawAllocation { + // Process-unique step id. + int64 step_id = 1; + + // Name of the operation making the allocation. + string operation = 2; + + // Number of bytes in the allocation. + int64 num_bytes = 3; + + // Address of the allocation. + uint64 ptr = 4; + + // Id of the tensor buffer being allocated, used to match to a + // corresponding deallocation. + int64 allocation_id = 5; + + // Name of the allocator used. + string allocator_name = 6; +} + +message MemoryLogRawDeallocation { + // Process-unique step id. + int64 step_id = 1; + + // Name of the operation making the deallocation. + string operation = 2; + + // Id of the tensor buffer being deallocated, used to match to a + // corresponding allocation. + int64 allocation_id = 3; + + // Name of the allocator used. + string allocator_name = 4; + + // True if the deallocation is queued and will be performed later, + // e.g. for GPU lazy freeing of buffers. + bool deferred = 5; +} diff --git a/ngraph/frontend/tensorflow/src/proto/model.proto b/ngraph/frontend/tensorflow/src/proto/model.proto new file mode 100644 index 00000000000000..ba74d7a2b7ee19 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/model.proto @@ -0,0 +1,130 @@ +syntax = "proto3"; + +package tensorflow.data.model; + +option cc_enable_arenas = true; + +// Class of a node in the performance model. +enum NodeClass { + UNKNOWN = 0; + INTERLEAVE_MANY = 1; + ASYNC_INTERLEAVE_MANY = 2; + KNOWN_RATIO = 3; + ASYNC_KNOWN_RATIO = 4; + UNKNOWN_RATIO = 5; +} + +// Algorithm used for model autotuning optimization. +enum AutotuneAlgorithm { + HILL_CLIMB = 0; + GRADIENT_DESCENT = 1; +} + +// Protocol buffer representing the data used by the autotuning modeling +// framework. +message ModelProto { + // General representation of a node in the model. + message Node { + // Unique node ID. + int64 id = 1; + + // Human-readable name of the node. + string name = 2; + + // An indication whether autotuning is enabled for this node. + bool autotune = 3; + + // The number of bytes stored in this node's buffer. + int64 buffered_bytes = 4; + + // The number of elements stored in this node's buffer. + int64 buffered_elements = 5; + + // The number of bytes consumed by the node. + int64 bytes_consumed = 6; + + // The number of bytes produced by the node. + int64 bytes_produced = 7; + + // The number of elements produced by the node. + int64 num_elements = 8; + + // The aggregate processing time spent in this node. + int64 processing_time = 9; + + // An indication whether this node records metrics about produced and + // consumed elements. + bool record_metrics = 10; + + // Represents a node parameter. + message Parameter { + // Human-readable name of the parameter. + string name = 1; + + // Identifies the model value of the parameter. This can be different from + // the actual value (e.g. during optimization search). + double value = 2; + + // The actual value of the parameter. + double state_value = 3; + + // Minimum value of the parameter. + double min = 4; + + // Maximum value of the parameter. + double max = 5; + + // Identifies whether the parameter should participate in autotuning. + bool tunable = 6; + } + + // Parameters of this node. + repeated Parameter parameters = 11; + + // Statistic of inputs processing time history. + double input_processing_time_sum = 12; + int64 input_processing_time_count = 13; + + // Inputs of this node. + repeated Node inputs = 14; + + // Class of this node. + NodeClass node_class = 15; + + // Ratio of input to output elements. This is only used by KNOWN_RATIO and + // ASYNC_KNOWN_RATIO nodes. + double ratio = 16; + + // Ratio identifies how many parallelism calls are introduced by one + // buffered element. This is only used by ASYNC_KNOWN_RATIO nodes. + double memory_ratio = 17; + } + + // Output node of this model. + Node output = 1; + + // Counter for node IDs of this model. + int64 id_counter = 2; + + // Indicates whether the modeling framework should collect resource usage, + // e.g. CPU, memory. + bool collect_resource_usage = 3; + + // Contains parameters of the model autotuning optimization. + message OptimizationParams { + // Algorithm used for autotuning optimization. + AutotuneAlgorithm algorithm = 1; + + // Number of available logical threads. + int64 cpu_budget = 2; + + // Amount of available memory in bytes. + int64 ram_budget = 3; + + // Time between two consecutive `GetNext` calls to the iterator represented + // by the output node. + double model_input_time = 4; + } + + OptimizationParams optimization_params = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/node_def.proto b/ngraph/frontend/tensorflow/src/proto/node_def.proto new file mode 100644 index 00000000000000..17d8ecf684b77d --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/node_def.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +package tensorflow; + +import "attr_value.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "NodeProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/node_def_go_proto"; + +message NodeDef { + // The name given to this operator. Used for naming inputs, + // logging, visualization, etc. Unique within a single GraphDef. + // Must match the regexp "[A-Za-z0-9.][A-Za-z0-9_>./]*". + string name = 1; + + // The operation name. There may be custom parameters in attrs. + // Op names starting with an underscore are reserved for internal use. + string op = 2; + + // Each input is "node:src_output" with "node" being a string name and + // "src_output" indicating which output tensor to use from "node". If + // "src_output" is 0 the ":0" suffix can be omitted. Regular inputs + // may optionally be followed by control inputs that have the format + // "^node". + repeated string input = 3; + + // A (possibly partial) specification for the device on which this + // node should be placed. + // The expected syntax for this string is as follows: + // + // DEVICE_SPEC ::= PARTIAL_SPEC + // + // PARTIAL_SPEC ::= ("/" CONSTRAINT) * + // CONSTRAINT ::= ("job:" JOB_NAME) + // | ("replica:" [1-9][0-9]*) + // | ("task:" [1-9][0-9]*) + // | ("device:" [A-Za-z]* ":" ([1-9][0-9]* | "*") ) + // + // Valid values for this string include: + // * "/job:worker/replica:0/task:1/device:GPU:3" (full specification) + // * "/job:worker/device:GPU:3" (partial specification) + // * "" (no specification) + // + // If the constraints do not resolve to a single device (or if this + // field is empty or not present), the runtime will attempt to + // choose a device automatically. + string device = 4; + + // Operation-specific graph-construction-time configuration. + // Note that this should include all attrs defined in the + // corresponding OpDef, including those with a value matching + // the default -- this allows the default to change and makes + // NodeDefs easier to interpret on their own. However, if + // an attr with a default is not specified in this list, the + // default will be used. + // The "names" (keys) must match the regexp "[a-z][a-z0-9_]+" (and + // one of the names from the corresponding OpDef's attr field). + // The values must have a type matching the corresponding OpDef + // attr's type field. + // TODO(josh11b): Add some examples here showing best practices. + map attr = 5; + + message ExperimentalDebugInfo { + // Opaque string inserted into error messages created by the runtime. + // + // This is intended to store the list of names of the nodes from the + // original graph that this node was derived. For example if this node, say + // C, was result of a fusion of 2 nodes A and B, then 'original_node' would + // be {A, B}. This information can be used to map errors originating at the + // current node to some top level source code. + repeated string original_node_names = 1; + + // This is intended to store the list of names of the functions from the + // original graph that this node was derived. For example if this node, say + // C, was result of a fusion of node A in function FA and node B in function + // FB, then `original_funcs` would be {FA, FB}. If the node is in the top + // level graph, the `original_func` is empty. This information, with the + // `original_node_names` can be used to map errors originating at the + // current ndoe to some top level source code. + repeated string original_func_names = 2; + } + + // This stores debug information associated with the node. + ExperimentalDebugInfo experimental_debug_info = 6; +} diff --git a/ngraph/frontend/tensorflow/src/proto/op_def.proto b/ngraph/frontend/tensorflow/src/proto/op_def.proto new file mode 100644 index 00000000000000..5e5412103a7e7b --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/op_def.proto @@ -0,0 +1,174 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "OpDefProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/op_def_go_proto"; +import "attr_value.proto"; +import "types.proto"; +import "resource_handle.proto"; + +// Defines an operation. A NodeDef in a GraphDef specifies an Op by +// using the "op" field which should match the name of a OpDef. +// LINT.IfChange +message OpDef { + // Op names starting with an underscore are reserved for internal use. + // Names should be CamelCase and match the regexp "[A-Z][a-zA-Z0-9>_]*". + string name = 1; + + // For describing inputs and outputs. + message ArgDef { + // Name for the input/output. Should match the regexp "[a-z][a-z0-9_]*". + string name = 1; + + // Human readable description. + string description = 2; + + // Describes the type of one or more tensors that are accepted/produced + // by this input/output arg. The only legal combinations are: + // * For a single tensor: either the "type" field is set or the + // "type_attr" field is set to the name of an attr with type "type". + // * For a sequence of tensors with the same type: the "number_attr" + // field will be set to the name of an attr with type "int", and + // either the "type" or "type_attr" field will be set as for + // single tensors. + // * For a sequence of tensors, the "type_list_attr" field will be set + // to the name of an attr with type "list(type)". + DataType type = 3; + string type_attr = 4; // if specified, attr must have type "type" + string number_attr = 5; // if specified, attr must have type "int" + // If specified, attr must have type "list(type)", and none of + // type, type_attr, and number_attr may be specified. + string type_list_attr = 6; + + // The handle data for resource inputs. + repeated ResourceHandleProto.DtypeAndShape handle_data = 7; + + // For inputs: if true, the inputs are required to be refs. + // By default, inputs can be either refs or non-refs. + // For outputs: if true, outputs are refs, otherwise they are not. + bool is_ref = 16; + }; + + // Description of the input(s). + repeated ArgDef input_arg = 2; + + // Description of the output(s). + repeated ArgDef output_arg = 3; + + // Named control outputs for this operation. Useful only for composite + // operations (i.e. functions) which want to name different control outputs. + repeated string control_output = 20; + + // Description of the graph-construction-time configuration of this + // Op. That is to say, this describes the attr fields that will + // be specified in the NodeDef. + message AttrDef { + // A descriptive name for the argument. May be used, e.g. by the + // Python client, as a keyword argument name, and so should match + // the regexp "[a-z][a-z0-9_]+". + string name = 1; + + // One of the type names from attr_value.proto ("string", "list(string)", + // "int", etc.). + string type = 2; + + // A reasonable default for this attribute if the user does not supply + // a value. If not specified, the user must supply a value. + AttrValue default_value = 3; + + // Human-readable description. + string description = 4; + + // TODO(josh11b): bool is_optional? + + // --- Constraints --- + // These constraints are only in effect if specified. Default is no + // constraints. + + // For type == "int", this is a minimum value. For "list(___)" + // types, this is the minimum length. + bool has_minimum = 5; + int64 minimum = 6; + + // The set of allowed values. Has type that is the "list" version + // of the "type" field above (uses the "list" field of AttrValue). + // If type == "type" or "list(type)" above, then the "type" field + // of "allowed_values.list" has the set of allowed DataTypes. + // If type == "string" or "list(string)", then the "s" field of + // "allowed_values.list" has the set of allowed strings. + AttrValue allowed_values = 7; + } + repeated AttrDef attr = 4; + + // Optional deprecation based on GraphDef versions. + OpDeprecation deprecation = 8; + + // One-line human-readable description of what the Op does. + string summary = 5; + + // Additional, longer human-readable description of what the Op does. + string description = 6; + + // ------------------------------------------------------------------------- + // Which optimizations this operation can participate in. + + // True if the operation is commutative ("op(a,b) == op(b,a)" for all inputs) + bool is_commutative = 18; + + // If is_aggregate is true, then this operation accepts N >= 2 + // inputs and produces 1 output all of the same type. Should be + // associative and commutative, and produce output with the same + // shape as the input. The optimizer may replace an aggregate op + // taking input from multiple devices with a tree of aggregate ops + // that aggregate locally within each device (and possibly within + // groups of nearby devices) before communicating. + // TODO(josh11b): Implement that optimization. + bool is_aggregate = 16; // for things like add + + // Other optimizations go here, like + // can_alias_input, rewrite_when_output_unused, partitioning_strategy, etc. + + // ------------------------------------------------------------------------- + // Optimization constraints. + + // Ops are marked as stateful if their behavior depends on some state beyond + // their input tensors (e.g. variable reading op) or if they have + // a side-effect (e.g. printing or asserting ops). Equivalently, stateless ops + // must always produce the same output for the same input and have + // no side-effects. + // + // By default Ops may be moved between devices. Stateful ops should + // either not be moved, or should only be moved if that state can also + // be moved (e.g. via some sort of save / restore). + // Stateful ops are guaranteed to never be optimized away by Common + // Subexpression Elimination (CSE). + bool is_stateful = 17; // for things like variables, queue + + // ------------------------------------------------------------------------- + // Non-standard options. + + // By default, all inputs to an Op must be initialized Tensors. Ops + // that may initialize tensors for the first time should set this + // field to true, to allow the Op to take an uninitialized Tensor as + // input. + bool allows_uninitialized_input = 19; // for Assign, etc. +}; +// LINT.ThenChange( +// https://www.tensorflow.org/code/tensorflow/core/framework/op_def_util.cc) + +// Information about version-dependent deprecation of an op +message OpDeprecation { + // First GraphDef version at which the op is disallowed. + int32 version = 1; + + // Explanation of why it was deprecated and what to use instead. + string explanation = 2; +}; + +// A collection of OpDefs +message OpList { + repeated OpDef op = 1; +}; diff --git a/ngraph/frontend/tensorflow/src/proto/reader_base.proto b/ngraph/frontend/tensorflow/src/proto/reader_base.proto new file mode 100644 index 00000000000000..6fae310248dd40 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/reader_base.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "ReaderBaseProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/reader_base_go_proto"; + +// For serializing and restoring the state of ReaderBase, see +// reader_base.h for details. +message ReaderBaseState { + int64 work_started = 1; + int64 work_finished = 2; + int64 num_records_produced = 3; + bytes current_work = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/remote_fused_graph_execute_info.proto b/ngraph/frontend/tensorflow/src/proto/remote_fused_graph_execute_info.proto new file mode 100644 index 00000000000000..2cdaa6719e0aab --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/remote_fused_graph_execute_info.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package tensorflow; + +import "graph.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "RemoteFusedGraphExecuteInfoProto"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/remote_fused_graph_execute_info_go_proto"; + +// Protocol buffer representing a handle to a tensorflow resource. Handles are +// not valid across executions, but can be serialized back and forth from within +// a single run. +message RemoteFusedGraphExecuteInfo { + message TensorShapeTypeProto { + DataType dtype = 1; + TensorShapeProto shape = 2; + } + + // Definition of remote graph + GraphDef remote_graph = 1; + + // Remote fused graph input node name + repeated string graph_input_node_name = 2; + + // Remote fused graph output node name + repeated string graph_output_node_name = 3; + + // Executor's name + string executor_name = 4; + + // Optional: Parameters given to the executor + bytes serialized_executor_parameters = 5; + + // Optional: Default graph input tensor shape used to allocate memory + // before executing op + repeated TensorShapeTypeProto default_graph_input_tensor_shape = 6; + + // Optional: Default graph input tensor shape used to allocate memory + // before executing op + // TODO(satok): Remote output tensor shape once shape information is stored + // in NodeDef + repeated TensorShapeTypeProto default_graph_output_tensor_shape = 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/resource_handle.proto b/ngraph/frontend/tensorflow/src/proto/resource_handle.proto new file mode 100644 index 00000000000000..e2bce956547810 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/resource_handle.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package tensorflow; + +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "ResourceHandle"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/resource_handle_go_proto"; + +// Protocol buffer representing a handle to a tensorflow resource. Handles are +// not valid across executions, but can be serialized back and forth from within +// a single run. +message ResourceHandleProto { + // Unique name for the device containing the resource. + string device = 1; + + // Container in which this resource is placed. + string container = 2; + + // Unique name of this resource. + string name = 3; + + // Hash code for the type of the resource. Is only valid in the same device + // and in the same execution. + uint64 hash_code = 4; + + // For debug-only, the name of the type pointed to by this handle, if + // available. + string maybe_type_name = 5; + + // Protocol buffer representing a pair of (data type, tensor shape). + message DtypeAndShape { + DataType dtype = 1; + TensorShapeProto shape = 2; + } + + // Data types and shapes for the underlying resource. + repeated DtypeAndShape dtypes_and_shapes = 6; + + reserved 7; +} diff --git a/ngraph/frontend/tensorflow/src/proto/step_stats.proto b/ngraph/frontend/tensorflow/src/proto/step_stats.proto new file mode 100644 index 00000000000000..7b97c3a38325de --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/step_stats.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +package tensorflow; + +import "allocation_description.proto"; +import "tensor_description.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "StepStatsProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/step_stats_go_proto"; + +// An allocation/de-allocation operation performed by the allocator. +message AllocationRecord { + // The timestamp of the operation. + int64 alloc_micros = 1; + // Number of bytes allocated, or de-allocated if negative. + int64 alloc_bytes = 2; +} + +message AllocatorMemoryUsed { + string allocator_name = 1; + // These are per-node allocator memory stats. + int64 total_bytes = 2; + int64 peak_bytes = 3; + // The bytes that are not deallocated. + int64 live_bytes = 4; + // The allocation and deallocation timeline. + repeated AllocationRecord allocation_records = 6; + + // These are snapshots of the overall allocator memory stats. + // The number of live bytes currently allocated by the allocator. + int64 allocator_bytes_in_use = 5; +} + +// Output sizes recorded for a single execution of a graph node. +message NodeOutput { + int32 slot = 1; + TensorDescription tensor_description = 3; +} + +// For memory tracking. +message MemoryStats { + int64 temp_memory_size = 1; + int64 persistent_memory_size = 3; + repeated int64 persistent_tensor_alloc_ids = 5; + + int64 device_temp_memory_size = 2 [deprecated = true]; + int64 device_persistent_memory_size = 4 [deprecated = true]; + repeated int64 device_persistent_tensor_alloc_ids = 6 [deprecated = true]; +} + +// Time/size stats recorded for a single execution of a graph node. +message NodeExecStats { + // TODO(tucker): Use some more compact form of node identity than + // the full string name. Either all processes should agree on a + // global id (cost_id?) for each node, or we should use a hash of + // the name. + string node_name = 1; + int64 all_start_micros = 2; + int64 op_start_rel_micros = 3; + int64 op_end_rel_micros = 4; + int64 all_end_rel_micros = 5; + repeated AllocatorMemoryUsed memory = 6; + repeated NodeOutput output = 7; + string timeline_label = 8; + int64 scheduled_micros = 9; + uint32 thread_id = 10; + repeated AllocationDescription referenced_tensor = 11; + MemoryStats memory_stats = 12; + int64 all_start_nanos = 13; + int64 op_start_rel_nanos = 14; + int64 op_end_rel_nanos = 15; + int64 all_end_rel_nanos = 16; + int64 scheduled_nanos = 17; +} + +message DeviceStepStats { + string device = 1; + repeated NodeExecStats node_stats = 2; + // Its key is thread id. + map thread_names = 3; +} + +message StepStats { + repeated DeviceStepStats dev_stats = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/summary.proto b/ngraph/frontend/tensorflow/src/proto/summary.proto new file mode 100644 index 00000000000000..ef1aa2e0d1c587 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/summary.proto @@ -0,0 +1,149 @@ +syntax = "proto3"; + +package tensorflow; + +import "tensor.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "SummaryProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/summary_go_proto"; + +// Metadata associated with a series of Summary data +message SummaryDescription { + // Hint on how plugins should process the data in this series. + // Supported values include "scalar", "histogram", "image", "audio" + string type_hint = 1; +} + +// Serialization format for histogram module in +// core/lib/histogram/histogram.h +message HistogramProto { + double min = 1; + double max = 2; + double num = 3; + double sum = 4; + double sum_squares = 5; + + // Parallel arrays encoding the bucket boundaries and the bucket values. + // bucket(i) is the count for the bucket i. The range for + // a bucket is: + // i == 0: -DBL_MAX .. bucket_limit(0) + // i != 0: bucket_limit(i-1) .. bucket_limit(i) + repeated double bucket_limit = 6 [packed = true]; + repeated double bucket = 7 [packed = true]; +} + +// A SummaryMetadata encapsulates information on which plugins are able to make +// use of a certain summary value. +message SummaryMetadata { + message PluginData { + // The name of the plugin this data pertains to. + string plugin_name = 1; + + // The content to store for the plugin. The best practice is for this to be + // a binary serialized protocol buffer. + bytes content = 2; + } + + // Data that associates a summary with a certain plugin. + PluginData plugin_data = 1; + + // Display name for viewing in TensorBoard. + string display_name = 2; + + // Longform readable description of the summary sequence. Markdown supported. + string summary_description = 3; + + // Class of data stored in this time series. Required for compatibility with + // TensorBoard's generic data facilities (`DataProvider`, et al.). This value + // imposes constraints on the dtype and shape of the corresponding tensor + // values. See `DataClass` docs for details. + DataClass data_class = 4; +} + +enum DataClass { + // Unknown data class, used (implicitly) for legacy data. Will not be + // processed by data ingestion pipelines. + DATA_CLASS_UNKNOWN = 0; + // Scalar time series. Each `Value` for the corresponding tag must have + // `tensor` set to a rank-0 tensor of type `DT_FLOAT` (float32). + DATA_CLASS_SCALAR = 1; + // Tensor time series. Each `Value` for the corresponding tag must have + // `tensor` set. The tensor value is arbitrary, but should be small to + // accommodate direct storage in database backends: an upper bound of a few + // kilobytes is a reasonable rule of thumb. + DATA_CLASS_TENSOR = 2; + // Blob sequence time series. Each `Value` for the corresponding tag must + // have `tensor` set to a rank-1 tensor of bytestring dtype. + DATA_CLASS_BLOB_SEQUENCE = 3; +} + +// A Summary is a set of named values to be displayed by the +// visualizer. +// +// Summaries are produced regularly during training, as controlled by +// the "summary_interval_secs" attribute of the training operation. +// Summaries are also produced at the end of an evaluation. +message Summary { + message Image { + // Dimensions of the image. + int32 height = 1; + int32 width = 2; + // Valid colorspace values are + // 1 - grayscale + // 2 - grayscale + alpha + // 3 - RGB + // 4 - RGBA + // 5 - DIGITAL_YUV + // 6 - BGRA + int32 colorspace = 3; + // Image data in encoded format. All image formats supported by + // image_codec::CoderUtil can be stored here. + bytes encoded_image_string = 4; + } + + message Audio { + // Sample rate of the audio in Hz. + float sample_rate = 1; + // Number of channels of audio. + int64 num_channels = 2; + // Length of the audio in frames (samples per channel). + int64 length_frames = 3; + // Encoded audio data and its associated RFC 2045 content type (e.g. + // "audio/wav"). + bytes encoded_audio_string = 4; + string content_type = 5; + } + + message Value { + // This field is deprecated and will not be set. + string node_name = 7; + + // Tag name for the data. Used by TensorBoard plugins to organize data. Tags + // are often organized by scope (which contains slashes to convey + // hierarchy). For example: foo/bar/0 + string tag = 1; + + // Contains metadata on the summary value such as which plugins may use it. + // Take note that many summary values may lack a metadata field. This is + // because the FileWriter only keeps a metadata object on the first summary + // value with a certain tag for each tag. TensorBoard then remembers which + // tags are associated with which plugins. This saves space. + SummaryMetadata metadata = 9; + + // Value associated with the tag. + oneof value { + float simple_value = 2; + bytes obsolete_old_style_histogram = 3; + Image image = 4; + HistogramProto histo = 5; + Audio audio = 6; + TensorProto tensor = 8; + } + } + + // Set of values for the summary. + repeated Value value = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/tensor.proto b/ngraph/frontend/tensorflow/src/proto/tensor.proto new file mode 100644 index 00000000000000..7a25c446e68772 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor.proto @@ -0,0 +1,96 @@ +syntax = "proto3"; + +package tensorflow; + +import "resource_handle.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "TensorProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_go_proto"; + +// Protocol buffer representing a tensor. +message TensorProto { + DataType dtype = 1; + + // Shape of the tensor. TODO(touts): sort out the 0-rank issues. + TensorShapeProto tensor_shape = 2; + + // Only one of the representations below is set, one of "tensor_contents" and + // the "xxx_val" attributes. We are not using oneof because as oneofs cannot + // contain repeated fields it would require another extra set of messages. + + // Version number. + // + // In version 0, if the "repeated xxx" representations contain only one + // element, that element is repeated to fill the shape. This makes it easy + // to represent a constant Tensor with a single value. + int32 version_number = 3; + + // Serialized raw tensor content from either Tensor::AsProtoTensorContent or + // memcpy in tensorflow::grpc::EncodeTensorToByteBuffer. This representation + // can be used for all tensor types. The purpose of this representation is to + // reduce serialization overhead during RPC call by avoiding serialization of + // many repeated small items. + bytes tensor_content = 4; + + // Type specific representations that make it easy to create tensor protos in + // all languages. Only the representation corresponding to "dtype" can + // be set. The values hold the flattened representation of the tensor in + // row major order. + + // DT_HALF, DT_BFLOAT16. Note that since protobuf has no int16 type, we'll + // have some pointless zero padding for each value here. + repeated int32 half_val = 13 [packed = true]; + + // DT_FLOAT. + repeated float float_val = 5 [packed = true]; + + // DT_DOUBLE. + repeated double double_val = 6 [packed = true]; + + // DT_INT32, DT_INT16, DT_INT8, DT_UINT8. + repeated int32 int_val = 7 [packed = true]; + + // DT_STRING + repeated bytes string_val = 8; + + // DT_COMPLEX64. scomplex_val(2*i) and scomplex_val(2*i+1) are real + // and imaginary parts of i-th single precision complex. + repeated float scomplex_val = 9 [packed = true]; + + // DT_INT64 + repeated int64 int64_val = 10 [packed = true]; + + // DT_BOOL + repeated bool bool_val = 11 [packed = true]; + + // DT_COMPLEX128. dcomplex_val(2*i) and dcomplex_val(2*i+1) are real + // and imaginary parts of i-th double precision complex. + repeated double dcomplex_val = 12 [packed = true]; + + // DT_RESOURCE + repeated ResourceHandleProto resource_handle_val = 14; + + // DT_VARIANT + repeated VariantTensorDataProto variant_val = 15; + + // DT_UINT32 + repeated uint32 uint32_val = 16 [packed = true]; + + // DT_UINT64 + repeated uint64 uint64_val = 17 [packed = true]; +} + +// Protocol buffer representing the serialization format of DT_VARIANT tensors. +message VariantTensorDataProto { + // Name of the type of objects being serialized. + string type_name = 1; + // Portions of the object that are not Tensors. + bytes metadata = 2; + // Tensors contained within objects being serialized. + repeated TensorProto tensors = 3; +} diff --git a/ngraph/frontend/tensorflow/src/proto/tensor_description.proto b/ngraph/frontend/tensorflow/src/proto/tensor_description.proto new file mode 100644 index 00000000000000..c162c0e2385533 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor_description.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package tensorflow; + +import "allocation_description.proto"; +import "tensor_shape.proto"; +import "types.proto"; + +option cc_enable_arenas = true; +option java_outer_classname = "TensorDescriptionProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_description_go_proto"; + +message TensorDescription { + // Data type of tensor elements + DataType dtype = 1; + + // Shape of the tensor. + TensorShapeProto shape = 2; + + // Information about the size and allocator used for the data + AllocationDescription allocation_description = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/tensor_shape.proto b/ngraph/frontend/tensorflow/src/proto/tensor_shape.proto new file mode 100644 index 00000000000000..45d5b78ecbbc4c --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor_shape.proto @@ -0,0 +1,46 @@ +// Protocol buffer representing the shape of tensors. + +syntax = "proto3"; +option cc_enable_arenas = true; +option java_outer_classname = "TensorShapeProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_shape_go_proto"; + +package tensorflow; + +// Dimensions of a tensor. +message TensorShapeProto { + // One dimension of the tensor. + message Dim { + // Size of the tensor in that dimension. + // This value must be >= -1, but values of -1 are reserved for "unknown" + // shapes (values of -1 mean "unknown" dimension). Certain wrappers + // that work with TensorShapeProto may fail at runtime when deserializing + // a TensorShapeProto containing a dim value of -1. + int64 size = 1; + + // Optional name of the tensor dimension. + string name = 2; + }; + + // Dimensions of the tensor, such as {"input", 30}, {"output", 40} + // for a 30 x 40 2D tensor. If an entry has size -1, this + // corresponds to a dimension of unknown size. The names are + // optional. + // + // The order of entries in "dim" matters: It indicates the layout of the + // values in the tensor in-memory representation. + // + // The first entry in "dim" is the outermost dimension used to layout the + // values, the last entry is the innermost dimension. This matches the + // in-memory layout of RowMajor Eigen tensors. + // + // If "dim.size()" > 0, "unknown_rank" must be false. + repeated Dim dim = 2; + + // If true, the number of dimensions in the shape is unknown. + // + // If true, "dim.size()" must be 0. + bool unknown_rank = 3; +}; diff --git a/ngraph/frontend/tensorflow/src/proto/tensor_slice.proto b/ngraph/frontend/tensorflow/src/proto/tensor_slice.proto new file mode 100644 index 00000000000000..4463658d391e2f --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/tensor_slice.proto @@ -0,0 +1,39 @@ +// Protocol buffer representing slices of a tensor + +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "TensorSliceProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_slice_go_proto"; + +// Can only be interpreted if you know the corresponding TensorShape. +message TensorSliceProto { + // Extent of the slice in one dimension. + message Extent { + // Either both or no attributes must be set. When no attribute is set + // means: All data in that dimension. + + // Start index of the slice, starting at 0. + int64 start = 1; + + // Length of the slice: if the length is missing or -1 we will + // interpret this as "everything in this dimension". We use + // "oneof" to preserve information about whether the length is + // present without changing the serialization format from the + // prior proto2 version of this proto. + oneof has_length { + int64 length = 2; + } + } + + // Extent of the slice in all tensor dimensions. + // + // Must have one entry for each of the dimension of the tensor that this + // slice belongs to. The order of sizes is the same as the order of + // dimensions in the TensorShape. + repeated Extent extent = 1; +} diff --git a/ngraph/frontend/tensorflow/src/proto/types.proto b/ngraph/frontend/tensorflow/src/proto/types.proto new file mode 100644 index 00000000000000..e5f33036dcde55 --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/types.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package tensorflow; +option cc_enable_arenas = true; +option java_outer_classname = "TypesProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/types_go_proto"; + +// (== suppress_warning documentation-presence ==) +// LINT.IfChange +enum DataType { + // Not a legal value for DataType. Used to indicate a DataType field + // has not been set. + DT_INVALID = 0; + + // Data types that all computation devices are expected to be + // capable to support. + DT_FLOAT = 1; + DT_DOUBLE = 2; + DT_INT32 = 3; + DT_UINT8 = 4; + DT_INT16 = 5; + DT_INT8 = 6; + DT_STRING = 7; + DT_COMPLEX64 = 8; // Single-precision complex + DT_INT64 = 9; + DT_BOOL = 10; + DT_QINT8 = 11; // Quantized int8 + DT_QUINT8 = 12; // Quantized uint8 + DT_QINT32 = 13; // Quantized int32 + DT_BFLOAT16 = 14; // Float32 truncated to 16 bits. Only for cast ops. + DT_QINT16 = 15; // Quantized int16 + DT_QUINT16 = 16; // Quantized uint16 + DT_UINT16 = 17; + DT_COMPLEX128 = 18; // Double-precision complex + DT_HALF = 19; + DT_RESOURCE = 20; + DT_VARIANT = 21; // Arbitrary C++ data types + DT_UINT32 = 22; + DT_UINT64 = 23; + + // Do not use! These are only for parameters. Every enum above + // should have a corresponding value below (verified by types_test). + DT_FLOAT_REF = 101; + DT_DOUBLE_REF = 102; + DT_INT32_REF = 103; + DT_UINT8_REF = 104; + DT_INT16_REF = 105; + DT_INT8_REF = 106; + DT_STRING_REF = 107; + DT_COMPLEX64_REF = 108; + DT_INT64_REF = 109; + DT_BOOL_REF = 110; + DT_QINT8_REF = 111; + DT_QUINT8_REF = 112; + DT_QINT32_REF = 113; + DT_BFLOAT16_REF = 114; + DT_QINT16_REF = 115; + DT_QUINT16_REF = 116; + DT_UINT16_REF = 117; + DT_COMPLEX128_REF = 118; + DT_HALF_REF = 119; + DT_RESOURCE_REF = 120; + DT_VARIANT_REF = 121; + DT_UINT32_REF = 122; + DT_UINT64_REF = 123; +} +// LINT.ThenChange( +// https://www.tensorflow.org/code/tensorflow/c/tf_datatype.h, +// https://www.tensorflow.org/code/tensorflow/go/tensor.go, +// https://www.tensorflow.org/code/tensorflow/core/framework/tensor.cc, +// https://www.tensorflow.org/code/tensorflow/core/framework/types.h, +// https://www.tensorflow.org/code/tensorflow/core/framework/types.cc, +// https://www.tensorflow.org/code/tensorflow/python/framework/dtypes.py, +// https://www.tensorflow.org/code/tensorflow/python/framework/function.py) + +// For identifying the underlying type of a variant. For variants, the types +// listed here are a subset of the types in the variant type registry, +// corresponding to commonly used variants which must occasionally be +// special-cased. +enum SpecializedType { + // Invalid/unknown specialized type. + ST_INVALID = 0; + // "tensorflow::TensorList" in the variant type registry. + ST_TENSOR_LIST = 1; + // "tensorflow::data::Optional" in the variant type registry. + ST_OPTIONAL = 2; +} diff --git a/ngraph/frontend/tensorflow/src/proto/variable.proto b/ngraph/frontend/tensorflow/src/proto/variable.proto new file mode 100644 index 00000000000000..09d7fb3d45c95a --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/variable.proto @@ -0,0 +1,84 @@ +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "VariableProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/variable_go_proto"; + +// Indicates when a distributed variable will be synced. +enum VariableSynchronization { + // `AUTO`: Indicates that the synchronization will be determined by the + // current `DistributionStrategy` (eg. With `MirroredStrategy` this would be + // `ON_WRITE`). + VARIABLE_SYNCHRONIZATION_AUTO = 0; + // `NONE`: Indicates that there will only be one copy of the variable, so + // there is no need to sync. + VARIABLE_SYNCHRONIZATION_NONE = 1; + // `ON_WRITE`: Indicates that the variable will be updated across devices + // every time it is written. + VARIABLE_SYNCHRONIZATION_ON_WRITE = 2; + // `ON_READ`: Indicates that the variable will be aggregated across devices + // when it is read (eg. when checkpointing or when evaluating an op that uses + // the variable). + VARIABLE_SYNCHRONIZATION_ON_READ = 3; +} + +// Indicates how a distributed variable will be aggregated. +enum VariableAggregation { + // `NONE`: This is the default, giving an error if you use a + // variable-update operation with multiple replicas. + VARIABLE_AGGREGATION_NONE = 0; + // `SUM`: Add the updates across replicas. + VARIABLE_AGGREGATION_SUM = 1; + // `MEAN`: Take the arithmetic mean ("average") of the updates across + // replicas. + VARIABLE_AGGREGATION_MEAN = 2; + // `ONLY_FIRST_REPLICA`: This is for when every replica is performing the same + // update, but we only want to perform the update once. Used, e.g., for the + // global step counter. + VARIABLE_AGGREGATION_ONLY_FIRST_REPLICA = 3; +} + +// Protocol buffer representing a Variable. +message VariableDef { + // Name of the variable tensor. + string variable_name = 1; + + // Name of the tensor holding the variable's initial value. + string initial_value_name = 6; + + // Name of the initializer op. + string initializer_name = 2; + + // Name of the snapshot tensor. + string snapshot_name = 3; + + // Support for saving variables as slices of a larger variable. + SaveSliceInfoDef save_slice_info_def = 4; + + // Whether to represent this as a ResourceVariable. + bool is_resource = 5; + + // Whether this variable should be trained. + bool trainable = 7; + + // Indicates when a distributed variable will be synced. + VariableSynchronization synchronization = 8; + + // Indicates how a distributed variable will be aggregated. + VariableAggregation aggregation = 9; +} + +message SaveSliceInfoDef { + // Name of the full variable of which this is a slice. + string full_name = 1; + // Shape of the full variable. + repeated int64 full_shape = 2; + // Offset of this variable into the full variable. + repeated int64 var_offset = 3; + // Shape of this variable. + repeated int64 var_shape = 4; +} diff --git a/ngraph/frontend/tensorflow/src/proto/versions.proto b/ngraph/frontend/tensorflow/src/proto/versions.proto new file mode 100644 index 00000000000000..2cca6e37d325dc --- /dev/null +++ b/ngraph/frontend/tensorflow/src/proto/versions.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package tensorflow; + +option cc_enable_arenas = true; +option java_outer_classname = "VersionsProtos"; +option java_multiple_files = true; +option java_package = "org.tensorflow.framework"; +option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/versions_go_proto"; + +// Version information for a piece of serialized data +// +// There are different types of versions for each type of data +// (GraphDef, etc.), but they all have the same common shape +// described here. +// +// Each consumer has "consumer" and "min_producer" versions (specified +// elsewhere). A consumer is allowed to consume this data if +// +// producer >= min_producer +// consumer >= min_consumer +// consumer not in bad_consumers +// +message VersionDef { + // The version of the code that produced this data. + int32 producer = 1; + + // Any consumer below this version is not allowed to consume this data. + int32 min_consumer = 2; + + // Specific consumer versions which are disallowed (e.g. due to bugs). + repeated int32 bad_consumers = 3; +} diff --git a/ngraph/frontend/tensorflow/src/tensorflow.cpp b/ngraph/frontend/tensorflow/src/tensorflow.cpp new file mode 100644 index 00000000000000..38b5217a3230ae --- /dev/null +++ b/ngraph/frontend/tensorflow/src/tensorflow.cpp @@ -0,0 +1,75 @@ +//***************************************************************************** +// 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 "graph.pb.h" + +#include "../include/tensorflow_frontend/tensorflow.hpp" + +#include "ngraph_builder.h" + +using namespace google; + +using namespace ngraph::frontend; + +InputModelTensorflow::InputModelTensorflow (const std::string& _path) : path(_path) +{ + std::ifstream pb_stream(path, std::ios::binary); + graph_def = std::make_shared(); + std::cout << "[ INFO ] Model Parsed: " << graph_def->ParseFromIstream(&pb_stream) << std::endl; + std::cout << "[ INFO ] Loaded model contains " << graph_def->node_size() << " nodes." << std::endl; +} + +std::vector InputModelTensorflow::getInputs () const { +// TODO: Cache results + std::vector result; + for (int i = 0; i < graph_def->node_size(); ++i) { + if (graph_def->node(i).op() == "Placeholder") + result.push_back(std::make_shared(graph_def->node(i).name())); + } + return result; +} + +void InputModelTensorflow::setPartialShape (Place::Ptr place, const ngraph::PartialShape& pshape) { + auto place_tf = std::dynamic_pointer_cast(place); + partialShapes[place_tf->name] = pshape; +} + +std::shared_ptr ngraph::frontend::FrontEndTensorflow::convert (InputModel::Ptr model) const +{ + auto model_tf = std::dynamic_pointer_cast(model); + std::cerr << "[ INFO ] FrontEndTensorflow::convert invoked\n"; + + std::shared_ptr f; + std::cerr << "[ STATUS ] TranslateGraph return: " << tensorflow::ngraph_bridge::Builder::TranslateGraph( + model_tf->partialShapes, {}, model_tf->graph_def.get(), "here_should_be_a_graph_name", f) << "\n"; + std::cerr << "[ INFO ] Resulting nGraph function contains " << f->get_ops().size() << " nodes." << std::endl; + std::cerr << "[ STATUS ] Running Transpose Sinking transformation\n"; + + ngraph::pass::Manager manager; + manager.register_pass(); + manager.register_pass(); + manager.run_passes(f); + + std::cerr << "[ INFO ] Resulting nGraph function contains " << f->get_ops().size() << " nodes." << std::endl; + return f; +} diff --git a/ngraph/python/CMakeLists.txt b/ngraph/python/CMakeLists.txt index cacaa1b669af73..ff1a01c1b7929c 100644 --- a/ngraph/python/CMakeLists.txt +++ b/ngraph/python/CMakeLists.txt @@ -78,7 +78,8 @@ file(GLOB_RECURSE SOURCES src/pyngraph/*.cpp) pybind11_add_module(_${PROJECT_NAME} MODULE ${SOURCES}) target_include_directories(_${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") -target_link_libraries(_${PROJECT_NAME} PRIVATE ngraph::ngraph) +target_include_directories(_${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../frontend/generic/include") +target_link_libraries(_${PROJECT_NAME} PRIVATE ngraph::ngraph ngraph::onnx_importer ngraph::frontend_manager) if (TARGET ngraph::onnx_importer) add_dependencies(_${PROJECT_NAME} ngraph::onnx_importer) endif() diff --git a/ngraph/python/src/ngraph/__init__.py b/ngraph/python/src/ngraph/__init__.py index 0b276049d33ea8..4393d7d249542f 100644 --- a/ngraph/python/src/ngraph/__init__.py +++ b/ngraph/python/src/ngraph/__init__.py @@ -12,7 +12,13 @@ __version__ = "0.0.0.dev0" from ngraph.impl import Node +from ngraph.impl import PartialShape +from ngraph.impl import Dimension from ngraph.impl import Function +from ngraph.impl import FrontEndManager +from ngraph.impl import FrontEndCapabilities +from ngraph.impl import FrontEnd +from ngraph.impl import InputModel from ngraph.helpers import function_from_cnn from ngraph.helpers import function_to_cnn diff --git a/ngraph/python/src/ngraph/impl/__init__.py b/ngraph/python/src/ngraph/impl/__init__.py index 259a6e277f0e2b..0fd393f792b0a7 100644 --- a/ngraph/python/src/ngraph/impl/__init__.py +++ b/ngraph/python/src/ngraph/impl/__init__.py @@ -36,11 +36,16 @@ from _pyngraph import Dimension from _pyngraph import Function +from _pyngraph import FrontEndManager +from _pyngraph import FrontEnd +from _pyngraph import FrontEndCapabilities +from _pyngraph import InputModel from _pyngraph import Input from _pyngraph import Output from _pyngraph import Node from _pyngraph import Type from _pyngraph import PartialShape +from _pyngraph import Dimension from _pyngraph import Shape from _pyngraph import Strides from _pyngraph import CoordinateDiff diff --git a/ngraph/python/src/pyngraph/frontend_manager.cpp b/ngraph/python/src/pyngraph/frontend_manager.cpp new file mode 100644 index 00000000000000..57b8c5998a6131 --- /dev/null +++ b/ngraph/python/src/pyngraph/frontend_manager.cpp @@ -0,0 +1,82 @@ +//***************************************************************************** +// 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 "frontend_manager/frontend_manager.hpp" +#include "pyngraph/function.hpp" + +namespace py = pybind11; + + +void regclass_pyngraph_FrontEndManager(py::module m) +{ + py::class_> fem(m, "FrontEndManager", py::dynamic_attr()); + fem.doc() = "ngraph.impl.FrontEndManager wraps ngraph::frontend::FrontEndManager"; + + fem.def(py::init<>()); + + fem.def("availableFrontEnds", &ngraph::frontend::FrontEndManager::availableFrontEnds); + fem.def("loadByFramework", &ngraph::frontend::FrontEndManager::loadByFramework, py::arg("framework"), py::arg("capabilities") = ngraph::frontend::FEC_DEFAULT); +} + +void regclass_pyngraph_FrontEnd(py::module m) +{ + py::class_> fem(m, "FrontEnd", py::dynamic_attr()); + fem.doc() = "ngraph.impl.FrontEnd wraps ngraph::frontend::FrontEnd"; + + fem.def("loadFromFile", &ngraph::frontend::FrontEnd::loadFromFile); + fem.def("convert", static_cast (ngraph::frontend::FrontEnd::*)(ngraph::frontend::InputModel::Ptr)const>(&ngraph::frontend::FrontEnd::convert)); + fem.def("convert", static_cast (ngraph::frontend::FrontEnd::*)(std::shared_ptr)const>(&ngraph::frontend::FrontEnd::convert)); +} + +void regclass_pyngraph_Place(py::module m) +{ + py::class_> fem(m, "Place", py::dynamic_attr()); + fem.doc() = "ngraph.impl.Place wraps ngraph::frontend::Place"; + + //fem.def("load", &ngraph::frontend::FrontEnd::load); + //fem.def("convert", &ngraph::frontend::FrontEnd::convert); +} + +void regclass_pyngraph_InputModel(py::module m) +{ + py::class_> im(m, "InputModel", py::dynamic_attr()); + im.doc() = "ngraph.impl.InputModel wraps ngraph::frontend::InputModel"; + im.def("extractSubgraph", &ngraph::frontend::InputModel::extractSubgraph); + im.def("getPlaceByTensorName", &ngraph::frontend::InputModel::getPlaceByTensorName); + im.def("setPartialShape", &ngraph::frontend::InputModel::setPartialShape); + im.def("getInputs", &ngraph::frontend::InputModel::getInputs); +} + +void regclass_pyngraph_FEC(py::module m) +{ + py::class_> type(m, "FrontEndCapabilities"); + //type.doc() = "FrontEndCapabilities"; + type.attr("DEFAULT") = ngraph::frontend::FEC_DEFAULT; + type.attr("CUT") = ngraph::frontend::FEC_CUT; + type.attr("NAMES") = ngraph::frontend::FEC_NAMES; + type.attr("REPLACE") = ngraph::frontend::FEC_REPLACE; + type.attr("TRAVERSE") = ngraph::frontend::FEC_TRAVERSE; + type.attr("WILDCARDS") = ngraph::frontend::FEC_WILDCARDS; + + type.def("__eq__", + [](const ngraph::frontend::FrontEndCapabilities& a, const ngraph::frontend::FrontEndCapabilities& b) { return a == b; }, + py::is_operator()); + +} diff --git a/ngraph/python/src/pyngraph/frontend_manager.hpp b/ngraph/python/src/pyngraph/frontend_manager.hpp new file mode 100644 index 00000000000000..4446fb45f40d56 --- /dev/null +++ b/ngraph/python/src/pyngraph/frontend_manager.hpp @@ -0,0 +1,27 @@ +//***************************************************************************** +// 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 py = pybind11; + +void regclass_pyngraph_FrontEndManager(py::module m); +void regclass_pyngraph_FrontEnd(py::module m); +void regclass_pyngraph_InputModel(py::module m); +void regclass_pyngraph_FEC(py::module m); +void regclass_pyngraph_Place(py::module m); diff --git a/ngraph/python/src/pyngraph/pyngraph.cpp b/ngraph/python/src/pyngraph/pyngraph.cpp index 92b507b64834e1..0a4fb8f5d71a29 100644 --- a/ngraph/python/src/pyngraph/pyngraph.cpp +++ b/ngraph/python/src/pyngraph/pyngraph.cpp @@ -16,6 +16,7 @@ #if defined(NGRAPH_ONNX_IMPORT_ENABLE) #include "pyngraph/onnx_import/onnx_import.hpp" #endif +#include "pyngraph/frontend_manager.hpp" #include "pyngraph/dimension.hpp" #include "pyngraph/ops/constant.hpp" #include "pyngraph/ops/parameter.hpp" @@ -41,6 +42,11 @@ PYBIND11_MODULE(_pyngraph, m) regclass_pyngraph_Shape(m); regclass_pyngraph_PartialShape(m); regclass_pyngraph_Node(m); + regclass_pyngraph_Place(m); + regclass_pyngraph_FEC(m); + regclass_pyngraph_FrontEndManager(m); + regclass_pyngraph_FrontEnd(m); + regclass_pyngraph_InputModel(m); regclass_pyngraph_Input(m); regclass_pyngraph_Output(m); regclass_pyngraph_NodeFactory(m); diff --git a/ngraph/test/CMakeLists.txt b/ngraph/test/CMakeLists.txt index dd7f23d04d2eac..064d29b88d5144 100644 --- a/ngraph/test/CMakeLists.txt +++ b/ngraph/test/CMakeLists.txt @@ -532,3 +532,5 @@ if (NGRAPH_INTERPRETER_ENABLE) target_compile_definitions(unit-test PRIVATE NGRAPH_INTERPRETER_ENABLE) target_link_libraries(unit-test PRIVATE interpreter_backend) endif() + +MESSAGE("HERE2" ${ONNX_IMPORT_INCLUDE_DIR}) \ No newline at end of file diff --git a/ngraph/test/models/onnx/model_editor/add_ab.prototxt b/ngraph/test/models/onnx/model_editor/add_ab.prototxt new file mode 100644 index 00000000000000..b614b84c379dd9 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/add_ab.prototxt @@ -0,0 +1,59 @@ +ir_version: 3 +producer_name: "nGraph ONNX Importer" +graph { + node { + input: "A" + input: "B" + output: "X" + op_type: "Add" + } + node { + input: "X" + input: "X" + output: "Y" + op_type: "Add" + } + name: "test_graph" + input { + name: "A" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "B" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "Y" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } +} +opset_import { + version: 13 +} 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 index ade2123bafd865..689ddab137aef9 100644 --- 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 @@ -2,9 +2,16 @@ ir_version: 7 producer_name: "test_data_generator" graph { node { - input: "relu1" + input: "in1" + output: "relu1" + name: "relu1_name" + op_type: "Relu" + } + node { + input: "add1/placeholder_port_0" input: "in2" output: "add1" + name: "add_ambiguous_name" op_type: "Add" } node { @@ -17,18 +24,21 @@ graph { input: "relu1" input: "add1" output: "add2" + name: "add_ambiguous_name" op_type: "Add" } node { input: "add1" input: "conv1" output: "mul2" + name: "" op_type: "Mul" } node { input: "add2" output: "split1" output: "split2" + name: "split_name" op_type: "Split" attribute { name: "axis" @@ -37,7 +47,7 @@ graph { } } node { - input: "relu1" + input: "mul1/placeholder_port_0" input: "split1" output: "mul1" op_type: "Mul" @@ -52,6 +62,22 @@ graph { 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 { @@ -107,7 +133,23 @@ graph { } } input { - name: "relu1" + name: "add1/placeholder_port_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "mul1/placeholder_port_0" type { tensor_type { elem_type: 1 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 index 5d62d03bb617ac..b219693f3c6ec5 100644 --- 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 @@ -1,6 +1,19 @@ ir_version: 7 producer_name: "test_data_generator" graph { + node { + input: "in1" + output: "relu1" + name: "relu1_name" + op_type: "Relu" + } + node { + input: "relu1" + input: "in2" + output: "add1" + name: "add_ambiguous_name" + op_type: "Add" + } node { input: "in3" input: "in4" @@ -8,15 +21,17 @@ graph { op_type: "Conv" } node { - input: "relu1" - input: "add1" + input: "add2/placeholder_port_0" + input: "add2/placeholder_port_1" output: "add2" + name: "add_ambiguous_name" op_type: "Add" } node { input: "add1" input: "conv1" output: "mul2" + name: "" op_type: "Mul" } name: "subgraph_extraction_testing" @@ -29,6 +44,32 @@ graph { 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 { @@ -74,7 +115,7 @@ graph { } } input { - name: "relu1" + name: "add2/placeholder_port_0" type { tensor_type { elem_type: 1 @@ -90,7 +131,7 @@ graph { } } input { - name: "add1" + name: "add2/placeholder_port_1" type { tensor_type { elem_type: 1 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 index ea70f359331898..314cb096a9b2e7 100644 --- 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 @@ -1,22 +1,31 @@ ir_version: 7 producer_name: "test_data_generator" graph { + node { + input: "in1" + output: "relu1" + name: "relu1_name" + op_type: "Relu" + } node { input: "relu1" input: "in2" output: "add1" + name: "add_ambiguous_name" op_type: "Add" } node { - input: "relu1" + input: "add2/placeholder_port_0" input: "add1" output: "add2" + name: "add_ambiguous_name" op_type: "Add" } node { input: "add2" output: "split1" output: "split2" + name: "split_name" op_type: "Split" attribute { name: "axis" @@ -25,12 +34,28 @@ graph { } } node { - input: "relu1" + input: "mul1/placeholder_port_0" input: "split1" output: "mul1" op_type: "Mul" } 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 { @@ -42,7 +67,23 @@ graph { } } input { - name: "relu1" + name: "add2/placeholder_port_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "mul1/placeholder_port_0" type { tensor_type { elem_type: 1 diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_4.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_4.prototxt new file mode 100644 index 00000000000000..dc7c3b7c91db03 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_4.prototxt @@ -0,0 +1,224 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + name: "relu1_name" + op_type: "Relu" + } + node { + input: "add1/placeholder_port_0" + input: "in2" + output: "add1" + name: "add_ambiguous_name" + op_type: "Add" + } + node { + input: "in3" + input: "in4" + output: "conv1" + op_type: "Conv" + } + node { + input: "add2/placeholder_port_0" + input: "add1" + output: "add2" + name: "add_ambiguous_name" + op_type: "Add" + } + node { + input: "add1" + input: "conv1" + output: "mul2" + name: "" + op_type: "Mul" + } + node { + input: "add2" + output: "split1" + output: "split2" + name: "split_name" + 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 + } + } + } + } + } + input { + name: "add1/placeholder_port_0" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 2 + } + dim { + dim_value: 2 + } + } + } + } + } + input { + name: "add2/placeholder_port_0" + 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 + } + } + } + } + } + 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_5.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_5.prototxt new file mode 100644 index 00000000000000..e2afe1ff1e2702 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__input_edge_from_tensor_with_multiple_consumers_5.prototxt @@ -0,0 +1,120 @@ +ir_version: 7 +producer_name: "test_data_generator" +graph { + node { + input: "in1" + output: "relu1" + name: "relu1_name" + op_type: "Relu" + } + node { + input: "relu1" + input: "in2" + output: "add1" + name: "add_ambiguous_name" + op_type: "Add" + } + node { + input: "add2/placeholder_port_0" + input: "add1" + output: "add2" + name: "add_ambiguous_name" + op_type: "Add" + } + node { + input: "add2" + output: "split1" + output: "split2" + name: "split_name" + 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: "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: "add2/placeholder_port_0" + 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__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 index 186d8f4f01c400..5c5083fb84a36e 100644 --- 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 @@ -4,6 +4,12 @@ graph { node { input: "in1" output: "relu1" + name: "add1" + op_type: "Relu" + } + node { + input: "in1" + output: "relu2" op_type: "Relu" } node { @@ -14,16 +20,18 @@ graph { node { input: "in2" output: "relu4" + name: "relu4_name" op_type: "Relu" } node { input: "relu1" input: "relu2" output: "add1" + name: "add1_name" op_type: "Add" } node { - input: "relu2" + input: "add2/placeholder_port_0" input: "relu3" output: "add2" op_type: "Add" @@ -50,7 +58,7 @@ graph { } } input { - name: "relu2" + name: "add2/placeholder_port_0" type { tensor_type { elem_type: 1 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 index c8556c7d366c44..1319a560db2892 100644 --- 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 @@ -3,7 +3,7 @@ producer_name: "test_data_generator" graph { node { input: "in1" - output: "relu1" + output: "relu2" op_type: "Relu" } node { @@ -14,12 +14,14 @@ graph { node { input: "in2" output: "relu4" + name: "relu4_name" op_type: "Relu" } node { input: "relu1" input: "relu2" output: "add1" + name: "add1_name" op_type: "Add" } node { @@ -55,7 +57,7 @@ graph { } } input { - name: "relu2" + name: "relu1" type { tensor_type { elem_type: 1 diff --git a/ngraph/test/models/onnx/model_editor/reference/subgraph__twice_input_edge_from_tensor_with_single_consumer.prototxt b/ngraph/test/models/onnx/model_editor/reference/subgraph__twice_input_edge_from_tensor_with_single_consumer.prototxt new file mode 100644 index 00000000000000..12cbda0da5d621 --- /dev/null +++ b/ngraph/test/models/onnx/model_editor/reference/subgraph__twice_input_edge_from_tensor_with_single_consumer.prototxt @@ -0,0 +1,74 @@ +ir_version: 3 +producer_name: "nGraph ONNX Importer" +graph { + node { + input: "A" + input: "B" + output: "X" + name: "add_node1" + op_type: "Add" + } + node { + input: "X" + input: "Y/placeholder_port_1" + output: "Y" + name: "add_node2" + op_type: "Add" + } + name: "test_graph" + input { + name: "A" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "B" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + input { + name: "Y/placeholder_port_1" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } + output { + name: "Y" + type { + tensor_type { + elem_type: 1 + shape { + dim { + dim_value: 1 + } + } + } + } + } +} +opset_import { + version: 13 +} diff --git a/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt b/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt index f41a21428ab232..1b024156b0e825 100644 --- a/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt +++ b/ngraph/test/models/onnx/model_editor/subgraph__inception_head.prototxt @@ -8,7 +8,7 @@ graph { input: "conv1/7x7_s2_w_0" input: "conv1/7x7_s2_b_0" output: "conv1/7x7_s2_1" - name: "" + name: "conv1" op_type: "Conv" attribute { name: "strides" @@ -34,13 +34,13 @@ graph { node { input: "conv1/7x7_s2_1" output: "conv1/7x7_s2_2" - name: "" + name: "relu1" op_type: "Relu" } node { input: "conv1/7x7_s2_2" output: "pool1/3x3_s2_1" - name: "" + name: "maxpool1" op_type: "MaxPool" attribute { name: "strides" diff --git a/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt index 9e79acc9cf9411..fe8972df10c98d 100644 --- a/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt +++ b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests.prototxt @@ -5,12 +5,14 @@ graph { input: "in1" output: "relu1" op_type: "Relu" + name: "relu1_name" } node { input: "relu1" input: "in2" output: "add1" op_type: "Add" + name: "add_ambiguous_name" } node { input: "in3" @@ -23,12 +25,14 @@ graph { input: "add1" output: "add2" op_type: "Add" + name: "add_ambiguous_name" } node { input: "add1" input: "conv1" output: "mul2" op_type: "Mul" + name: "" } node { input: "add2" @@ -40,6 +44,7 @@ graph { i: 1 type: INT } + name: "split_name" } node { input: "relu1" 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 index 76d8587d9e8cb2..a5fbee597db549 100644 --- a/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests_2.prototxt +++ b/ngraph/test/models/onnx/model_editor/subgraph_extraction_tests_2.prototxt @@ -5,6 +5,7 @@ graph { input: "in1" output: "relu1" op_type: "Relu" + name: "add1" } node { input: "in1" @@ -20,12 +21,14 @@ graph { input: "in2" output: "relu4" op_type: "Relu" + name: "relu4_name" } node { input: "relu1" input: "relu2" output: "add1" op_type: "Add" + name: "add1_name" } node { input: "relu2" diff --git a/ngraph/test/onnx/onnx_editor.cpp b/ngraph/test/onnx/onnx_editor.cpp index 84aa9333b3cae1..fc784f605eee7c 100644 --- a/ngraph/test/onnx/onnx_editor.cpp +++ b/ngraph/test/onnx/onnx_editor.cpp @@ -21,8 +21,7 @@ NGRAPH_SUPPRESS_DEPRECATED_START using namespace ngraph; -using namespace ngraph::onnx_import; -using namespace ngraph::onnx_editor; +using namespace onnx_editor; using namespace ngraph::test; static std::string s_manifest = "${MANIFEST}"; @@ -274,12 +273,13 @@ NGRAPH_TEST(onnx_editor, shapes__static_to_dynamic_rank_substitution) } } + NGRAPH_TEST(onnx_editor, subgraph__linear_model_head_cut) { 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")}}, {}); + editor.cut_graph_fragment({{InputEdge(1, 0)}}, {}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_head_cut.prototxt"); @@ -294,8 +294,8 @@ NGRAPH_TEST(onnx_editor, subgraph__linear_model_head_cut_ins_and_outs) 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")}}); + editor.cut_graph_fragment({{InputEdge(1, 0)}}, + {{OutputEdge(2, 0)}}); // expected to behave the same way as subgraph__linear_model_head_cut const auto ref_model = file_util::path_join( @@ -311,7 +311,7 @@ NGRAPH_TEST(onnx_editor, subgraph__linear_model_deeper_head_cut) 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")}}, {}); + editor.cut_graph_fragment({{InputEdge(2, 0)}}, {}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, @@ -327,7 +327,7 @@ NGRAPH_TEST(onnx_editor, subgraph__linear_model_tail_cut) 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"}}}); + editor.cut_graph_fragment({}, {{OutputEdge{1, 0}}}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__linear_model_tail_cut.prototxt"); @@ -342,7 +342,7 @@ NGRAPH_TEST(onnx_editor, subgraph__linear_model_tail_cut_ins_and_outs) 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"}}}); + editor.cut_graph_fragment({{InputEdge{0, 0}}}, {{OutputEdge{1, 0}}}); // expected to behave the same way as subgraph__linear_model_tail_cut const auto ref_model = file_util::path_join( @@ -358,7 +358,7 @@ NGRAPH_TEST(onnx_editor, subgraph__linear_model_with_initializer_tail_cut) 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"}}}); + editor.cut_graph_fragment({}, {{OutputEdge{1, 0}}}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, @@ -374,7 +374,7 @@ NGRAPH_TEST(onnx_editor, subgraph__initializer_without_matching_input_tail_cut) 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"}}}); + editor.cut_graph_fragment({}, {{OutputEdge{1, 0}}}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -391,7 +391,7 @@ NGRAPH_TEST(onnx_editor, subgraph__linear_model_deeper_tail_cut) 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"}}}); + editor.cut_graph_fragment({}, {{OutputEdge{0, 0}}}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, @@ -421,8 +421,8 @@ NGRAPH_TEST(onnx_editor, subgraph__initializer_to_input_replacement) 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"}}}); + editor.cut_graph_fragment({{InputEdge{0, 2}}}, + {{OutputEdge{0, 0}}}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, @@ -438,8 +438,8 @@ NGRAPH_TEST(onnx_editor, subgraph__initializer_to_input_replacement_2) 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"}}}); + editor.cut_graph_fragment({{InputEdge{0, 2}}}, + {{OutputEdge{0, 0}}}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, @@ -455,7 +455,7 @@ NGRAPH_TEST(onnx_editor, subgraph__multiout_op_output_edge) ONNXModelEditor editor{file_util::path_join( SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; - editor.cut_graph_fragment({}, {{OutputEdge{5, "split2"}}}); + editor.cut_graph_fragment({}, {{OutputEdge{5, 1}}}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, "onnx/model_editor/reference/subgraph__multiout_op_output_edge.prototxt"); @@ -470,8 +470,8 @@ NGRAPH_TEST(onnx_editor, subgraph__existing_inputs_and_outputs_based_extraction) 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"}}}); + editor.cut_graph_fragment({{InputEdge{1, 1}, InputEdge{2, 0}}}, + {{OutputEdge{4, 0}}}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -483,13 +483,30 @@ NGRAPH_TEST(onnx_editor, subgraph__existing_inputs_and_outputs_based_extraction) EXPECT_TRUE(result.is_ok) << result.error_message; } +NGRAPH_TEST(onnx_editor, subgraph__twice_input_edge_from_tensor_with_single_consumer) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/add_ab.prototxt")}; + + editor.cut_graph_fragment({InputEdge{1, 1}}, {}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__twice_input_edge_from_tensor_with_single_consumer.prototxt"); + + 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) { 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"}}}); + editor.cut_graph_fragment({{InputEdge{1, 0}, InputEdge{6, 0}}}, + {{OutputEdge{6, 0}, OutputEdge{4, 0}}}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -506,8 +523,8 @@ NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumer 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"}}}); + editor.cut_graph_fragment({{InputEdge{3, 0}, InputEdge{3, 1}}}, + {{OutputEdge{3, 0}, OutputEdge{4, 0}}}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -524,8 +541,8 @@ NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumer 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"}}}); + editor.cut_graph_fragment({{InputEdge{3, 0}, InputEdge{6, 0}}}, + {{OutputEdge{6, 0}, OutputEdge{5, 1}}}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -542,14 +559,31 @@ NGRAPH_TEST(onnx_editor, subgraph__input_edge_from_tensor_with_multiple_consumer 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"}}}); + editor.cut_graph_fragment({{InputEdge{1, 0}, InputEdge{3, 0}}}, {}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__input_edge_from_tensor_with_multiple_consumers_4.prototxt"); + + 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_5) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + editor.cut_graph_fragment({InputEdge{3, 0}}, + {{OutputEdge{6,0}, OutputEdge{5, 1}}}); // 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.prototxt"); + "subgraph__input_edge_from_tensor_with_multiple_consumers_5.prototxt"); const auto result = compare_onnx_models(editor.model_string(), ref_model); @@ -561,7 +595,7 @@ NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_input_relu2) ONNXModelEditor editor{file_util::path_join( SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; - editor.cut_graph_fragment({{InputEdge{4, "relu2"}}}, {}); + editor.cut_graph_fragment({{InputEdge{4, 0}}}, {}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -578,7 +612,7 @@ NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_initializer) ONNXModelEditor editor{file_util::path_join( SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; - editor.cut_graph_fragment({{InputEdge{2, "in2"}}}, {}); + editor.cut_graph_fragment({{InputEdge{2, 0}}}, {}); const auto ref_model = file_util::path_join(SERIALIZED_ZOO, @@ -595,7 +629,7 @@ NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_initializer_2) 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"}}}, {}); + editor.cut_graph_fragment({{InputEdge{2, 0}, InputEdge{3, 0}}}, {}); // same as above const auto ref_model = @@ -613,7 +647,7 @@ NGRAPH_TEST(onnx_editor, subgraph__multiple_consumers_of_graph_initializer_relu2 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"}}}, {}); + editor.cut_graph_fragment({{InputEdge{5, 0}, InputEdge{3, 0}}}, {}); const auto ref_model = file_util::path_join( SERIALIZED_ZOO, @@ -631,18 +665,36 @@ NGRAPH_TEST(onnx_editor, subgraph__invalid_edge_idx) file_util::path_join(SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt"); ONNXModelEditor editor{model_path}; - - EXPECT_THROW(editor.cut_graph_fragment({{InputEdge{15, "x"}}}, {}), ngraph::ngraph_error); + try + { + editor.cut_graph_fragment({{InputEdge{15, 0}}}, {}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE( + msg.find("The specified node index is out of range of nodes in the original model") != + std::string::npos); + } } -NGRAPH_TEST(onnx_editor, subgraph__invalid_edge_name) +NGRAPH_TEST(onnx_editor, subgraph__invalid_port_idx) { const auto model_path = file_util::path_join(SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt"); ONNXModelEditor editor{model_path}; - - EXPECT_THROW(editor.cut_graph_fragment({{InputEdge{0, "x"}}}, {}), ngraph::ngraph_error); + try + { + editor.cut_graph_fragment({{InputEdge{0, 3}}}, {}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE( + msg.find("Node with index: 0 has not input with index: 3") != + std::string::npos); + } } NGRAPH_TEST(onnx_editor, subgraph__inputs_getter) @@ -653,11 +705,539 @@ NGRAPH_TEST(onnx_editor, subgraph__inputs_getter) 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")}}, {}); + editor.cut_graph_fragment({{InputEdge{1, 0}}}, {}); EXPECT_EQ(editor.model_inputs(), (std::vector{"conv1/7x7_s2_1"})); } +// HIGHT LEVEL API TESTS +// INPUT EDGES TEST +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_output_name_and_input_name) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + const InputEdge edge = editor.find_input_edge(EditorNode{EditorOutput{"conv1/7x7_s2_2"}}, + EditorInput{"conv1/7x7_s2_1"}); + EXPECT_EQ(edge.m_node_idx, 1); + EXPECT_EQ(edge.m_port_idx, 0); + + const InputEdge edge2 = editor.find_input_edge(EditorNode{EditorOutput{"conv1/7x7_s2_1"}}, + EditorInput{"data_0"}); + EXPECT_EQ(edge2.m_node_idx, 0); + EXPECT_EQ(edge2.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_output_name_and_input_index) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + const InputEdge edge = + editor.find_input_edge(EditorNode{EditorOutput{"conv1/7x7_s2_2"}}, EditorInput{0}); + EXPECT_EQ(edge.m_node_idx, 1); + EXPECT_EQ(edge.m_port_idx, 0); + + const InputEdge edge2 = + editor.find_input_edge(EditorNode{EditorOutput{"conv1/7x7_s2_1"}}, EditorInput{1}); + EXPECT_EQ(edge2.m_node_idx, 0); + EXPECT_EQ(edge2.m_port_idx, 1); + + const InputEdge edge3 = + editor.find_input_edge(EditorNode{EditorOutput{"conv1/7x7_s2_1"}}, EditorInput{2}); + EXPECT_EQ(edge3.m_node_idx, 0); + EXPECT_EQ(edge3.m_port_idx, 2); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_node_name_and_input_name) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; + + const InputEdge edge = + editor.find_input_edge(EditorNode{"relu1"}, EditorInput{"conv1/7x7_s2_1"}); + EXPECT_EQ(edge.m_node_idx, 1); + EXPECT_EQ(edge.m_port_idx, 0); + + const InputEdge edge2 = + editor.find_input_edge(EditorNode{"conv1"}, EditorInput{"conv1/7x7_s2_w_0"}); + EXPECT_EQ(edge2.m_node_idx, 0); + EXPECT_EQ(edge2.m_port_idx, 1); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_node_name_and_input_index) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const InputEdge edge = editor.find_input_edge(EditorNode{"relu1_name"}, EditorInput{0}); + EXPECT_EQ(edge.m_node_idx, 0); + EXPECT_EQ(edge.m_port_idx, 0); + + const InputEdge edge2 = editor.find_input_edge(EditorNode{"split_name"}, EditorInput{0}); + EXPECT_EQ(edge2.m_node_idx, 5); + EXPECT_EQ(edge2.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_empty_node_name) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + try + { + editor.find_input_edge(EditorNode{""}, EditorInput{"conv1/7x7_s2_1"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE( + msg.find("Node with name: not_given and output_name: not_given was not found") != + std::string::npos); + } +} + +// OUTPUT EDGES TEST +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_output_name) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const OutputEdge edge = + editor.find_output_edge(EditorNode{EditorOutput{"mul2"}}, EditorOutput{"mul2"}); + EXPECT_EQ(edge.m_node_idx, 4); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = + editor.find_output_edge(EditorNode{EditorOutput{"split1"}}, EditorOutput{"split2"}); + EXPECT_EQ(edge2.m_node_idx, 5); + EXPECT_EQ(edge2.m_port_idx, 1); + + // simplified overload + const OutputEdge edge3 = + editor.find_output_edge("mul2"); + EXPECT_EQ(edge3.m_node_idx, 4); + EXPECT_EQ(edge3.m_port_idx, 0); + + const OutputEdge edge4 = + editor.find_output_edge("split2"); + EXPECT_EQ(edge4.m_node_idx, 5); + EXPECT_EQ(edge4.m_port_idx, 1); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_output_name_and_output_index) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const OutputEdge edge = + editor.find_output_edge(EditorNode{EditorOutput{"add2"}}, EditorOutput{0}); + EXPECT_EQ(edge.m_node_idx, 3); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = + editor.find_output_edge(EditorNode{EditorOutput{"split1"}}, EditorOutput{1}); + EXPECT_EQ(edge2.m_node_idx, 5); + EXPECT_EQ(edge2.m_port_idx, 1); + + const OutputEdge edge3 = + editor.find_output_edge(EditorNode{EditorOutput{"split2"}}, EditorOutput{0}); + EXPECT_EQ(edge3.m_node_idx, 5); + EXPECT_EQ(edge3.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_node_name_and_output_name) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const OutputEdge edge = + editor.find_output_edge(EditorNode{"relu1_name"}, EditorOutput{"relu1"}); + EXPECT_EQ(edge.m_node_idx, 0); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = + editor.find_output_edge(EditorNode{"split_name"}, EditorOutput{"split2"}); + EXPECT_EQ(edge2.m_node_idx, 5); + EXPECT_EQ(edge2.m_port_idx, 1); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_node_name_and_output_index) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const OutputEdge edge = editor.find_output_edge(EditorNode{"relu1_name"}, EditorOutput{0}); + EXPECT_EQ(edge.m_node_idx, 0); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = editor.find_output_edge(EditorNode{"split_name"}, EditorOutput{1}); + EXPECT_EQ(edge2.m_node_idx, 5); + EXPECT_EQ(edge2.m_port_idx, 1); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_edge_const_network) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + const InputEdge edge = + editor.find_input_edge(EditorNode{EditorOutput{"relu4"}}, EditorInput{0}); + EXPECT_EQ(edge.m_node_idx, 3); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = editor.find_output_edge(EditorNode{"relu4_name"}, EditorOutput{0}); + EXPECT_EQ(edge2.m_node_idx, 3); + EXPECT_EQ(edge2.m_port_idx, 0); + + const OutputEdge edge3 = editor.find_output_edge(EditorNode{"add1_name"}, EditorOutput{0}); + EXPECT_EQ(edge3.m_node_idx, 4); + EXPECT_EQ(edge3.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_edge_error_handling) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + // node with given output name not found + try + { + editor.find_input_edge(EditorNode{EditorOutput{"not_existed"}}, EditorInput{0}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE( + msg.find("Node with name: not_given and output_name: not_existed was not found") != + std::string::npos); + } + + // node with given name not found + try + { + editor.find_input_edge(EditorNode{"not_existed"}, EditorInput{0}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE( + msg.find("Node with name: not_existed and output_name: not_given was not found") != + std::string::npos); + } + + // input index out of scope + try + { + editor.find_input_edge(EditorNode{"relu4_name"}, EditorInput{1}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Node with index: 3 has not input with index: 1") != + std::string::npos); + } + + // output index out of scope + try + { + editor.find_output_edge(EditorNode{"relu4_name"}, EditorOutput{1}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Node with index: 3 has not output with index: 1") != + std::string::npos); + } + + // input name not found + try + { + editor.find_input_edge(EditorNode{"relu4_name"}, EditorInput{"not_existed"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Node with index: 3 has not input with name: not_existed") != + std::string::npos); + } + + // output name not found + try + { + editor.find_output_edge(EditorNode{"relu4_name"}, EditorOutput{"not_existed"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Node with index: 3 has not output with name: not_existed") != + std::string::npos); + } +} + +// Nodes with ambiguous node names tests +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_ambiguous_node_name_but_matched_input) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + InputEdge edge = editor.find_input_edge(EditorNode{"add_ambiguous_name"}, EditorInput{"in2"}); + EXPECT_EQ(edge.m_node_idx, 1); + EXPECT_EQ(edge.m_port_idx, 1); + + const InputEdge edge2 = editor.find_input_edge(EditorNode{"add_ambiguous_name"}, EditorInput{"add1"}); + EXPECT_EQ(edge2.m_node_idx, 3); + EXPECT_EQ(edge2.m_port_idx, 1); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_ambiguous_node_name_and_not_matched_input) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + try + { + editor.find_input_edge(EditorNode{"add_ambiguous_name"}, EditorInput{"in3"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Input edge described by: add_ambiguous_name and input name: in3 was not found") != + std::string::npos); + } + + try + { + editor.find_input_edge(EditorNode{"add_ambiguous_name"}, EditorInput{"relu1"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Given node name: add_ambiguous_name and input name: relu1 are ambiguous to determine input edge") != + std::string::npos); + } +} + +NGRAPH_TEST(onnx_editor, editor_api_select_input_edge_by_ambiguous_node_name_and_input_index) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + try + { + editor.find_input_edge(EditorNode{"add_ambiguous_name"}, EditorInput{0}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Given node name: add_ambiguous_name and input index: 0 are ambiguous to determine input edge") != + std::string::npos); + } +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_ambiguous_node_name_but_matched_output) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const OutputEdge edge = editor.find_output_edge(EditorNode{"add_ambiguous_name"}, EditorOutput{"add1"}); + EXPECT_EQ(edge.m_node_idx, 1); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = editor.find_output_edge(EditorNode{"add_ambiguous_name"}, EditorOutput{"add2"}); + EXPECT_EQ(edge2.m_node_idx, 3); + EXPECT_EQ(edge2.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_the_same_node_name_and_output_name) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests_2.prototxt")}; + + const OutputEdge edge = editor.find_output_edge(EditorNode{"add1"}, EditorOutput{0}); + EXPECT_EQ(edge.m_node_idx, 0); + EXPECT_EQ(edge.m_port_idx, 0); + + const OutputEdge edge2 = editor.find_output_edge(EditorNode{EditorOutput{"add1"}}, EditorOutput{0}); + EXPECT_EQ(edge2.m_node_idx, 4); + EXPECT_EQ(edge2.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_ambiguous_node_name_and_not_matched_output) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + try + { + editor.find_output_edge(EditorNode{"add_ambiguous_name"}, EditorOutput{"split2"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Output edge described by: add_ambiguous_name and output name: split2 was not found") != + std::string::npos); + } +} + +NGRAPH_TEST(onnx_editor, editor_api_select_output_edge_by_ambiguous_node_name_and_output_index) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + try + { + editor.find_output_edge(EditorNode{"add_ambiguous_name"}, EditorOutput{0}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Given node name: add_ambiguous_name and output index: 0 are ambiguous to determine output edge") != + std::string::npos); + } +} + +NGRAPH_TEST(onnx_editor, editor_api_use_edge_mapper_with_graph_cutter) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + // InputEdge{1, "in2"} + const auto input_edge_1 = editor.find_input_edge( + EditorNode(EditorOutput("add1")), EditorInput(1)); + // InputEdge{2, "in3"} + const auto input_edge_2 = editor.find_input_edge( + EditorNode(EditorOutput("conv1")), EditorInput(0)); + + + const auto output_edge = editor.find_output_edge( + EditorNode(EditorOutput("mul2")), EditorOutput(0)); + // OutputEdge{4, "mul2"} + editor.cut_graph_fragment({input_edge_1, input_edge_2}, {output_edge}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__existing_inputs_and_outputs_based_extraction.prototxt"); + + const auto result = compare_onnx_models(editor.model_string(), ref_model); + + EXPECT_TRUE(result.is_ok) << result.error_message; + + // check if mapper was updated after the model changed + const auto input_edge_4 = editor.find_input_edge( + EditorNode(EditorOutput("relu1")), EditorInput(0)); + EXPECT_EQ(input_edge_4.m_node_idx, 0); + EXPECT_EQ(input_edge_4.m_port_idx, 0); + + const auto input_edge_5 = editor.find_input_edge( + EditorNode(EditorOutput("add1")), EditorInput(1)); + EXPECT_EQ(input_edge_5.m_node_idx, 1); + EXPECT_EQ(input_edge_5.m_port_idx, 1); + + const auto output_edge_3 = editor.find_output_edge("mul2"); + EXPECT_EQ(output_edge_3.m_node_idx, 3); + EXPECT_EQ(output_edge_3.m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_find_output_consumers) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + std::vector output_consumers = editor.find_output_consumers("relu1"); + EXPECT_EQ(output_consumers.size(), 3); + EXPECT_EQ(output_consumers[0].m_node_idx, 1); + EXPECT_EQ(output_consumers[0].m_port_idx, 0); + EXPECT_EQ(output_consumers[1].m_node_idx, 3); + EXPECT_EQ(output_consumers[1].m_port_idx, 0); + EXPECT_EQ(output_consumers[2].m_node_idx, 6); + EXPECT_EQ(output_consumers[2].m_port_idx, 0); + + output_consumers = editor.find_output_consumers("add1"); + EXPECT_EQ(output_consumers.size(), 2); + EXPECT_EQ(output_consumers[0].m_node_idx, 3); + EXPECT_EQ(output_consumers[0].m_port_idx, 1); + EXPECT_EQ(output_consumers[1].m_node_idx, 4); + EXPECT_EQ(output_consumers[1].m_port_idx, 0); + + output_consumers = editor.find_output_consumers("in3"); + EXPECT_EQ(output_consumers.size(), 1); + EXPECT_EQ(output_consumers[0].m_node_idx, 2); + EXPECT_EQ(output_consumers[0].m_port_idx, 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_find_output_consumers_empty_result) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + const std::vector output_consumers = editor.find_output_consumers("not_existed"); + EXPECT_EQ(output_consumers.size(), 0); +} + +NGRAPH_TEST(onnx_editor, editor_api_is_correct_and_unambiguous_node) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/subgraph_extraction_tests.prototxt")}; + + bool is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{EditorOutput{"relu1"}}); + EXPECT_EQ(is_correct_node, true); + + is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{EditorOutput{"mul2"}}); + EXPECT_EQ(is_correct_node, true); + + is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{EditorOutput{"split2"}}); + EXPECT_EQ(is_correct_node, true); + + is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{"relu1_name"}); + EXPECT_EQ(is_correct_node, true); + + is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{EditorOutput{"in3"}}); + EXPECT_EQ(is_correct_node, false); + + is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{"add_ambiguous_name"}); + EXPECT_EQ(is_correct_node, false); + + is_correct_node = editor.is_correct_and_unambiguous_node(EditorNode{"not_exist"}); + EXPECT_EQ(is_correct_node, false); +} + +NGRAPH_TEST(onnx_editor, editor_api_input_edge_from_tensor_with_single_consumer) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/add_ab.prototxt")}; + + const auto edge = editor.find_input_edge(EditorNode{EditorOutput{"Y"}}, EditorInput{1}); + editor.cut_graph_fragment({edge}, {}); + + const auto ref_model = + file_util::path_join(SERIALIZED_ZOO, + "onnx/model_editor/reference/" + "subgraph__twice_input_edge_from_tensor_with_single_consumer.prototxt"); + + const auto result = compare_onnx_models(editor.model_string(), ref_model); + + EXPECT_TRUE(result.is_ok) << result.error_message; +} + +NGRAPH_TEST(onnx_editor, editor_api_input_edge_from_tensor_with_single_consumer_ambiguous) +{ + ONNXModelEditor editor{file_util::path_join( + SERIALIZED_ZOO, "onnx/model_editor/add_ab.prototxt")}; + + try + { + editor.find_input_edge(EditorNode{EditorOutput{"Y"}}, EditorInput{"X"}); + } + catch (const std::exception& e) + { + std::string msg{e.what()}; + EXPECT_TRUE(msg.find("Node with index: 1 has more than one inputs with name: X") != + std::string::npos); + } +} + using TestEngine = test::INTERPRETER_Engine; NGRAPH_TEST(onnx_editor, values__append_one_initializer) @@ -778,7 +1358,7 @@ NGRAPH_TEST(onnx_editor, combined__cut_and_replace_shape) SERIALIZED_ZOO, "onnx/model_editor/subgraph__inception_head.prototxt")}; const auto new_shape = PartialShape({1, 64, 112, 112}); - editor.cut_graph_fragment({{InputEdge(1, "conv1/7x7_s2_1")}}, {}); + editor.cut_graph_fragment({{InputEdge(1, 0)}}, {}); editor.set_input_shapes({{"conv1/7x7_s2_1", new_shape}}); const auto ref_model = file_util::path_join( diff --git a/tools/pdpd_poc/cat3.bmp b/tools/pdpd_poc/cat3.bmp new file mode 100644 index 00000000000000..2de8516d2099b6 Binary files /dev/null and b/tools/pdpd_poc/cat3.bmp differ diff --git a/tools/pdpd_poc/download_model.py b/tools/pdpd_poc/download_model.py new file mode 100644 index 00000000000000..8e60fa00cedd42 --- /dev/null +++ b/tools/pdpd_poc/download_model.py @@ -0,0 +1,7 @@ +def download_pdpd_resnet50(): + import paddlehub as hub + module = hub.Module(name="resnet_v2_50_imagenet") + return module.directory + +print(download_pdpd_resnet50()) + diff --git a/tools/pdpd_poc/framework.proto b/tools/pdpd_poc/framework.proto new file mode 100644 index 00000000000000..baaecb55d06ee3 --- /dev/null +++ b/tools/pdpd_poc/framework.proto @@ -0,0 +1,205 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. + +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. */ + +syntax = "proto2"; +package paddle.framework.proto; + +// Any incompatible changes to ProgramDesc and its dependencies should +// raise the version defined version.h. +// +// Serailization and Deserialization codes should be modified in a way +// that supports old versions following the version and compatibility policy. +message Version { optional int64 version = 1 [ default = 0 ]; } + +enum AttrType { + INT = 0; + FLOAT = 1; + STRING = 2; + INTS = 3; + FLOATS = 4; + STRINGS = 5; + BOOLEAN = 6; + BOOLEANS = 7; + BLOCK = 8; + LONG = 9; + BLOCKS = 10; + LONGS = 11; +} + +// OpDesc describes an instance of a C++ framework::OperatorBase +// derived class type. +message OpDesc { + + message Attr { + required string name = 1; + required AttrType type = 2; + optional int32 i = 3; + optional float f = 4; + optional string s = 5; + repeated int32 ints = 6; + repeated float floats = 7; + repeated string strings = 8; + optional bool b = 10; + repeated bool bools = 11; + optional int32 block_idx = 12; + optional int64 l = 13; + repeated int32 blocks_idx = 14; + repeated int64 longs = 15; + }; + + message Var { + required string parameter = 1; + repeated string arguments = 2; + }; + + required string type = 3; + repeated Var inputs = 1; + repeated Var outputs = 2; + repeated Attr attrs = 4; + optional bool is_target = 5 [ default = false ]; +}; + +// OpProto describes a C++ framework::OperatorBase derived class. +message OpProto { + + // VarProto describes the C++ type framework::Variable. + message Var { + required string name = 1; + required string comment = 2; + + optional bool duplicable = 3 [ default = false ]; + optional bool intermediate = 4 [ default = false ]; + optional bool dispensable = 5 [ default = false ]; + } + + // AttrProto describes the C++ type Attribute. + message Attr { + required string name = 1; + required AttrType type = 2; + required string comment = 3; + // If that attribute is generated, it means the Paddle third + // language binding has responsibility to fill that + // attribute. End-User should not set that attribute. + optional bool generated = 4 [ default = false ]; + } + + required string type = 1; + repeated Var inputs = 2; + repeated Var outputs = 3; + repeated Attr attrs = 4; + required string comment = 5; +} + +message VarType { + enum Type { + // Pod Types + BOOL = 0; + INT16 = 1; + INT32 = 2; + INT64 = 3; + FP16 = 4; + FP32 = 5; + FP64 = 6; + // Tensor is used in C++. + SIZE_T = 19; + UINT8 = 20; + INT8 = 21; + BF16 = 22; + COMPLEX64 = 23; + COMPLEX128 = 24; + + // Other types that may need additional descriptions + LOD_TENSOR = 7; + SELECTED_ROWS = 8; + FEED_MINIBATCH = 9; + FETCH_LIST = 10; + STEP_SCOPES = 11; + LOD_RANK_TABLE = 12; + LOD_TENSOR_ARRAY = 13; + PLACE_LIST = 14; + READER = 15; + // Any runtime decided variable type is raw + // raw variables should manage their own allocations + // in operators like nccl_op + RAW = 17; + TUPLE = 18; + } + + required Type type = 1; + + message TensorDesc { + // Should only be PODType. Is enforced in C++ + required Type data_type = 1; + repeated int64 dims = 2; // [UNK, 640, 480] is saved as [-1, 640, 480] + } + optional TensorDesc selected_rows = 2; + + message LoDTensorDesc { + required TensorDesc tensor = 1; + optional int32 lod_level = 2 [ default = 0 ]; + } + optional LoDTensorDesc lod_tensor = 3; + + message LoDTensorArrayDesc { + required TensorDesc tensor = 1; + optional int32 lod_level = 2 [ default = 0 ]; + } + optional LoDTensorArrayDesc tensor_array = 4; + + message ReaderDesc { repeated LoDTensorDesc lod_tensor = 1; } + optional ReaderDesc reader = 5; + + message Tuple { repeated Type element_type = 1; } + optional Tuple tuple = 7; +} + +message VarDesc { + required string name = 1; + required VarType type = 2; + optional bool persistable = 3 [ default = false ]; + // True if the variable is an input data and + // have to check the feed data shape and dtype + optional bool need_check_feed = 4 [ default = false ]; +} + +message BlockDesc { + required int32 idx = 1; + required int32 parent_idx = 2; + repeated VarDesc vars = 3; + repeated OpDesc ops = 4; + optional int32 forward_block_idx = 5 [ default = -1 ]; +} + +// In some cases, Paddle may perform operator definition iterations, +// and the operator uses OpVersionMap for compatibility testing. +message OpVersion { required int32 version = 1; } +message OpVersionMap { + message OpVersionPair { + required string op_name = 1; + required OpVersion op_version = 2; + } + repeated OpVersionPair pair = 1; +} + +// Please refer to +// https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/program.md +// for more details. +// TODO(panyx0718): A model can have multiple programs. Need a +// way to distinguish them. Maybe ID or name? +message ProgramDesc { + reserved 2, 3; // For backward compatibility. + repeated BlockDesc blocks = 1; + optional Version version = 4; + optional OpVersionMap op_version_map = 5; +} diff --git a/tools/pdpd_poc/generate_proto.sh b/tools/pdpd_poc/generate_proto.sh new file mode 100644 index 00000000000000..e8e36e348f95a8 --- /dev/null +++ b/tools/pdpd_poc/generate_proto.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +protoc --python_out=. --cpp_out=. framework.proto \ No newline at end of file diff --git a/tools/pdpd_poc/pdpd2ir.py b/tools/pdpd_poc/pdpd2ir.py new file mode 100644 index 00000000000000..2661e1aa80555b --- /dev/null +++ b/tools/pdpd_poc/pdpd2ir.py @@ -0,0 +1,261 @@ +import numpy as np +import ngraph as ng + +import framework_pb2 + + +def download_pdpd_resnet50(): + import paddlehub as hub + + module = hub.Module(name="resnet_v2_50_imagenet") + return module.directory + + +def make_ng_node(inputs: dict, nodes: dict, op, block): + creators = { + 'conv2d': conv2d_creator, + 'batch_norm': batch_norm_creator, + 'relu': relu_creator, + 'pool2d': pool2d_creator, + 'elementwise_add': elementwise_add_creator, + 'mul': mul_creator, + 'scale': scale_creator + } + assert op.type in creators, 'No creator for {} layer'.format(op.type) + inputs_preproc = {} + for input, input_names in inputs.items(): + inputs_preproc[input] = [nodes[input_name] for input_name in input_names] + return creators[op.type](inputs_preproc, op, block) + + +def get_attr(op, name: str, field: str, default=None, dst_type=None): + attrs = [a for a in op.attrs if a.name == name] + if len(attrs) == 0: + # there is no requested attribute in the protobuf message + return default + elif len(attrs) > 1: + raise Exception( + 'Found multiple entries for attribute name {} when at most one is expected. Protobuf message with ' + 'the issue: {}.', name, op) + else: + res = getattr(attrs[0], field) + if dst_type is not None: + return dst_type(res) + else: + return res + + +def conv2d_creator(inputs: dict, op, block): + assert len(inputs['Input']) == 1 + data = inputs['Input'][0] + assert len(inputs['Filter']) == 1 + filter = inputs['Filter'][0] + assert len(inputs['Bias']) == 0 + assert len(inputs['ResidualData']) == 0 + # TODO: resolve padding according to spec + return ng.convolution(data, + filter, + strides=get_attr(op, 'strides', 'ints'), + pads_begin=get_attr(op, 'paddings', 'ints'), + pads_end=get_attr(op, 'paddings', 'ints'), + dilations=get_attr(op, 'dilations', 'ints')) + + +def batch_norm_creator(inputs: dict, op, block): + assert len(inputs['X']) == 1 + assert len(inputs['Scale']) == 1 + assert len(inputs['Bias']) == 1 + assert len(inputs['Mean']) == 1 + assert len(inputs['Variance']) == 1 + data = inputs['X'][0] + gamma = inputs['Scale'][0] + beta = inputs['Bias'][0] + mean = inputs['Mean'][0] + variance = inputs['Variance'][0] + return ng.batch_norm_inference(data, gamma, beta, mean, variance, epsilon=get_attr(op, 'epsilon', 'f')) + + +def relu_creator(inputs: dict, op, block): + data = inputs['X'][0] + return ng.relu(data) + + +def pool2d_creator(inputs: dict, op, block): + data = inputs['X'][0] + # TODO: resolve padding according to spec + pooling_type = get_attr(op, 'pooling_type', 's') + global_pooling = get_attr(op, 'global_pooling', 'b') + if pooling_type == 'max' and not global_pooling: + return ng.max_pool(data, + strides=get_attr(op, 'strides', 'ints'), + pads_begin=get_attr(op, 'paddings', 'ints'), + pads_end=get_attr(op, 'paddings', 'ints'), + kernel_shape=get_attr(op, 'ksize', 'ints')) + elif pooling_type == 'avg' and global_pooling: + # TODO: resolve axes according to rank + return ng.reduce_mean(data, np.array([2, 3]), keep_dims=True) + else: + raise Exception('Unsupported pooling type') + + +def elementwise_add_creator(inputs: dict, op, block): + x = inputs['X'][0] + y = inputs['Y'][0] + # TODO: resolve broadcast + return ng.add(x, y) + + +def mul_creator(inputs: dict, op, block): + x = inputs['X'][0] + y = inputs['Y'][0] + assert x.output(0).get_partial_shape().rank.is_static + x_rank = x.output(0).get_partial_shape().rank.get_length() + assert y.output(0).get_partial_shape().rank.is_static and y.output(0).get_partial_shape().rank.get_length() == 2 + if x_rank > 2: + shape = ng.shape_of(x) + x_num_col_dims = get_attr(op, 'x_num_col_dims', 'i') + split = ng.variadic_split(shape, + axis=0, + split_lengths=np.array([x_num_col_dims, x_rank - x_num_col_dims])) + first_dim = ng.reduce_prod(split.output(0), reduction_axes=0) + first_dim = ng.reshape(first_dim, np.array([1]), special_zero=False) + second_dim = ng.reduce_prod(split.output(1), reduction_axes=0) + second_dim = ng.reshape(second_dim, np.array([1]), special_zero=False) + out_shape = ng.concat([first_dim, second_dim], axis=0) + x = ng.reshape(x, out_shape, special_zero=False) + return ng.matmul(x, y, transpose_a=False, transpose_b=False) + + +def scale_creator(inputs: dict, op, block): + data = inputs['X'][0] + return ng.multiply(data, np.array(get_attr(op, 'scale', 'f'), dtype=np.float32)) + + +DTYPE_PADDLE_NUMPY_MAP = { + np.float32: framework_pb2.VarType.FP32, + np.float64: framework_pb2.VarType.FP64, + np.int16: framework_pb2.VarType.INT16, + np.int32: framework_pb2.VarType.INT32, + np.int64: framework_pb2.VarType.INT64, + np.bool: framework_pb2.VarType.BOOL, + framework_pb2.VarType.FP32: np.float32, + framework_pb2.VarType.FP64: np.float64, + framework_pb2.VarType.INT16: np.int16, + framework_pb2.VarType.INT32: np.int32, + framework_pb2.VarType.INT64: np.int64, + framework_pb2.VarType.BOOL: np.bool +} + + +def read_tensor(var, model_dir): + assert var.type.type == framework_pb2.VarType.LOD_TENSOR + tensor = var.type.lod_tensor.tensor + with open(model_dir + '/' + var.name, mode='rb') as file: + fileContent = file.read() + assert tensor.data_type == framework_pb2.VarType.FP32 + t_len = np.prod(tensor.dims) * 4 + # TODO: figure out what is written in header of a file + return np.frombuffer(fileContent[len(fileContent) - t_len:], dtype=np.float32).reshape(tensor.dims) + + +def convert_model(model_dir): + fw_model = framework_pb2.ProgramDesc() + + with open(model_dir + '/__model__', 'rb') as f: + fw_model.ParseFromString(f.read()) + + nodes_dict = {} + parameter_nodes = [] + result_nodes = [] + + global_block = fw_model.blocks[0] + for var in global_block.vars: + if var.name.endswith('feed') or var.name.endswith('fetch'): + # feed and fetch is the names of inputs and outputs of the model + continue + if not var.persistable: + continue + tensor = read_tensor(var, model_dir) + nodes_dict[var.name] = ng.constant(tensor, tensor.dtype, var.name) + + for block in fw_model.blocks: + vars_dict = dict(zip([var.name for var in block.vars], + [var.type for var in block.vars])) + for i, op in enumerate(block.ops): + outputs_dict = dict(zip([output.parameter for output in op.outputs], + [output.arguments for output in op.outputs])) + inputs_dict = dict(zip([inp.parameter for inp in op.inputs], + [inp.arguments for inp in op.inputs])) + if op.type == 'feed': + layer_name = outputs_dict['Out'][0] + var = vars_dict[layer_name] + assert var.type == framework_pb2.VarType.LOD_TENSOR + tensor_desc = var.lod_tensor.tensor + param = ng.parameter(tensor_desc.dims, DTYPE_PADDLE_NUMPY_MAP[tensor_desc.data_type], name=layer_name) + nodes_dict[layer_name] = param + parameter_nodes.append(param) + elif op.type == 'fetch': + input_node = inputs_dict['X'][0] + assert input_node in nodes_dict + result_nodes.append(ng.result(nodes_dict[input_node])) + else: + node = make_ng_node(inputs_dict, nodes_dict, op, block) + for outp_var_list in outputs_dict.values(): + assert len(outp_var_list) <= 1 + if len(outp_var_list) == 1: + nodes_dict[outp_var_list[0]] = node + + return ng.Function(result_nodes, parameter_nodes, "PDPD_Resnet50_Function") + + +if __name__ == "__main__": + import cv2 + + img = cv2.imread("cat3.bmp") + img = cv2.resize(img, (224, 224)) + img = np.transpose(img, [2, 0, 1]) / 255 + img = np.expand_dims(img, 0) + img_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1)) + img_std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1)) + img -= img_mean + img /= img_std + + model_path = download_pdpd_resnet50() + '/model' + + import paddle + from paddle import fluid + + paddle.enable_static() + exe = fluid.Executor(fluid.CPUPlace()) + exe.run(fluid.default_startup_program()) + [program, feed, fetchs] = fluid.io.load_inference_model(model_path, exe) + + result = exe.run(program, fetch_list=fetchs, + feed={feed[0]: img.astype(np.float32)}, + feed_var_name='@HUB_resnet_v2_50_imagenet@feed', + fetch_var_name='@HUB_resnet_v2_50_imagenet@fetch') + + ng_function = convert_model(model_path) + + ie_network = ng.function_to_cnn(ng_function) + ie_network.reshape({'@HUB_resnet_v2_50_imagenet@image': [1, 3, 224, 224]}) + ie_network.serialize('PDPD_Resnet50_Function.xml', 'PDPD_Resnet50_Function.bin') + + from openvino.inference_engine import IECore + + ie = IECore() + # executable_network = ie.load_network(ie_network, 'CPU') + + net = ie.read_network('/media/data/OpenVINO/openvino/build/PDPD_Resnet50_Function.xml', + '/media/data/OpenVINO/openvino/build/PDPD_Resnet50_Function.bin') + executable_network = ie.load_network(net, 'CPU') + + # output = executable_network.infer( + # {'@HUB_resnet_v2_50_imagenet@image': img.astype(np.float32)}) + output = executable_network.infer( + {'Parameter_267': img.astype(np.float32)}) + + print(np.abs(result[0] - list(output.values())[0]).max()) + print(np.abs(result[1] - list(output.values())[1]).max()) + + print('') diff --git a/tools/pdpd_poc/pdpd2ir_test.py b/tools/pdpd_poc/pdpd2ir_test.py new file mode 100644 index 00000000000000..c6e167f1f817f2 --- /dev/null +++ b/tools/pdpd_poc/pdpd2ir_test.py @@ -0,0 +1,114 @@ +from unittest import TestCase + +from pdpd2ir import convert_model +import numpy as np +import ngraph as ng + +test_path = '/tmp/pdpd2ir_test/model' + + +class TestConversion(TestCase): + @staticmethod + def infer_ie(func, inp_dict: dict): + from openvino.inference_engine import IECore + + ie = IECore() + ie_network = ng.function_to_cnn(func) + executable_network = ie.load_network(ie_network, 'CPU') + return executable_network.infer(inp_dict) + + @staticmethod + def validate(var: list, inp_dict: dict): + import paddle + from paddle import fluid + + paddle.enable_static() + exe = fluid.Executor(fluid.CPUPlace()) + exe.run(fluid.default_startup_program()) + res_pdpd = exe.run(fluid.default_main_program(), fetch_list=var, feed=inp_dict) + + fluid.io.save_inference_model(test_path, list(inp_dict.keys()), var, exe) + + func = convert_model(test_path) + + res_ie = TestConversion.infer_ie(func, inp_dict) + + return np.all(np.isclose(res_pdpd[0], list(res_ie.values())[0], rtol=1e-4, atol=1e-5)) + + def test_convert_pure_conv_model(self): + import paddle + from paddle import fluid + paddle.enable_static() + + inp_blob = np.random.randn(1, 3, 224, 224).astype(np.float32) + + x = fluid.data(name='x', shape=[1, 3, 224, 224], dtype='float32') + my_conv = fluid.layers.conv2d(input=x, num_filters=64, filter_size=(7, 7), stride=(2, 2), padding=(3, 3), + dilation=(1, 1), groups=1, bias_attr=False) + + result = self.validate([my_conv], {'x': inp_blob}) + + self.assertTrue(result) + + def test_convert_conv_model(self): + import paddle + from paddle import fluid + paddle.enable_static() + + inp_blob = np.random.randn(1, 3, 224, 224).astype(np.float32) + + x = fluid.data(name='x', shape=[1, 3, 224, 224], dtype='float32') + my_conv = fluid.layers.conv2d(input=x, num_filters=64, filter_size=(7, 7), stride=(2, 2), padding=(3, 3), + dilation=(1, 1), groups=1, bias_attr=False) + bn = fluid.layers.batch_norm(my_conv, act='relu', is_test=True) + + result = self.validate([bn], {'x': inp_blob}) + + self.assertTrue(result) + + def test_convert_resnet50(self): + import cv2 + + img = cv2.imread("cat3.bmp") + img = cv2.resize(img, (224, 224)) + img = np.transpose(img, [2, 0, 1]) / 255 + img = np.expand_dims(img, 0) + img_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1)) + img_std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1)) + img -= img_mean + img /= img_std + + import paddlehub as hub + + module = hub.Module(name="resnet_v2_50_imagenet") + + model_path = module.directory + '/model' + + import paddle + from paddle import fluid + paddle.enable_static() + + exe = fluid.Executor(fluid.CPUPlace()) + exe.run(fluid.default_startup_program()) + [program, feed, fetchs] = fluid.io.load_inference_model(model_path, exe) + + result = exe.run(program, fetch_list=fetchs, + feed={feed[0]: img.astype(np.float32)}, + feed_var_name='@HUB_resnet_v2_50_imagenet@feed', + fetch_var_name='@HUB_resnet_v2_50_imagenet@fetch') + + ng_function = convert_model(model_path) + + ie_network = ng.function_to_cnn(ng_function) + ie_network.reshape({'@HUB_resnet_v2_50_imagenet@image': [1, 3, 224, 224]}) + ie_network.serialize('PDPD_Resnet50_Function.xml', 'PDPD_Resnet50_Function.bin') + + from openvino.inference_engine import IECore + + ie = IECore() + executable_network = ie.load_network(ie_network, 'CPU') + + output = executable_network.infer( + {'@HUB_resnet_v2_50_imagenet@image': img.astype(np.float32)}) + + self.assertTrue(np.all(np.isclose(result[0], list(output.values())[0], rtol=1e-4, atol=1e-5))) diff --git a/tools/pdpd_poc/requirements.txt b/tools/pdpd_poc/requirements.txt new file mode 100644 index 00000000000000..aa8bc8bd797116 --- /dev/null +++ b/tools/pdpd_poc/requirements.txt @@ -0,0 +1,4 @@ +numpy +protobuf>=3.6.1 +paddlepaddle==2.0.0rc1 +paddlehub==2.0.0rc0