From 3de9189d50e6442c7f4a552fabac83af7504d0d7 Mon Sep 17 00:00:00 2001 From: Alexey Lebedev Date: Wed, 23 Mar 2022 11:44:00 +0300 Subject: [PATCH] [core][python] ov::serialize (#10945) * add ov::serialize * create python binding * update python tools * use ov::serialize in benchmark app * remove serialize from python offline_transformations * fix import * revert pot * update docs * apply review comments * add const * make bin path optional * Add docs * add compare test --- docs/snippets/ov_preprocessing.cpp | 2 +- docs/snippets/ov_preprocessing.py | 8 +-- samples/cpp/benchmark_app/main.cpp | 4 +- .../offline_transformations/__init__.py | 1 - .../python/src/openvino/runtime/__init__.py | 1 + .../python/src/pyopenvino/core/common.cpp | 16 +++++ .../python/src/pyopenvino/core/common.hpp | 3 + .../core/offline_transformations.cpp | 72 ------------------- .../python/src/pyopenvino/pyopenvino.cpp | 55 ++++++++++++++ .../test_transformations/test_offline_api.py | 13 +++- src/core/include/openvino/core/graph_util.hpp | 13 ++++ src/core/src/graph_util.cpp | 9 +++ .../tests/pass/serialization/serialize.cpp | 34 ++++++--- .../openvino/tools/benchmark/utils/utils.py | 9 +-- .../tools/mo/back/offline_transformations.py | 3 +- .../tools/mo/moc_frontend/serialize.py | 3 +- .../tools/mo/utils/check_ie_bindings.py | 4 +- .../openvino/tools/pot/graph/graph_utils.py | 1 + 18 files changed, 145 insertions(+), 106 deletions(-) diff --git a/docs/snippets/ov_preprocessing.cpp b/docs/snippets/ov_preprocessing.cpp index 5504deeca07..697a5ea8a70 100644 --- a/docs/snippets/ov_preprocessing.cpp +++ b/docs/snippets/ov_preprocessing.cpp @@ -199,7 +199,7 @@ void save_example() { // ======== Step 3: Save the model ================ std::string xml = "/path/to/some_model_saved.xml"; std::string bin = "/path/to/some_model_saved.bin"; - ov::pass::Serialize(xml, bin).run_on_model(model); + ov::serialize(model, xml, bin); //! [ov:preprocess:save] } diff --git a/docs/snippets/ov_preprocessing.py b/docs/snippets/ov_preprocessing.py index 3cf594c882d..fd70b38f9df 100644 --- a/docs/snippets/ov_preprocessing.py +++ b/docs/snippets/ov_preprocessing.py @@ -3,7 +3,7 @@ # from openvino.preprocess import ResizeAlgorithm, ColorFormat -from openvino.runtime import Layout, Type +from openvino.runtime import Layout, Type, serialize xml_path = '' @@ -210,11 +210,7 @@ model = ppp.build() set_batch(model, 2) # ======== Step 3: Save the model ================ -pass_manager = Manager() -pass_manager.register_pass(pass_name="Serialize", - xml_path='/path/to/some_model_saved.xml', - bin_path='/path/to/some_model_saved.bin') -pass_manager.run_passes(model) +serialize(model, '/path/to/some_model_saved.xml', '/path/to/some_model_saved.bin') # ! [ov:preprocess:save] # ! [ov:preprocess:save_load] diff --git a/samples/cpp/benchmark_app/main.cpp b/samples/cpp/benchmark_app/main.cpp index 70fe3119868..057296c2a45 100644 --- a/samples/cpp/benchmark_app/main.cpp +++ b/samples/cpp/benchmark_app/main.cpp @@ -1103,9 +1103,7 @@ int main(int argc, char* argv[]) { if (!FLAGS_exec_graph_path.empty()) { try { - std::string fileName = fileNameNoExt(FLAGS_exec_graph_path); - ov::pass::Serialize serializer(fileName + ".xml", fileName + ".bin"); - serializer.run_on_model(std::const_pointer_cast(compiledModel.get_runtime_model())); + ov::serialize(compiledModel.get_runtime_model(), FLAGS_exec_graph_path); slog::info << "executable graph is stored to " << FLAGS_exec_graph_path << slog::endl; } catch (const std::exception& ex) { slog::err << "Can't get executable graph: " << ex.what() << slog::endl; diff --git a/src/bindings/python/src/openvino/offline_transformations/__init__.py b/src/bindings/python/src/openvino/offline_transformations/__init__.py index 5b05fed8bfe..5c8be1be82d 100644 --- a/src/bindings/python/src/openvino/offline_transformations/__init__.py +++ b/src/bindings/python/src/openvino/offline_transformations/__init__.py @@ -15,6 +15,5 @@ from openvino.pyopenvino.offline_transformations import apply_low_latency_transf from openvino.pyopenvino.offline_transformations import apply_pruning_transformation from openvino.pyopenvino.offline_transformations import generate_mapping_file from openvino.pyopenvino.offline_transformations import apply_make_stateful_transformation -from openvino.pyopenvino.offline_transformations import serialize from openvino.pyopenvino.offline_transformations import compress_model_transformation from openvino.pyopenvino.offline_transformations import compress_quantize_weights_transformation diff --git a/src/bindings/python/src/openvino/runtime/__init__.py b/src/bindings/python/src/openvino/runtime/__init__.py index 2628fb17b2a..f40efa258d2 100644 --- a/src/bindings/python/src/openvino/runtime/__init__.py +++ b/src/bindings/python/src/openvino/runtime/__init__.py @@ -47,6 +47,7 @@ from openvino.pyopenvino import ProfilingInfo from openvino.pyopenvino import get_version from openvino.pyopenvino import get_batch from openvino.pyopenvino import set_batch +from openvino.pyopenvino import serialize # Import opsets from openvino.runtime import opset1 diff --git a/src/bindings/python/src/pyopenvino/core/common.cpp b/src/bindings/python/src/pyopenvino/core/common.cpp index 3ff7a32d465..997dcb22927 100644 --- a/src/bindings/python/src/pyopenvino/core/common.cpp +++ b/src/bindings/python/src/pyopenvino/core/common.cpp @@ -505,4 +505,20 @@ py::dict outputs_to_dict(const std::vector>& outputs, return res; } +ov::pass::Serialize::Version convert_to_version(const std::string& version) { + using Version = ov::pass::Serialize::Version; + + if (version == "UNSPECIFIED") { + return Version::UNSPECIFIED; + } + if (version == "IR_V10") { + return Version::IR_V10; + } + if (version == "IR_V11") { + return Version::IR_V11; + } + throw ov::Exception("Invoked with wrong version argument: '" + version + + "'! The supported versions are: 'UNSPECIFIED'(default), 'IR_V10', 'IR_V11'."); +} + }; // namespace Common diff --git a/src/bindings/python/src/pyopenvino/core/common.hpp b/src/bindings/python/src/pyopenvino/core/common.hpp index 8a3199643b2..f3f9ad0be03 100644 --- a/src/bindings/python/src/pyopenvino/core/common.hpp +++ b/src/bindings/python/src/pyopenvino/core/common.hpp @@ -19,6 +19,7 @@ #include "openvino/runtime/infer_request.hpp" #include "openvino/runtime/tensor.hpp" #include "openvino/runtime/properties.hpp" +#include "openvino/pass/serialize.hpp" #include "pyopenvino/core/containers.hpp" #include "pyopenvino/graph/any.hpp" @@ -55,6 +56,8 @@ uint32_t get_optimal_number_of_requests(const ov::CompiledModel& actual); py::dict outputs_to_dict(const std::vector>& outputs, ov::InferRequest& request); +ov::pass::Serialize::Version convert_to_version(const std::string& version); + // Use only with classes that are not creatable by users on Python's side, because // Objects created in Python that are wrapped with such wrapper will cause memory leaks. template diff --git a/src/bindings/python/src/pyopenvino/core/offline_transformations.cpp b/src/bindings/python/src/pyopenvino/core/offline_transformations.cpp index 3495033cf26..b86221f9f17 100644 --- a/src/bindings/python/src/pyopenvino/core/offline_transformations.cpp +++ b/src/bindings/python/src/pyopenvino/core/offline_transformations.cpp @@ -21,19 +21,6 @@ #include "openvino/pass/low_latency.hpp" #include "openvino/pass/manager.hpp" -using Version = ov::pass::Serialize::Version; - -inline Version convert_to_version(const std::string& version) { - if (version == "UNSPECIFIED") - return Version::UNSPECIFIED; - if (version == "IR_V10") - return Version::IR_V10; - if (version == "IR_V11") - return Version::IR_V11; - throw ov::Exception("Invoked with wrong version argument: '" + version + - "'! The supported versions are: 'UNSPECIFIED'(default), 'IR_V10', 'IR_V11'."); -} - namespace py = pybind11; void regmodule_offline_transformations(py::module m) { @@ -129,63 +116,4 @@ void regmodule_offline_transformations(py::module m) { manager.run_passes(model); }, py::arg("model")); - - // todo: remove as serialize as part of passManager api will be merged - m_offline_transformations.def( - "serialize", - [](std::shared_ptr model, - const std::string& path_to_xml, - const std::string& path_to_bin, - const std::string& version) { - ov::pass::Manager manager; - manager.register_pass(path_to_xml, path_to_bin, convert_to_version(version)); - manager.run_passes(model); - }, - py::arg("model"), - py::arg("model_path"), - py::arg("weights_path"), - py::arg("version") = "UNSPECIFIED", - R"( - Serialize given model into IR. The generated .xml and .bin files will be saved - into provided paths. - - :param model: model which will be converted to IR representation - :type model: openvino.runtime.Model - :param xml_path: path where .xml file will be saved - :type xml_path: str - :param bin_path: path where .bin file will be saved - :type bin_path: str - :param version: sets the version of the IR which will be generated. - Supported versions are: - - "UNSPECIFIED" (default) : Use the latest or model version - - "IR_V10" : v10 IR - - "IR_V11" : v11 IR - - :Examples: - - 1. Default IR version: - - .. code-block:: python - - shape = [2, 2] - parameter_a = ov.parameter(shape, dtype=np.float32, name="A") - parameter_b = ov.parameter(shape, dtype=np.float32, name="B") - parameter_c = ov.parameter(shape, dtype=np.float32, name="C") - model = (parameter_a + parameter_b) * parameter_c - func = Model(model, [parameter_a, parameter_b, parameter_c], "Model") - # IR generated with default version - serialize(func, model_path="./serialized.xml", weights_path="./serialized.bin") - - 2. IR version 11: - - .. code-block:: python - - parameter_a = ov.parameter(shape, dtype=np.float32, name="A") - parameter_b = ov.parameter(shape, dtype=np.float32, name="B") - parameter_c = ov.parameter(shape, dtype=np.float32, name="C") - model = (parameter_a + parameter_b) * parameter_c - func = Model(model, [parameter_a, parameter_b, parameter_c], "Model") - # IR generated with default version - serialize(func, model_path="./serialized.xml", "./serialized.bin", version="IR_V11") - )"); } diff --git a/src/bindings/python/src/pyopenvino/pyopenvino.cpp b/src/bindings/python/src/pyopenvino/pyopenvino.cpp index 53ac0003301..c38cab64ffe 100644 --- a/src/bindings/python/src/pyopenvino/pyopenvino.cpp +++ b/src/bindings/python/src/pyopenvino/pyopenvino.cpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -95,6 +96,60 @@ PYBIND11_MODULE(pyopenvino, m) { py::arg("model"), py::arg("batch_size") = -1); + m.def( + "serialize", + [](std::shared_ptr& model, + const std::string& xml_path, + const std::string& bin_path, + const std::string& version) { + ov::serialize(model, xml_path, bin_path, Common::convert_to_version(version)); + }, + py::arg("model"), + py::arg("xml_path"), + py::arg("bin_path") = "", + py::arg("version") = "UNSPECIFIED", + R"( + Serialize given model into IR. The generated .xml and .bin files will be saved + into provided paths. + :param model: model which will be converted to IR representation + :type model: openvino.runtime.Model + :param xml_path: path where .xml file will be saved + :type xml_path: str + :param bin_path: path where .bin file will be saved (optional), + the same name as for xml_path will be used by default. + :type bin_path: str + :param version: version of the generated IR (optional). + Supported versions are: + - "UNSPECIFIED" (default) : Use the latest or model version + - "IR_V10" : v10 IR + - "IR_V11" : v11 IR + + :Examples: + + 1. Default IR version: + + .. code-block:: python + + shape = [2, 2] + parameter_a = ov.parameter(shape, dtype=np.float32, name="A") + parameter_b = ov.parameter(shape, dtype=np.float32, name="B") + parameter_c = ov.parameter(shape, dtype=np.float32, name="C") + op = (parameter_a + parameter_b) * parameter_c + model = Model(op, [parameter_a, parameter_b, parameter_c], "Model") + # IR generated with default version + serialize(model, xml_path="./serialized.xml", bin_path="./serialized.bin") + 2. IR version 11: + + .. code-block:: python + parameter_a = ov.parameter(shape, dtype=np.float32, name="A") + parameter_b = ov.parameter(shape, dtype=np.float32, name="B") + parameter_c = ov.parameter(shape, dtype=np.float32, name="C") + op = (parameter_a + parameter_b) * parameter_c + model = Model(ops, [parameter_a, parameter_b, parameter_c], "Model") + # IR generated with default version + serialize(model, xml_path="./serialized.xml", bin_path="./serialized.bin", version="IR_V11") + )"); + regclass_graph_PyRTMap(m); regmodule_graph_types(m); regclass_graph_Dimension(m); // Dimension must be registered before PartialShape diff --git a/src/bindings/python/tests/test_transformations/test_offline_api.py b/src/bindings/python/tests/test_transformations/test_offline_api.py index 758f2491768..195d8c597bf 100644 --- a/src/bindings/python/tests/test_transformations/test_offline_api.py +++ b/src/bindings/python/tests/test_transformations/test_offline_api.py @@ -3,9 +3,10 @@ import os import numpy as np +from openvino.runtime import serialize from openvino.offline_transformations import apply_moc_transformations, apply_pot_transformations, \ apply_low_latency_transformation, apply_pruning_transformation, apply_make_stateful_transformation, \ - compress_model_transformation, serialize + compress_model_transformation from openvino.runtime import Model, PartialShape, Core import openvino.runtime as ov @@ -140,6 +141,16 @@ def test_Version_default(): os.remove(bin_path) +def test_serialize_default_bin(): + xml_path = "./serialized_function.xml" + bin_path = "./serialized_function.bin" + model = get_test_function() + serialize(model, xml_path) + assert os.path.exists(bin_path) + os.remove(xml_path) + os.remove(bin_path) + + def test_Version_ir_v10(): core = Core() xml_path = "./serialized_function.xml" diff --git a/src/core/include/openvino/core/graph_util.hpp b/src/core/include/openvino/core/graph_util.hpp index edb88b265c6..bed0bbc25f3 100644 --- a/src/core/include/openvino/core/graph_util.hpp +++ b/src/core/include/openvino/core/graph_util.hpp @@ -18,6 +18,7 @@ #include "openvino/core/model.hpp" #include "openvino/core/node.hpp" #include "openvino/op/parameter.hpp" +#include "openvino/pass/serialize.hpp" namespace ov { @@ -278,4 +279,16 @@ bool replace_output_update_name(Output node, const Output& node_inpu OPENVINO_API bool replace_node_update_name(const std::shared_ptr& target, const std::shared_ptr& replacement); + +/// \brief Serialize given model into IR. The generated .xml and .bin files will be saved into provided paths. +/// \param m Model which will be converted to IR representation. +/// \param xml_path Path where .xml file will be saved. +/// \param bin_path Path where .bin file will be saved (optional). +/// The same name as for xml_path will be used by default. +/// \param version Version of the generated IR (optional). +OPENVINO_API +void serialize(const std::shared_ptr& m, + const std::string& xml_path, + const std::string& bin_path = "", + ov::pass::Serialize::Version version = ov::pass::Serialize::Version::UNSPECIFIED); } // namespace ov diff --git a/src/core/src/graph_util.cpp b/src/core/src/graph_util.cpp index 5f1101b076e..f0649f101f9 100644 --- a/src/core/src/graph_util.cpp +++ b/src/core/src/graph_util.cpp @@ -808,3 +808,12 @@ bool ov::replace_node_update_name(const std::shared_ptr& target, const std copy_runtime_info(target, replacement); return true; } + +void ov::serialize(const std::shared_ptr& m, + const std::string& xml_path, + const std::string& bin_path, + ov::pass::Serialize::Version version) { + ov::pass::Manager manager; + manager.register_pass(xml_path, bin_path, version); + manager.run_passes(std::const_pointer_cast(m)); +} diff --git a/src/core/tests/pass/serialization/serialize.cpp b/src/core/tests/pass/serialization/serialize.cpp index dc632a00284..ad9400ee958 100644 --- a/src/core/tests/pass/serialization/serialize.cpp +++ b/src/core/tests/pass/serialization/serialize.cpp @@ -24,6 +24,20 @@ public: std::string m_out_xml_path; std::string m_out_bin_path; + void CompareSerialized(std::function&)> serializer) { + auto expected = ov::test::readModel(m_model_path, m_binary_path); + auto orig = ov::clone_model(*expected); + serializer(expected); + auto result = ov::test::readModel(m_out_xml_path, m_out_bin_path); + const auto fc = FunctionsComparator::with_default() + .enable(FunctionsComparator::ATTRIBUTES) + .enable(FunctionsComparator::CONST_VALUES); + const auto res = fc.compare(result, expected); + const auto res2 = fc.compare(expected, orig); + EXPECT_TRUE(res.valid) << res.message; + EXPECT_TRUE(res2.valid) << res2.message; + } + void SetUp() override { m_model_path = ov::util::path_join({SERIALIZED_ZOO, "ir/", std::get<0>(GetParam())}); if (!std::get<1>(GetParam()).empty()) { @@ -42,17 +56,15 @@ public: }; TEST_P(SerializationTest, CompareFunctions) { - auto expected = ov::test::readModel(m_model_path, m_binary_path); - auto orig = ov::clone_model(*expected); - ov::pass::Serialize(m_out_xml_path, m_out_bin_path).run_on_model(expected); - auto result = ov::test::readModel(m_out_xml_path, m_out_bin_path); - const auto fc = FunctionsComparator::with_default() - .enable(FunctionsComparator::ATTRIBUTES) - .enable(FunctionsComparator::CONST_VALUES); - const auto res = fc.compare(result, expected); - const auto res2 = fc.compare(expected, orig); - EXPECT_TRUE(res.valid) << res.message; - EXPECT_TRUE(res2.valid) << res2.message; + CompareSerialized([this](const std::shared_ptr& m) { + ov::pass::Serialize(m_out_xml_path, m_out_bin_path).run_on_model(m); + }); +} + +TEST_P(SerializationTest, SerializeHelper) { + CompareSerialized([this](const std::shared_ptr& m) { + ov::serialize(m, m_out_xml_path, m_out_bin_path); + }); } INSTANTIATE_TEST_SUITE_P( diff --git a/tools/benchmark_tool/openvino/tools/benchmark/utils/utils.py b/tools/benchmark_tool/openvino/tools/benchmark/utils/utils.py index d72ed3a561f..dbf528f8ac4 100644 --- a/tools/benchmark_tool/openvino/tools/benchmark/utils/utils.py +++ b/tools/benchmark_tool/openvino/tools/benchmark/utils/utils.py @@ -3,9 +3,8 @@ from collections import defaultdict import datetime -from openvino.runtime import Core, Model, PartialShape, Dimension, Layout, Type +from openvino.runtime import Core, Model, PartialShape, Dimension, Layout, Type, serialize from openvino.preprocess import PrePostProcessor -from openvino.runtime.passes import Manager from .constants import DEVICE_DURATION_IN_SECS, UNKNOWN_DEVICE_TYPE, \ CPU_DEVICE_NAME, GPU_DEVICE_NAME @@ -308,11 +307,7 @@ def process_help_inference_string(benchmark_app, device_number_streams): def dump_exec_graph(compiled_model, model_path): - weight_path = model_path[:model_path.find(".xml")] + ".bin" - pass_manager = Manager() - pass_manager.register_pass("Serialize", model_path, weight_path) - pass_manager.run_passes(compiled_model.get_runtime_model()) - + serialize(compiled_model.get_runtime_model(), model_path) def print_perf_counters(perf_counts_list): diff --git a/tools/mo/openvino/tools/mo/back/offline_transformations.py b/tools/mo/openvino/tools/mo/back/offline_transformations.py index b2d18e55950..25a5d2c0574 100644 --- a/tools/mo/openvino/tools/mo/back/offline_transformations.py +++ b/tools/mo/openvino/tools/mo/back/offline_transformations.py @@ -54,7 +54,8 @@ def apply_offline_transformations(input_model: str, argv: argparse.Namespace): # to produce correct mapping extract_names = argv.framework in ['tf', 'mxnet', 'kaldi'] - from openvino.offline_transformations import generate_mapping_file, serialize # pylint: disable=import-error,no-name-in-module + from openvino.runtime import serialize # pylint: disable=import-error,no-name-in-module + from openvino.offline_transformations import generate_mapping_file # pylint: disable=import-error,no-name-in-module from openvino.frontend import FrontEndManager # pylint: disable=no-name-in-module,import-error from openvino.tools.mo.back.preprocessing import apply_preprocessing # pylint: disable=no-name-in-module,import-error diff --git a/tools/mo/openvino/tools/mo/moc_frontend/serialize.py b/tools/mo/openvino/tools/mo/moc_frontend/serialize.py index 488c0b9120f..40b3fba0176 100644 --- a/tools/mo/openvino/tools/mo/moc_frontend/serialize.py +++ b/tools/mo/openvino/tools/mo/moc_frontend/serialize.py @@ -40,7 +40,8 @@ def moc_emit_ir(ngraph_function: Model, argv: argparse.Namespace): orig_model_name = os.path.normpath(os.path.join(output_dir, argv.model_name)) - from openvino.offline_transformations import serialize, generate_mapping_file # pylint: disable=import-error,no-name-in-module + from openvino.runtime import serialize # pylint: disable=import-error,no-name-in-module + from openvino.offline_transformations import generate_mapping_file # pylint: disable=import-error,no-name-in-module serialize(ngraph_function, (orig_model_name + ".xml").encode('utf-8'), (orig_model_name + ".bin").encode('utf-8')) del argv.feManager diff --git a/tools/mo/openvino/tools/mo/utils/check_ie_bindings.py b/tools/mo/openvino/tools/mo/utils/check_ie_bindings.py index 22d3ac39c40..8701bc88f34 100644 --- a/tools/mo/openvino/tools/mo/utils/check_ie_bindings.py +++ b/tools/mo/openvino/tools/mo/utils/check_ie_bindings.py @@ -51,9 +51,9 @@ def import_core_modules(silent: bool, path_to_module: str): from openvino.offline_transformations import apply_moc_transformations, apply_moc_legacy_transformations,\ apply_low_latency_transformation # pylint: disable=import-error,no-name-in-module from openvino.offline_transformations import apply_make_stateful_transformation, generate_mapping_file # pylint: disable=import-error,no-name-in-module - from openvino.offline_transformations import generate_mapping_file, apply_make_stateful_transformation, serialize # pylint: disable=import-error,no-name-in-module + from openvino.offline_transformations import generate_mapping_file, apply_make_stateful_transformation # pylint: disable=import-error,no-name-in-module - from openvino.runtime import Model, get_version # pylint: disable=import-error,no-name-in-module + from openvino.runtime import Model, serialize, get_version # pylint: disable=import-error,no-name-in-module from openvino.runtime.op import Parameter # pylint: disable=import-error,no-name-in-module from openvino.runtime import PartialShape, Dimension # pylint: disable=import-error,no-name-in-module from openvino.frontend import FrontEndManager, FrontEnd # pylint: disable=no-name-in-module,import-error diff --git a/tools/pot/openvino/tools/pot/graph/graph_utils.py b/tools/pot/openvino/tools/pot/graph/graph_utils.py index 65157013a5a..1d570a5bec2 100644 --- a/tools/pot/openvino/tools/pot/graph/graph_utils.py +++ b/tools/pot/openvino/tools/pot/graph/graph_utils.py @@ -34,6 +34,7 @@ def load_graph(model_config, target_device='ANY'): apply_pot_transformations(model, target_device.encode('utf-8')) bin_path = serialized_bin_path xml_path = serialized_xml_path + # TODO: replace by openvino.runtime.serialize pass_manager.register_pass(pass_name="Serialize", xml_path=xml_path, bin_path=bin_path) pass_manager.run_passes(model)