From b130b73f804766881aedabb88d133bdb1f31c7df Mon Sep 17 00:00:00 2001 From: Anastasia Kuporosova Date: Fri, 3 Mar 2023 17:07:34 +0100 Subject: [PATCH] [Docs][PyOV] Add docstrings for transformations + python examples for stateful model (#15978) * [Docs][PyOV] Add docstrings for transformation + python examples for stateful * add snippets + small improvements --- docs/snippets/ov_network_state_intro.py | 172 ++++++++++++++++++ .../graph/passes/transformations.cpp | 36 +++- .../python/tests/test_graph/test_basic.py | 8 +- .../test_public_transformations.py | 52 ++++-- 4 files changed, 242 insertions(+), 26 deletions(-) create mode 100644 docs/snippets/ov_network_state_intro.py diff --git a/docs/snippets/ov_network_state_intro.py b/docs/snippets/ov_network_state_intro.py new file mode 100644 index 00000000000..1ec8c31433d --- /dev/null +++ b/docs/snippets/ov_network_state_intro.py @@ -0,0 +1,172 @@ +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import logging as log +import numpy as np +import sys + +from openvino.runtime import opset10 as ops +from openvino.runtime import Core, Model, PartialShape, Tensor, Type +from openvino.runtime.passes import LowLatency2, MakeStateful, Manager + + +def state_network_example(): + #! [ov:state_network] + input = ops.parameter([1, 1], dtype=np.float32) + read = ops.read_value(input, "variable0") + add = ops.add(read, input) + save = ops.assign(add, "variable0") + result = ops.result(add) + model = Model(results=[result], sinks=[save], parameters=[input]) + #! [ov:state_network] + + +def low_latency_2_example(): + #! [ov:low_latency_2] + # Precondition for Model. + # TensorIterator and Parameter are created in body of TensorIterator with names + tensor_iterator_name = "TI_name" + body_parameter_name = "body_parameter_name" + idx = "0" # this is a first variable in the network + + # The State will be named "TI_name/param_name/variable_0" + state_name = tensor_iterator_name + "//" + body_parameter_name + "//" + "variable_" + idx # todo + + #! [ov:get_ov_model] + core = Core() + ov_model = core.read_model("path_to_the_model") + #! [ov:get_ov_model] + + # reshape input if needed + + #! [ov:reshape_ov_model] + ov_model.reshape({"X": PartialShape([1, 1, 16])}) + #! [ov:reshape_ov_model] + + #! [ov:apply_low_latency_2] + manager = Manager() + manager.register_pass(LowLatency2()) + manager.run_passes(ov_model) + #! [ov:apply_low_latency_2] + hd_specific_model = core.compile_model(ov_model) + # Try to find the Variable by name + infer_request = hd_specific_model.create_infer_request() + states = infer_request.query_state() + for state in states: + name = state.get_name() + if (name == state_name): + # some actions + #! [ov:low_latency_2] + + #! [ov:low_latency_2_use_parameters] + manager.register_pass(LowLatency2(False)) + #! [ov:low_latency_2_use_parameters] + + +def apply_make_stateful_tensor_names(): + #! [ov:make_stateful_tensor_names] + core = Core() + ov_model = core.read_model("path_to_the_model") + tensor_names = {"tensor_name_1": "tensor_name_4", + "tensor_name_3": "tensor_name_6"} + manager = Manager() + manager.register_pass(MakeStateful(tensor_names)) + manager.run_passes(ov_model) + #! [ov:make_stateful_tensor_names] + + +def apply_make_stateful_ov_nodes(): + #! [ov:make_stateful_ov_nodes] + core = Core() + ov_model = core.read_model("path_to_the_model") + # Parameter_1, Result_1, Parameter_3, Result_3 are + # ops.parameter/ops.result in the ov_model + pairs = ["""(Parameter_1, Result_1), (Parameter_3, Result_3)"""] + manager = Manager() + manager.register_pass(MakeStateful(pairs)) + manager.run_passes(ov_model) + #! [ov:make_stateful_ov_nodes] + + +def main(): + #! [ov:state_api_usage] + # 1. Load inference engine + log.info("Loading Inference Engine") + core = Core() + + # 2. Read a model + log.info("Loading network files") + model = core.read_model("path_to_the_model") + + + # 3. Load network to CPU + hw_specific_model = core.compile_model(model, "CPU") + + # 4. Create Infer Request + infer_request = hw_specific_model.create_infer_request() + + # 5. Reset memory states before starting + states = infer_request.query_state() + if (states.size() != 1): + log.error(f"Invalid queried state number. Expected 1, but got {str(states.size())}") + return -1 + + for state in states: + state.reset() + + # 6. Inference + input_data = np.arange(start=1, stop=12, dtype=np.float32) + + # This example demonstrates how to work with OpenVINO State API. + # Input_data: some array with 12 float numbers + + # Part1: read the first four elements of the input_data array sequentially. + # Expected output for the first utterance: + # sum of the previously processed elements [ 1, 3, 6, 10] + + # Part2: reset state value (set to 0) and read the next four elements. + # Expected output for the second utterance: + # sum of the previously processed elements [ 5, 11, 18, 26] + + # Part3: set state value to 5 and read the next four elements. + # Expected output for the third utterance: + # sum of the previously processed elements + 5 [ 14, 24, 35, 47] + target_state = states[0] + + # Part 1 + log.info("Infer the first utterance") + for next_input in range(len(input_data)/3): + infer_request.infer({0 : input_data[next_input]}) + state_buf = target_state.state.data + log.info(state_buf[0]) + + # Part 2 + log.info("\nReset state between utterances...\n") + target_state.reset() + + log.info("Infer the second utterance") + for next_input in range(len(input_data)/3, (len(input_data)/3 * 2)): + infer_request.infer({0 : input_data[next_input]}) + state_buf = target_state.state.data + log.info(state_buf[0]) + + # Part 3 + log.info("\nSet state value between utterances to 5...\n") + v = np.asarray([5], dtype=np.float32) + tensor = Tensor(v, shared_memory=True) + target_state.state = tensor + + log.info("Infer the third utterance") + for next_input in range((input_data.size()/3 * 2), input_data.size()): + infer_request.infer({0 : input_data[next_input]}) + + state_buf = target_state.state.data + log.info(state_buf[0]) + + log.info("Execution successful") + #! [ov:state_api_usage] + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/bindings/python/src/pyopenvino/graph/passes/transformations.cpp b/src/bindings/python/src/pyopenvino/graph/passes/transformations.cpp index 970e36e2c87..73b7dedd042 100644 --- a/src/bindings/python/src/pyopenvino/graph/passes/transformations.cpp +++ b/src/bindings/python/src/pyopenvino/graph/passes/transformations.cpp @@ -98,15 +98,41 @@ void regclass_transformations(py::module m) { py::class_, ov::pass::ModelPass, ov::pass::PassBase> make_stateful(m, "MakeStateful"); make_stateful.doc() = "openvino.runtime.passes.MakeStateful transformation"; - // TODO: update docstrings for c-tors below - make_stateful.def(py::init(), py::arg("pairs_to_replace")); - make_stateful.def(py::init&>()); + make_stateful.def( + py::init(), + py::arg("pairs_to_replace"), + R"( The transformation replaces the provided pairs Parameter and Result with openvino Memory operations ReadValue and Assign. + + :param pairs_to_replace: + :type pairs_to_replace: List[Tuple[op.Parameter, op.Result] + )"); + make_stateful.def(py::init&>(), + py::arg("pairs_to_replace"), + R"( + The transformation replaces the provided pairs Parameter and Result with openvino Memory operations ReadValue and Assign. + + :param pairs_to_replace: a dictionary of names of the provided Parameter and Result operations. + :type pairs_to_replace: Dict[str, str] + )"); py::class_, ov::pass::ModelPass, ov::pass::PassBase> low_latency(m, "LowLatency2"); low_latency.doc() = "openvino.runtime.passes.LowLatency2 transformation"; - // TODO: update docstrings for c-tor below - low_latency.def(py::init(), py::arg("use_const_initializer") = true); + + low_latency.def(py::init(), + py::arg("use_const_initializer") = true, + R"( + Create LowLatency2 pass which is used for changing the structure of the model, + which contains TensorIterator/Loop operations. + The transformation finds all TensorIterator/Loop layers in the network, + processes all back edges that describe a connection between Result and Parameter of the TensorIterator/Loop bodies, + and inserts ReadValue and Assign layers at the input and output corresponding to this back edge. + + :param use_const_initializer: Changes the type of the initializing subgraph for ReadValue operations. + If "true", then the transformation inserts Constant before ReadValue operation. + If "false, then the transformation leaves existed initializing subgraph for ReadValue operation. + :type use_const_initializer: bool + )"); py::class_, diff --git a/src/bindings/python/tests/test_graph/test_basic.py b/src/bindings/python/tests/test_graph/test_basic.py index 2af079ee227..67bb1f1afad 100644 --- a/src/bindings/python/tests/test_graph/test_basic.py +++ b/src/bindings/python/tests/test_graph/test_basic.py @@ -30,7 +30,7 @@ def test_graph_function_api(): assert parameter_a.partial_shape == PartialShape([2, 2]) parameter_a.layout = ov.Layout("NC") assert parameter_a.layout == ov.Layout("NC") - function = Model(model, [parameter_a, parameter_b, parameter_c], "TestFunction") + function = Model(model, [parameter_a, parameter_b, parameter_c], "TestModel") function.get_parameters()[1].set_partial_shape(PartialShape([3, 4, 5])) @@ -56,7 +56,7 @@ def test_graph_function_api(): assert results[0].get_output_partial_shape(0) == PartialShape([2, 2]) results[0].layout = ov.Layout("NC") assert results[0].layout.to_string() == ov.Layout("NC") - assert function.get_friendly_name() == "TestFunction" + assert function.get_friendly_name() == "TestModel" @pytest.mark.parametrize( @@ -521,7 +521,7 @@ def test_sink_function_ctor(): add = ops.add(rv, input_data, name="MemoryAdd") node = ops.assign(add, "var_id_667") res = ops.result(add, "res") - function = Model(results=[res], sinks=[node], parameters=[input_data], name="TestFunction") + function = Model(results=[res], sinks=[node], parameters=[input_data], name="TestModel") ordered_ops = function.get_ordered_ops() op_types = [op.get_type_name() for op in ordered_ops] @@ -534,7 +534,7 @@ def test_sink_function_ctor(): assert (function.get_parameters()[0].get_partial_shape()) == PartialShape([2, 2]) assert len(function.get_parameters()) == 1 assert len(function.get_results()) == 1 - assert function.get_friendly_name() == "TestFunction" + assert function.get_friendly_name() == "TestModel" def test_node_version(): diff --git a/src/bindings/python/tests/test_transformations/test_public_transformations.py b/src/bindings/python/tests/test_transformations/test_public_transformations.py index eedc1c114f6..5b2fc19a64d 100644 --- a/src/bindings/python/tests/test_transformations/test_public_transformations.py +++ b/src/bindings/python/tests/test_transformations/test_public_transformations.py @@ -4,9 +4,9 @@ import os import pytest import numpy as np -import openvino.runtime as ov -from openvino.runtime import Model, PartialShape, Shape, opset8, Core +from openvino.runtime import Model, PartialShape, Shape, Core +from openvino.runtime import opset10 from openvino.runtime.passes import ( Manager, ConstantFolding, @@ -20,16 +20,34 @@ from tests.test_utils.test_utils import create_filename_for_test def get_model(): - param = opset8.parameter(PartialShape([1, 3, 22, 22]), name="parameter") + param = opset10.parameter(PartialShape([1, 3, 22, 22]), name="parameter") param.get_output_tensor(0).set_names({"parameter"}) - relu = opset8.relu(param) - reshape = opset8.reshape(relu, opset8.shape_of(relu), False) - res = opset8.result(reshape, name="result") + relu = opset10.relu(param) + reshape = opset10.reshape(relu, opset10.shape_of(relu), False) + res = opset10.result(reshape, name="result") res.get_output_tensor(0).set_names({"result"}) return Model([res], [param], "test") def test_make_stateful(): + param = opset10.parameter(PartialShape([1, 3, 22, 22]), name="parameter") + param.get_output_tensor(0).set_names({"parameter"}) + relu = opset10.relu(param) + reshape = opset10.reshape(relu, opset10.shape_of(relu), False) + res = opset10.result(reshape, name="result") + res.get_output_tensor(0).set_names({"result"}) + model = Model([res], [param], "test") + + manager = Manager() + manager.register_pass(MakeStateful([(param, res)])) + manager.run_passes(model) + + assert model is not None + assert len(model.get_parameters()) == 0 + assert len(model.get_results()) == 0 + + +def test_make_stateful_with_dict(): model = get_model() manager = Manager() @@ -68,20 +86,20 @@ def test_convert_precision(): def test_low_latency2(): - param_x = opset8.parameter(Shape([32, 40, 10]), np.float32, "X") - param_y = opset8.parameter(Shape([32, 40, 10]), np.float32, "Y") - param_m = opset8.parameter(Shape([32, 2, 10]), np.float32, "M") + param_x = opset10.parameter(Shape([32, 40, 10]), np.float32, "X") + param_y = opset10.parameter(Shape([32, 40, 10]), np.float32, "Y") + param_m = opset10.parameter(Shape([32, 2, 10]), np.float32, "M") - x_i = opset8.parameter(Shape([32, 2, 10]), np.float32, "X_i") - y_i = opset8.parameter(Shape([32, 2, 10]), np.float32, "Y_i") - m_body = opset8.parameter(Shape([32, 2, 10]), np.float32, "M_body") + x_i = opset10.parameter(Shape([32, 2, 10]), np.float32, "X_i") + y_i = opset10.parameter(Shape([32, 2, 10]), np.float32, "Y_i") + m_body = opset10.parameter(Shape([32, 2, 10]), np.float32, "M_body") - add = opset8.add(x_i, y_i) - zo = opset8.multiply(add, m_body) + add = opset10.add(x_i, y_i) + zo = opset10.multiply(add, m_body) body = Model([zo], [x_i, y_i, m_body], "body_function") - ti = opset8.tensor_iterator() + ti = opset10.tensor_iterator() ti.set_body(body) ti.set_sliced_input(x_i, param_x.output(0), 0, 2, 2, 39, 1) ti.set_sliced_input(y_i, param_y.output(0), 0, 2, 2, -1, 1) @@ -90,8 +108,8 @@ def test_low_latency2(): out0 = ti.get_iter_value(zo.output(0), -1) out1 = ti.get_concatenated_slices(zo.output(0), 0, 2, 2, 39, 1) - result0 = opset8.result(out0) - result1 = opset8.result(out1) + result0 = opset10.result(out0) + result1 = opset10.result(out1) model = Model([result0, result1], [param_x, param_y, param_m])