From 73010788725f8f07efb6df20711ece712ee213ea Mon Sep 17 00:00:00 2001 From: alexander Date: Mon, 18 Oct 2021 19:17:24 +0100 Subject: MLECO-2488: added model optimization options and updated OptimizerOptions constructor. Signed-off-by: alexander Change-Id: Ic2ad6a46c3830f2526ba8b20ca0db0780be4b9a2 --- python/pyarmnn/src/pyarmnn/__init__.py | 4 +- python/pyarmnn/src/pyarmnn/swig/armnn.i | 1 + .../src/pyarmnn/swig/modules/armnn_backend_opt.i | 103 ++++++++++++++ .../src/pyarmnn/swig/modules/armnn_network.i | 24 +++- .../src/pyarmnn/swig/typemaps/model_options.i | 55 ++++++++ python/pyarmnn/test/test_modeloption.py | 149 +++++++++++++++++++++ python/pyarmnn/test/test_network.py | 11 +- python/pyarmnn/test/test_onnx_parser.py | 1 - python/pyarmnn/test/test_runtime.py | 6 + python/pyarmnn/test/test_supported_backends.py | 2 - python/pyarmnn/test/test_tflite_parser.py | 1 + 11 files changed, 350 insertions(+), 7 deletions(-) create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i create mode 100644 python/pyarmnn/test/test_modeloption.py diff --git a/python/pyarmnn/src/pyarmnn/__init__.py b/python/pyarmnn/src/pyarmnn/__init__.py index 13fdf95c6f..44992522c0 100644 --- a/python/pyarmnn/src/pyarmnn/__init__.py +++ b/python/pyarmnn/src/pyarmnn/__init__.py @@ -53,6 +53,7 @@ from ._generated.pyarmnn import Optimize, OptimizerOptions, IOptimizedNetwork, I # Backend from ._generated.pyarmnn import BackendId from ._generated.pyarmnn import IDeviceSpec +from ._generated.pyarmnn import BackendOptions, BackendOption # Tensors from ._generated.pyarmnn import TensorInfo, TensorShape @@ -65,7 +66,8 @@ from ._generated.pyarmnn import IProfiler # Types from ._generated.pyarmnn import DataType_Float16, DataType_Float32, DataType_QAsymmU8, DataType_Signed32, \ - DataType_Boolean, DataType_QSymmS16, DataType_QSymmS8, DataType_QAsymmS8 + DataType_Boolean, DataType_QSymmS16, DataType_QSymmS8, DataType_QAsymmS8, ShapeInferenceMethod_ValidateOnly, \ + ShapeInferenceMethod_InferAndValidate from ._generated.pyarmnn import DataLayout_NCHW, DataLayout_NHWC from ._generated.pyarmnn import MemorySource_Malloc, MemorySource_Undefined, MemorySource_DmaBuf, \ MemorySource_DmaBufProtected diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn.i b/python/pyarmnn/src/pyarmnn/swig/armnn.i index 172d2d80cf..c525e1767e 100644 --- a/python/pyarmnn/src/pyarmnn/swig/armnn.i +++ b/python/pyarmnn/src/pyarmnn/swig/armnn.i @@ -14,6 +14,7 @@ //armnn api submodules %include "modules/armnn_backend.i" +%include "modules/armnn_backend_opt.i" %include "modules/armnn_types.i" %include "modules/armnn_descriptors.i" %include "modules/armnn_lstmparam.i" diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i new file mode 100644 index 0000000000..2ff54aaaa1 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i @@ -0,0 +1,103 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/BackendId.hpp" +#include "armnn/BackendOptions.hpp" +%} + +#pragma SWIG nowarn=SWIGWARN_PARSE_NESTED_CLASS + +%{ + typedef armnn::BackendOptions::BackendOption BackendOption; +%} + +%feature("docstring", +" +Struct for the users to pass backend specific option. +") BackendOption; +%nodefaultctor BackendOption; +struct BackendOption +{ + BackendOption(std::string name, bool value); + BackendOption(std::string name, int value); + BackendOption(std::string name, unsigned int value); + BackendOption(std::string name, float value); + BackendOption(std::string name, std::string value); + + std::string GetName(); +}; + +namespace armnn +{ +%feature("docstring", +" +Struct for backend specific options, see `BackendOption`. +Options are assigned to a specific backend by providing a backend id. + +") BackendOptions; +%nodefaultctor BackendOptions; +struct BackendOptions +{ + BackendOptions(BackendId backend); + + BackendOptions(const BackendOptions& other); + + %feature("docstring", + " + Add backend option. + + Args: + option (`BackendOption`): backend option + ") AddOption; + void AddOption(const BackendOption& option); + + %feature("docstring", + " + Get a backend id. + + Returns: + BackendId: assigned backend id. + ") GetBackendId; + const BackendId& GetBackendId(); + + %feature("docstring", + " + Get backend options count. + + Returns: + int: number of options for a backend. + ") GetOptionCount; + size_t GetOptionCount(); + + %feature("docstring", + " + Get backend option by index. + + Args: + idx (int): backend option index + + Returns: + BackendOption: backend option. + ") GetOption; + const BackendOption& GetOption(size_t idx); + + %pythoncode %{ + def __iter__(self): + for count in range(self.GetOptionCount()): + yield self[count] + %} +}; + +%extend BackendOptions { + + const BackendOption& __getitem__(size_t i) const { + return $self->GetOption(i); + } + + size_t __len__() const { + return $self->GetOptionCount(); + } +} +} diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i index f4581ca5ec..d50b841f4a 100644 --- a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i @@ -11,6 +11,11 @@ %} %include +%include + +namespace std { + %template() std::vector; +} namespace armnn { @@ -24,9 +29,19 @@ Contains: that can not be reduced will be left in Fp32. m_ReduceFp32ToFp16 (bool): Reduces Fp32 network to Fp16 for faster processing. Layers that can not be reduced will be left in Fp32. - m_ImportEnabled (bool): Enable memory import. + m_ImportEnabled (bool): Enable memory import. + m_shapeInferenceMethod: The ShapeInferenceMethod modifies how the output shapes are treated. + When ValidateOnly is selected, the output shapes are inferred from the input parameters + of the layer and any mismatch is reported. + When InferAndValidate is selected 2 actions are performed: (1)infer output shape from + inputs and (2)validate the shapes as in ValidateOnly. This option has been added to work + with tensors which rank or dimension sizes are not specified explicitly, however this + information can be calculated from the inputs. + m_ModelOptions: List of backends optimisation options. ") OptimizerOptions; + +%model_options_typemap; struct OptimizerOptions { OptimizerOptions(); @@ -34,13 +49,18 @@ struct OptimizerOptions OptimizerOptions(bool reduceFp32ToFp16, bool debug, bool reduceFp32ToBf16 = false, - bool importEnabled = false); + ShapeInferenceMethod shapeInferenceMethod = armnn::ShapeInferenceMethod::ValidateOnly, + bool importEnabled = false, + std::vector modelOptions = {}); bool m_ReduceFp32ToBf16; bool m_ReduceFp32ToFp16; bool m_Debug; + ShapeInferenceMethod m_shapeInferenceMethod; bool m_ImportEnabled; + std::vector m_ModelOptions; }; +%model_options_clear; %feature("docstring", " diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i new file mode 100644 index 0000000000..c4acaefb1b --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i @@ -0,0 +1,55 @@ +// +// Copyright © 2021 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%inline %{ + + static PyObject* from_model_options_to_python(std::vector* input) { + Py_ssize_t size = input->size(); + PyObject* localList = PyList_New(size); + + if (!localList) { + Py_XDECREF(localList); + return PyErr_NoMemory(); + } + + for(Py_ssize_t i = 0; i < size; ++i) { + + PyObject* obj = SWIG_NewPointerObj(SWIG_as_voidptr(&input->at(i)), SWIGTYPE_p_armnn__BackendOptions, 0 | 0 ); + + PyList_SET_ITEM(localList, i, obj); + } + return localList; + } +%} + +%define %model_options_typemap + +// this typemap works for struct argument get + + %typemap(out) std::vector* { + $result = from_model_options_to_python($1); + } + +// this typemap works for struct argument set + %typemap(in) std::vector* { + if (PySequence_Check($input)) { + + int res = swig::asptr($input, &$1); + if (!SWIG_IsOK(res) || !$1) { + SWIG_exception_fail(SWIG_ArgError(($1 ? res : SWIG_TypeError)), + "in method '" "OptimizerOptions_m_ModelOptions_set" "', argument " "2"" of type '" "std::vector< armnn::BackendOptions,std::allocator< armnn::BackendOptions > > *""'"); + } + + } else { + PyErr_SetString(PyExc_TypeError, "Argument value object does not provide sequence protocol."); + SWIG_fail; + } + } + +%enddef + +%define %model_options_clear + %typemap(out) std::vector*; + %typemap(in) std::vector*; +%enddef diff --git a/python/pyarmnn/test/test_modeloption.py b/python/pyarmnn/test/test_modeloption.py new file mode 100644 index 0000000000..c03d4a8cce --- /dev/null +++ b/python/pyarmnn/test/test_modeloption.py @@ -0,0 +1,149 @@ +# Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +# SPDX-License-Identifier: MIT +import pytest + +from pyarmnn import BackendOptions, BackendOption, BackendId, OptimizerOptions, ShapeInferenceMethod_InferAndValidate + + +@pytest.mark.parametrize("data", (True, -100, 128, 0.12345, 'string')) +def test_backend_option_ctor(data): + bo = BackendOption("name", data) + assert "name" == bo.GetName() + + +def test_backend_options_ctor(): + backend_id = BackendId('a') + bos = BackendOptions(backend_id) + + assert 'a' == str(bos.GetBackendId()) + + another_bos = BackendOptions(bos) + assert 'a' == str(another_bos.GetBackendId()) + + +def test_backend_options_add(): + backend_id = BackendId('a') + bos = BackendOptions(backend_id) + bo = BackendOption("name", 1) + bos.AddOption(bo) + + assert 1 == bos.GetOptionCount() + assert 1 == len(bos) + + assert 'name' == bos[0].GetName() + assert 'name' == bos.GetOption(0).GetName() + for option in bos: + assert 'name' == option.GetName() + + bos.AddOption(BackendOption("name2", 2)) + + assert 2 == bos.GetOptionCount() + assert 2 == len(bos) + + +def test_backend_option_ownership(): + backend_id = BackendId('b') + bos = BackendOptions(backend_id) + bo = BackendOption('option', True) + bos.AddOption(bo) + + assert bo.thisown + + del bo + + assert 1 == bos.GetOptionCount() + option = bos[0] + assert not option.thisown + assert 'option' == option.GetName() + + del option + + option_again = bos[0] + assert not option_again.thisown + assert 'option' == option_again.GetName() + + +def test_optimizer_options_with_model_opt(): + a = BackendOptions(BackendId('a')) + + oo = OptimizerOptions(True, + False, + False, + ShapeInferenceMethod_InferAndValidate, + True, + [a]) + + mo = oo.m_ModelOptions + + assert 1 == len(mo) + assert 'a' == str(mo[0].GetBackendId()) + + b = BackendOptions(BackendId('b')) + + c = BackendOptions(BackendId('c')) + + oo.m_ModelOptions = (a, b, c) + + mo = oo.m_ModelOptions + + assert 3 == len(oo.m_ModelOptions) + + assert 'a' == str(mo[0].GetBackendId()) + assert 'b' == str(mo[1].GetBackendId()) + assert 'c' == str(mo[2].GetBackendId()) + + +def test_optimizer_option_default(): + oo = OptimizerOptions(True, + False, + False, + ShapeInferenceMethod_InferAndValidate, + True) + + assert 0 == len(oo.m_ModelOptions) + + +def test_optimizer_options_fail(): + a = BackendOptions(BackendId('a')) + + with pytest.raises(TypeError) as err: + OptimizerOptions(True, + False, + False, + ShapeInferenceMethod_InferAndValidate, + True, + a) + + assert "Wrong number or type of arguments" in str(err.value) + + with pytest.raises(RuntimeError) as err: + OptimizerOptions(True, + False, + True, + ShapeInferenceMethod_InferAndValidate, + True, + [a]) + + assert "BFloat16 and Float16 optimization cannot be enabled at the same time" in str(err.value) + + with pytest.raises(TypeError) as err: + oo = OptimizerOptions(True, + False, + False, + ShapeInferenceMethod_InferAndValidate, + True) + + oo.m_ModelOptions = 'nonsense' + + assert "in method 'OptimizerOptions_m_ModelOptions_set', argument 2" in str(err.value) + + with pytest.raises(TypeError) as err: + oo = OptimizerOptions(True, + False, + False, + ShapeInferenceMethod_InferAndValidate, + True) + + oo.m_ModelOptions = ['nonsense', a] + + assert "in method 'OptimizerOptions_m_ModelOptions_set', argument 2" in str(err.value) diff --git a/python/pyarmnn/test/test_network.py b/python/pyarmnn/test/test_network.py index c24b113cdb..9b2dbf7f3a 100644 --- a/python/pyarmnn/test/test_network.py +++ b/python/pyarmnn/test/test_network.py @@ -6,12 +6,15 @@ import stat import pytest import pyarmnn as ann + def test_optimizer_options_default_values(): opt = ann.OptimizerOptions() assert opt.m_ReduceFp32ToFp16 == False assert opt.m_Debug == False assert opt.m_ReduceFp32ToBf16 == False assert opt.m_ImportEnabled == False + assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly + def test_optimizer_options_set_values1(): opt = ann.OptimizerOptions(True, True) @@ -19,6 +22,8 @@ def test_optimizer_options_set_values1(): assert opt.m_Debug == True assert opt.m_ReduceFp32ToBf16 == False assert opt.m_ImportEnabled == False + assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly + def test_optimizer_options_set_values2(): opt = ann.OptimizerOptions(False, False, True) @@ -26,13 +31,17 @@ def test_optimizer_options_set_values2(): assert opt.m_Debug == False assert opt.m_ReduceFp32ToBf16 == True assert opt.m_ImportEnabled == False + assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly + def test_optimizer_options_set_values3(): - opt = ann.OptimizerOptions(False, False, True, True) + opt = ann.OptimizerOptions(False, False, True, ann.ShapeInferenceMethod_InferAndValidate, True) assert opt.m_ReduceFp32ToFp16 == False assert opt.m_Debug == False assert opt.m_ReduceFp32ToBf16 == True assert opt.m_ImportEnabled == True + assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_InferAndValidate + @pytest.fixture(scope="function") def get_runtime(shared_data_folder, network_file): diff --git a/python/pyarmnn/test/test_onnx_parser.py b/python/pyarmnn/test/test_onnx_parser.py index 99353a0959..1424c24cea 100644 --- a/python/pyarmnn/test/test_onnx_parser.py +++ b/python/pyarmnn/test/test_onnx_parser.py @@ -5,7 +5,6 @@ import os import pytest import pyarmnn as ann import numpy as np -from typing import List @pytest.fixture() diff --git a/python/pyarmnn/test/test_runtime.py b/python/pyarmnn/test/test_runtime.py index 295c870370..fbdd8044ce 100644 --- a/python/pyarmnn/test/test_runtime.py +++ b/python/pyarmnn/test/test_runtime.py @@ -97,6 +97,7 @@ def test_python_disowns_network(random_runtime): assert not opt_network.thisown + def test_load_network(random_runtime): preferred_backends = random_runtime[0] network = random_runtime[1] @@ -109,6 +110,7 @@ def test_load_network(random_runtime): assert "" == messages assert net_id == 0 + def test_create_runtime_with_external_profiling_enabled(): options = ann.CreationOptions() @@ -125,6 +127,7 @@ def test_create_runtime_with_external_profiling_enabled(): assert runtime is not None + def test_create_runtime_with_external_profiling_enabled_invalid_options(): options = ann.CreationOptions() @@ -157,6 +160,7 @@ def test_load_network_properties_provided(random_runtime): assert "" == messages assert net_id == 0 + def test_network_properties_constructor(random_runtime): preferred_backends = random_runtime[0] network = random_runtime[1] @@ -178,10 +182,12 @@ def test_network_properties_constructor(random_runtime): assert "" == messages assert net_id == 0 + def test_network_properties_deprecated_constructor(): with pytest.warns(DeprecationWarning): warnings.warn("Deprecated: Use constructor with MemorySource argument instead.", DeprecationWarning) + def test_unload_network_fails_for_invalid_net_id(random_runtime): preferred_backends = random_runtime[0] network = random_runtime[1] diff --git a/python/pyarmnn/test/test_supported_backends.py b/python/pyarmnn/test/test_supported_backends.py index e1ca5ee6df..4ff5eb07a5 100644 --- a/python/pyarmnn/test/test_supported_backends.py +++ b/python/pyarmnn/test/test_supported_backends.py @@ -1,7 +1,5 @@ # Copyright © 2020 Arm Ltd. All rights reserved. # SPDX-License-Identifier: MIT -import os -import platform import pytest import pyarmnn as ann diff --git a/python/pyarmnn/test/test_tflite_parser.py b/python/pyarmnn/test/test_tflite_parser.py index 8735eef3fb..2764f3f30d 100644 --- a/python/pyarmnn/test/test_tflite_parser.py +++ b/python/pyarmnn/test/test_tflite_parser.py @@ -48,6 +48,7 @@ def create_with_opt() : parserOptions.m_InferAndValidate = True return ann.ITfLiteParser(parserOptions) + def test_tflite_parser_with_optional_options_out_of_scope(shared_data_folder): parser = create_with_opt() network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, "mock_model.tflite")) -- cgit v1.2.1