From dc0c6ed9f8b993e63f492f203d7d7080ab4c835c Mon Sep 17 00:00:00 2001 From: Richard Burton Date: Wed, 8 Apr 2020 16:39:05 +0100 Subject: [PATCH] Add PyArmNN to work with ArmNN API of 20.02 * Add Swig rules for generating python wrapper * Add documentation * Add tests and testing data Change-Id: If48eda08931514fa21e72214dfead2835f07237c Signed-off-by: Richard Burton Signed-off-by: Derek Lamberti --- python/pyarmnn/.gitignore | 214 ++++ python/pyarmnn/README.md | 206 ++++ python/pyarmnn/conftest.py | 52 + python/pyarmnn/docs_conf/config.mako | 34 + python/pyarmnn/images/pyarmnn.png | Bin 0 -> 74951 bytes python/pyarmnn/init_devenv.sh | 28 + python/pyarmnn/pylintconfig | 491 +++++++++ python/pyarmnn/scripts/generate_docs.py | 50 + python/pyarmnn/setup.py | 252 +++++ python/pyarmnn/src/pyarmnn/__init__.py | 143 +++ python/pyarmnn/src/pyarmnn/_generated/__init__.py | 2 + .../pyarmnn/src/pyarmnn/_quantization/__init__.py | 4 + .../_quantization/quantize_and_dequantize.py | 74 ++ python/pyarmnn/src/pyarmnn/_tensor/__init__.py | 6 + python/pyarmnn/src/pyarmnn/_tensor/const_tensor.py | 171 +++ python/pyarmnn/src/pyarmnn/_tensor/tensor.py | 126 +++ .../src/pyarmnn/_tensor/workload_tensors.py | 126 +++ python/pyarmnn/src/pyarmnn/_utilities/__init__.py | 4 + .../src/pyarmnn/_utilities/profiling_helper.py | 97 ++ python/pyarmnn/src/pyarmnn/_version.py | 26 + python/pyarmnn/src/pyarmnn/swig/armnn.i | 27 + .../pyarmnn/src/pyarmnn/swig/armnn_caffeparser.i | 103 ++ python/pyarmnn/src/pyarmnn/swig/armnn_onnxparser.i | 96 ++ .../pyarmnn/src/pyarmnn/swig/armnn_tfliteparser.i | 132 +++ python/pyarmnn/src/pyarmnn/swig/armnn_tfparser.i | 102 ++ python/pyarmnn/src/pyarmnn/swig/armnn_version.i | 58 + .../src/pyarmnn/swig/modules/armnn_backend.i | 65 ++ .../src/pyarmnn/swig/modules/armnn_descriptors.i | 1022 +++++++++++++++++ .../src/pyarmnn/swig/modules/armnn_lstmparam.i | 159 +++ .../src/pyarmnn/swig/modules/armnn_network.i | 1158 ++++++++++++++++++++ .../src/pyarmnn/swig/modules/armnn_profiler.i | 82 ++ .../src/pyarmnn/swig/modules/armnn_runtime.i | 254 +++++ .../src/pyarmnn/swig/modules/armnn_tensor.i | 333 ++++++ .../pyarmnn/src/pyarmnn/swig/modules/armnn_types.i | 140 +++ .../src/pyarmnn/swig/modules/armnn_types_utils.i | 28 + python/pyarmnn/src/pyarmnn/swig/standard_header.i | 53 + .../src/pyarmnn/swig/typemaps/network_optimize.i | 41 + .../src/pyarmnn/swig/typemaps/permutation_vector.i | 52 + .../src/pyarmnn/swig/typemaps/tensor_memory.i | 52 + .../src/pyarmnn/swig/typemaps/tensor_shape.i | 51 + python/pyarmnn/src/pyarmnn/swig/typemaps/vectors.i | 235 ++++ python/pyarmnn/swig_generate.py | 64 ++ python/pyarmnn/test/test_caffe_parser.py | 131 +++ python/pyarmnn/test/test_const_tensor.py | 251 +++++ python/pyarmnn/test/test_descriptors.py | 535 +++++++++ python/pyarmnn/test/test_generated.py | 52 + python/pyarmnn/test/test_iconnectable.py | 142 +++ python/pyarmnn/test/test_network.py | 288 +++++ python/pyarmnn/test/test_onnx_parser.py | 111 ++ python/pyarmnn/test/test_profiling_utilities.py | 68 ++ .../pyarmnn/test/test_quantize_and_dequantize.py | 91 ++ python/pyarmnn/test/test_runtime.py | 257 +++++ python/pyarmnn/test/test_setup.py | 100 ++ python/pyarmnn/test/test_supported_backends.py | 50 + python/pyarmnn/test/test_tensor.py | 144 +++ python/pyarmnn/test/test_tensor_conversion.py | 99 ++ python/pyarmnn/test/test_tensor_info.py | 27 + python/pyarmnn/test/test_tensor_shape.py | 78 ++ python/pyarmnn/test/test_tf_parser.py | 133 +++ python/pyarmnn/test/test_tflite_parser.py | 147 +++ python/pyarmnn/test/test_types.py | 29 + python/pyarmnn/test/test_version.py | 35 + .../shared/caffe_parser/golden_output_caffe.npy | Bin 0 -> 168 bytes .../testdata/shared/caffe_parser/input_caffe.npy | Bin 0 -> 3264 bytes python/pyarmnn/test/testdata/shared/license.txt | 10 + .../test/testdata/shared/mock_model.caffemodel | Bin 0 -> 138926 bytes .../pyarmnn/test/testdata/shared/mock_model.onnx | Bin 0 -> 139104 bytes python/pyarmnn/test/testdata/shared/mock_model.pb | Bin 0 -> 141105 bytes .../pyarmnn/test/testdata/shared/mock_model.tflite | Bin 0 -> 37944 bytes .../test/testdata/shared/mock_profile_out.json | 216 ++++ .../shared/onnx_parser/golden_output_onnx.npy | Bin 0 -> 168 bytes .../testdata/shared/onnx_parser/input_onnx.npy | Bin 0 -> 3264 bytes .../testdata/shared/tf_parser/golden_output_tf.npy | Bin 0 -> 168 bytes .../test/testdata/shared/tf_parser/input_tf.npy | Bin 0 -> 3264 bytes .../shared/tflite_parser/golden_output_lite.npy | Bin 0 -> 138 bytes .../testdata/shared/tflite_parser/input_lite.npy | Bin 0 -> 912 bytes python/pyarmnn/tox.ini | 60 + 77 files changed, 9467 insertions(+) create mode 100644 python/pyarmnn/.gitignore create mode 100644 python/pyarmnn/README.md create mode 100644 python/pyarmnn/conftest.py create mode 100644 python/pyarmnn/docs_conf/config.mako create mode 100644 python/pyarmnn/images/pyarmnn.png create mode 100755 python/pyarmnn/init_devenv.sh create mode 100644 python/pyarmnn/pylintconfig create mode 100644 python/pyarmnn/scripts/generate_docs.py create mode 100644 python/pyarmnn/setup.py create mode 100644 python/pyarmnn/src/pyarmnn/__init__.py create mode 100644 python/pyarmnn/src/pyarmnn/_generated/__init__.py create mode 100644 python/pyarmnn/src/pyarmnn/_quantization/__init__.py create mode 100644 python/pyarmnn/src/pyarmnn/_quantization/quantize_and_dequantize.py create mode 100644 python/pyarmnn/src/pyarmnn/_tensor/__init__.py create mode 100644 python/pyarmnn/src/pyarmnn/_tensor/const_tensor.py create mode 100644 python/pyarmnn/src/pyarmnn/_tensor/tensor.py create mode 100644 python/pyarmnn/src/pyarmnn/_tensor/workload_tensors.py create mode 100644 python/pyarmnn/src/pyarmnn/_utilities/__init__.py create mode 100644 python/pyarmnn/src/pyarmnn/_utilities/profiling_helper.py create mode 100644 python/pyarmnn/src/pyarmnn/_version.py create mode 100644 python/pyarmnn/src/pyarmnn/swig/armnn.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/armnn_caffeparser.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/armnn_onnxparser.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/armnn_tfliteparser.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/armnn_tfparser.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/armnn_version.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_descriptors.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_lstmparam.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_profiler.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_runtime.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_tensor.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_types.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/modules/armnn_types_utils.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/standard_header.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/typemaps/network_optimize.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/typemaps/permutation_vector.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_memory.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_shape.i create mode 100644 python/pyarmnn/src/pyarmnn/swig/typemaps/vectors.i create mode 100755 python/pyarmnn/swig_generate.py create mode 100644 python/pyarmnn/test/test_caffe_parser.py create mode 100644 python/pyarmnn/test/test_const_tensor.py create mode 100644 python/pyarmnn/test/test_descriptors.py create mode 100644 python/pyarmnn/test/test_generated.py create mode 100644 python/pyarmnn/test/test_iconnectable.py create mode 100644 python/pyarmnn/test/test_network.py create mode 100644 python/pyarmnn/test/test_onnx_parser.py create mode 100644 python/pyarmnn/test/test_profiling_utilities.py create mode 100644 python/pyarmnn/test/test_quantize_and_dequantize.py create mode 100644 python/pyarmnn/test/test_runtime.py create mode 100644 python/pyarmnn/test/test_setup.py create mode 100644 python/pyarmnn/test/test_supported_backends.py create mode 100644 python/pyarmnn/test/test_tensor.py create mode 100644 python/pyarmnn/test/test_tensor_conversion.py create mode 100644 python/pyarmnn/test/test_tensor_info.py create mode 100644 python/pyarmnn/test/test_tensor_shape.py create mode 100644 python/pyarmnn/test/test_tf_parser.py create mode 100644 python/pyarmnn/test/test_tflite_parser.py create mode 100644 python/pyarmnn/test/test_types.py create mode 100644 python/pyarmnn/test/test_version.py create mode 100644 python/pyarmnn/test/testdata/shared/caffe_parser/golden_output_caffe.npy create mode 100644 python/pyarmnn/test/testdata/shared/caffe_parser/input_caffe.npy create mode 100644 python/pyarmnn/test/testdata/shared/license.txt create mode 100644 python/pyarmnn/test/testdata/shared/mock_model.caffemodel create mode 100644 python/pyarmnn/test/testdata/shared/mock_model.onnx create mode 100644 python/pyarmnn/test/testdata/shared/mock_model.pb create mode 100644 python/pyarmnn/test/testdata/shared/mock_model.tflite create mode 100644 python/pyarmnn/test/testdata/shared/mock_profile_out.json create mode 100644 python/pyarmnn/test/testdata/shared/onnx_parser/golden_output_onnx.npy create mode 100644 python/pyarmnn/test/testdata/shared/onnx_parser/input_onnx.npy create mode 100644 python/pyarmnn/test/testdata/shared/tf_parser/golden_output_tf.npy create mode 100644 python/pyarmnn/test/testdata/shared/tf_parser/input_tf.npy create mode 100644 python/pyarmnn/test/testdata/shared/tflite_parser/golden_output_lite.npy create mode 100644 python/pyarmnn/test/testdata/shared/tflite_parser/input_lite.npy create mode 100644 python/pyarmnn/tox.ini diff --git a/python/pyarmnn/.gitignore b/python/pyarmnn/.gitignore new file mode 100644 index 0000000..733955a --- /dev/null +++ b/python/pyarmnn/.gitignore @@ -0,0 +1,214 @@ +# Created by .ignore support plugin (hsz.mobi) +### C++ template +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so +*.o + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Documentation +docs + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +generated_cxx/* +!generated_cxx/.keep + +src/pyarmnn/_generated/* +!src/pyarmnn/_generated/.keep +!src/pyarmnn/_generated/*.py +!src/pyarmnn/_generated/*.cpp +.idea +**/include diff --git a/python/pyarmnn/README.md b/python/pyarmnn/README.md new file mode 100644 index 0000000..7028ff8 --- /dev/null +++ b/python/pyarmnn/README.md @@ -0,0 +1,206 @@ +# About PyArmNN + +PyArmNN is a python extension for [Arm NN SDK](https://developer.arm.com/ip-products/processors/machine-learning/arm-nn). +PyArmNN provides interface similar to Arm NN C++ Api. +Before you proceed with the project setup, you will need to checkout and build a corresponding Arm NN version. + +PyArmNN is built around public headers from the armnn/include folder of Arm NN. PyArmNN does not implement any computation kernels itself, all operations are +delegated to the Arm NN library. + +The [SWIG](http://www.swig.org/) project is used to generate the Arm NN python shadow classes and C wrapper. + +The following diagram shows the conceptual architecture of this library: +![PyArmNN](./images/pyarmnn.png) + +# PyArmNN installation + +PyArmNN can be distributed as a source package or a binary package (wheel). + +Binary package is platform dependent, the name of the package will indicate the platform it was built for, e.g.: + +* Linux x86 64bit machine: pyarmnn-20.2.0-cp36-cp36m-*linux_x86_64*.whl +* Linux Aarch 64 bit machine: pyarmnn-20.2.0-cp36-cp36m-*linux_aarch64*.whl + +The source package is platform independent but installation involves compilation of Arm NN python extension. You will need to have g++ compatible with C++ 14 standard and a python development library installed on the build machine. + +Both of them, source and binary package, require the Arm NN library to be present on the target/build machine. + +It is strongly suggested to work within a python virtual environment. The further steps assume that the virtual environment was created and activated before running PyArmNN installation commands. + +PyArmNN also depends on the NumPy python library. It will be automatically downloaded and installed alongside PyArmNN. If your machine does not have access to Python pip repositories you might need to install NumPy in advance by following public instructions: https://scipy.org/install.html + +## Installing from wheel + +Make sure that Arm NN binaries and Arm NN dependencies are installed and can be found in one of the system default library locations. You can check default locations by executing the following command: +```bash +$ gcc --print-search-dirs +``` +Install PyArmNN from binary by pointing to the wheel file: +```bash +$ pip install /path/to/pyarmnn-20.2.0-cp36-cp36m-linux_aarch64.whl +``` + +## Installing from source package + +Alternatively, you can install from source. This is the more reliable way but requires a little more effort on the users part. + +While installing from sources, you have the freedom of choosing Arm NN libraries location. Set environment variables *ARMNN_LIB* and *ARMNN_INCLUDE* to point to Arm NN libraries and headers. +If you want to use system default locations, just set *ARMNN_INCLUDE* to point to Arm NN headers. + +```bash +$ export ARMNN_LIB=/path/to/libs +$ export ARMNN_INCLUDE=/path/to/headers +``` + +Install PyArmNN as follows: +```bash +$ pip install /path/to/pyarmnn-20.2.0.tar.gz +``` + +If PyArmNN installation script fails to find Arm NN libraries it will raise an error like this + +`RuntimeError: ArmNN library was not found in ('/usr/lib/gcc/aarch64-linux-gnu/8/', <...> ,'/lib/', '/usr/lib/'). Please install ArmNN to one of the standard locations or set correct ARMNN_INCLUDE and ARMNN_LIB env variables.` + +You can now verify that PyArmNN library is installed and check PyArmNN version using: +```bash +$ pip show pyarmnn +``` +You can also verify it by running the following and getting output similar to below: +```bash +$ python -c "import pyarmnn as ann;print(ann.GetVersion())" +'20200200' +``` + +# PyArmNN API overview + +#### Getting started +The easiest way to begin using PyArmNN is by using the Parsers. We will demonstrate how to use them below: + +Create a parser object and load your model file. +```python +import pyarmnn as ann +import imageio + +# ONNX, Caffe and TF parsers also exist. +parser = ann.ITfLiteParser() +network = parser.CreateNetworkFromBinaryFile('./model.tflite') +``` + +Get the input binding information by using the name of the input layer. +```python +input_binding_info = parser.GetNetworkInputBindingInfo(0, 'model/input') + +# Create a runtime object that will perform inference. +options = ann.CreationOptions() +runtime = ann.IRuntime(options) +``` +Choose preferred backends for execution and optimize the network. +```python +# Backend choices earlier in the list have higher preference. +preferredBackends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] +opt_network, messages = ann.Optimize(network, preferredBackends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + +# Load the optimized network into the runtime. +net_id, _ = runtime.LoadNetwork(opt_network) +``` +Make workload tensors using input and output binding information. +```python +# Load an image and create an inputTensor for inference. +img = imageio.imread('./image.png') +input_tensors = ann.make_input_tensors([input_binding_info], [img]) + +# Get output binding information for an output layer by using the layer name. +output_binding_info = parser.GetNetworkOutputBindingInfo(0, 'model/output') +output_tensors = ann.make_output_tensors([outputs_binding_info]) +``` + +Perform inference and get the results back into a numpy array. +```python +runtime.EnqueueWorkload(0, input_tensors, output_tensors) + +results = ann.workload_tensors_to_ndarray(output_tensors) +print(results) +``` + +# Setup development environment + +Before, proceeding to the next steps, make sure that: + +1. You have Python 3.6+ installed system-side. The package is not compatible with older Python versions. +2. You have python3.6-dev installed system-side. This contains header files needed to build PyArmNN extension module. +3. In case you build Python from sources manually, make sure that the following libraries are installed and available in you system: +``python3.6-dev build-essential checkinstall libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev`` +4. install SWIG, swig must be version 4.* + +## Setup virtual environment +Now you can proceed with setting up workspace: + +1. Set environment variables ARMNN_LIB (pointing to Arm NN libraries) and ARMNN_INCLUDE (pointing to Arm NN headers) +2. Create development env using script ``source init_devenv.sh`` + +## Generating SWIG wrappers +Before building package or running tests you need to generate SWIG wrappers based on the interface files. +It can be done with tox target 'gen': + +```bash +tox -e gen +``` + +## Running unit-tests + +Execute command from the project root dir: + +```bash +$ tox +``` +or +```bash +$ pytest -v +``` + +## Build python distr + +Python supports source and binary distribution packages. + +Source distr contains setup.py script that is executed on the users machine during package installation. +When preparing binary distr (wheel), setup.py is executed on the build machine and the resulting package contains only the result +of the build (generated files and resources, test results etc). + +In our case, PyArmNN depends on Arm NN installation. Thus, binary distr will be linked with +the local build machine libraries and runtime. + +### Source distr + +```bash +$ python setup.py clean --all +$ python setup.py sdist +``` + +As the result you will get `./dist/pyarmnn-20.2.0.tar.gz` file. As you can see it is platform independent. + +### Wheel + +```bash +$ export ARMNN_LIB=... +$ export ARMNN_INCLUDE=... +$ python setup.py clean --all +$ python setup.py bdist_wheel +``` + +As the result you will get something like `./dist/pyarmnn-20.2.0-cp36-cp36m-linux_x86_64.whl` file. As you can see it is platform dependent. +This command will launch extension build thus you need to have SWIG wrappers generated before running it. + +## Regenerate SWIG stubs inplace + +If you need to regenerate wrappers based on the new swig interfaces files, you will need to clean existing build folders +first and then rebuild extension: +```bash +$ python setup.py clean --all +``` +```bash +$ export ARMNN_LIB=/path/to/armnn/lib +$ export ARMNN_INCLUDE=/path/to/armnn/include +$ python setup.py build_ext --inplace +``` +It will put all generated files under ./src/pyarmnn/_generated folder. +Thus, this command can be used to re-generate new extensions in development env. diff --git a/python/pyarmnn/conftest.py b/python/pyarmnn/conftest.py new file mode 100644 index 0000000..af6b8da --- /dev/null +++ b/python/pyarmnn/conftest.py @@ -0,0 +1,52 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os +import platform + +import pytest + +ARCHITECTURES = set("x86_64 aarch64".split()) + + +@pytest.fixture(scope="module") +def data_folder_per_test(request): + """ + This fixture returns path to folder with test resources (one per test module) + """ + + basedir, script = request.fspath.dirname, request.fspath.basename + return str(os.path.join(basedir, "testdata", os.path.splitext(script)[0])) + + +@pytest.fixture(scope="module") +def shared_data_folder(request): + """ + This fixture returns path to folder with shared test resources among all tests + """ + + return str(os.path.join(request.fspath.dirname, "testdata", "shared")) + + +@pytest.fixture(scope="function") +def tmpdir(tmpdir): + """ + This fixture returns path to temp folder. Fixture was added for py35 compatibility + """ + + return str(tmpdir) + + +def pytest_runtest_setup(item): + supported_architectures = ARCHITECTURES.intersection(mark.name for mark in item.iter_markers()) + arch = platform.machine() + if supported_architectures and arch not in supported_architectures: + pytest.skip("cannot run on platform {}".format(arch)) + + +def pytest_configure(config): + config.addinivalue_line( + "markers", "aarch64: mark test to run only on aarch64" + ) + config.addinivalue_line( + "markers", "x86_64: mark test to run only on x86_64" + ) \ No newline at end of file diff --git a/python/pyarmnn/docs_conf/config.mako b/python/pyarmnn/docs_conf/config.mako new file mode 100644 index 0000000..b26003b --- /dev/null +++ b/python/pyarmnn/docs_conf/config.mako @@ -0,0 +1,34 @@ +<%! + # Template configuration. Copy over in your template directory + # (used with --template-dir) and adapt as required. + html_lang = 'en' + show_inherited_members = True + extract_module_toc_into_sidebar = True + list_class_variables_in_index = True + sort_identifiers = True + show_type_annotations = False + + # Show collapsed source code block next to each item. + # Disabling this can improve rendering speed of large modules. + show_source_code = False + + # A prefix to use for every HTML hyperlink in the generated documentation. + # No prefix results in all links being relative. + link_prefix = '' + + # Set the style keyword such as 'atom-one-light' or 'github-gist' + # Options: https://github.com/highlightjs/highlight.js/tree/master/src/styles + # Demo: https://highlightjs.org/static/demo/ + hljs_style = 'github' + + # If set, insert Google Analytics tracking code. Value is GA + # tracking id (UA-XXXXXX-Y). + google_analytics = '' + + # If set, render LaTeX math syntax within \(...\) (inline equations), + # or within \[...\] or $$...$$ or `.. math::` (block equations) + # as nicely-formatted math formulas using MathJax. + # Note: in Python docstrings, either all backslashes need to be escaped (\\) + # or you need to use raw r-strings. + latex_math = True +%> diff --git a/python/pyarmnn/images/pyarmnn.png b/python/pyarmnn/images/pyarmnn.png new file mode 100644 index 0000000000000000000000000000000000000000..7a900d8e3a1af3cd8c8b2e0c8c2d710080e4bf2a GIT binary patch literal 74951 zcmeFZ2UL@3w>BJgM#t`qj&;BWsG}ey5I_M5HG~90CsGUn(jWnnP(pQVV`T&t1p%>u zNV8A`1nh{2AXTX<2m&I~rGNVdeaCTd&ikFS{_~%2t&_zJl013JUG{xl*S_|B(3q?@ zf6mG|7z}1UL0{VhgPD!PU}i9WmUo3Rvhj0nc&1gw|07u&$llnDo*W8h0%!Q??Co`~h* zLX(u&Rg{s1k2YG#$w?~U;a`R#uBSy>rnoDy~Ncuyw}K8wfw^w@GT@-m;EUEt&9H2E$~ z;6mf0qZM=}noAS?ZOC%)pn0&M!Q^!(KX7Ex7#=jZ4O!Nwt2@vX73o--g0z#9v!XQC zSzcLMQBGM!S`jC!Bu|&4E8rYdChvkvbNzHNV`xw5iCa+6!C80<=t?Ac9b>E<*Oi0X zRg;D%I6LUe8@d=On7f(@NH~&;8P=UZ=F2jO42r3h4;Ojls>SE0-sVX=*Hzvc(*odvH zXGGFuD`CAIv|Z#Be={Ol$&`mAf$zERC&fZvWS2|xvp?Q0$5UdF*CR``75>eBdt7N7uWEp5W zSy{-MJCY4_97Nhe9g4Yw0ms7J%uSA_%}_QZP*}#s3M>wbVPNde@@5%2=yC`=d2PO@ zrIIp*s!7-K)|J)NH)m<;ay$(6*;Kp%Sr6;L*OPOQSJGE;H05ctEX{h0vGpyS4V9D(EWACGJQa=DTF?m=@~%P;M++sk zg20-fFY?gknd_U%oJXf0~ z@}~HZ;2=#$nkCzc;v&!zXzEgxmCOjPuJRO5b1V3a=IX8BZ9#SO;V2t=xLf#mY8!dV zD_b!24Yj$}Jbem@<3LBp@pVlsxdd$&rWM}ZQ0U?9W@hE;=BQ6}P;sU?bE)1G7grrG zD-xMzBrjAHkz5&A97z*vbtv&oRlqC zUd|#zPfbT%1v6c&oR$?$9`7tGFH1Kt&=EQ6T5vhWdTf!NoTduZ+epRDm}{oX#2Isy zDKvpHm*H;2G~pABeKd_IJ~VBLlZayCg6Fzf8+lM+nChAe`CcLyXCh2KO`H(^^zwGJ z)O5D+5h|h?W2(*75|O=}tUM?btcxLh!GOtfc6VTslvJS6@hS#dUL<`dce;*=qppgH zqK>Sx2i+Py3m&iJq_1nl^^s*Va9)b8JU26vIm^SEY^g~xL7&69EQAF3hqI1?v!}>F z#M8nv$v7|cK3-Wz)5zMuP|<;fu8E~Mx@aomEiFuRRY(NBn<5JJ$_!ICM;6DMN+McX zv&@VbS|SA<9fpD@Ng&7MThkdz46-xJ)k(`-sK?fJ#alY)tFYK2ysp5Us%NaEgJPQw z-b7Q$1OCBuarWftyYdxPC)h#@jMGhH%B5{WF@b_)@6|CLOBYJ;ill`scmBB=|HBE zoeWWMHD`JXX?SCbJH-|2;L6cr=;=DSFchre3_4VIcP9sygN28gj~-szNvLb+;m9RA zQ)Ibvu3m~9MQd#%K9$MUHFUMYdn%xZ65@F*A=#Q@4DdqNMN3asTi1x?u0_}63OQOL zoW34Yk%?#NIxq<|6K^;KtEWeW*pHXv(7pK-B?W7Fjj=|3$SEMf|Zw>4&H!7a8x$+cHmPT47`mURa}f{3|X3jn-N=6*AlPf zV4-NNtL>tKxA1hgA{x7zd$K(_JS<<<$yJ-;ZGwg7(&ZXqopiW-2Wz%GQ(g#f(0EkT zEIt%VYa=r^L!6Pjro19UkETp>Fo!{oH|Mgo$?%)34@Vgp6ZK$R>awv$A}_3_rh_I? zM^}@_HFbw0j7 z2hov-&aZ5)EuiTLjdb;0oSgMds7yYMq0eMnVTEQ6Y&ooeWvJ+=>*~xW(D*E87oxd` zkrhFX%Yea3z|#pznq&u|IfJ5QC7`&ml-vb0XCYCG#rM&6W*b}TxRHt8begh{wY!6& zgRCRP#Zl2hMPKB_a&$5lDw!M0>M0w!>+-BbJ`TE6H#Y}43!Vo_5of9FBr>MCIU2Cx zS$Rw{(ZE7Q8x{{uEprDec|1`B_bqFtWe)QlPr~VH>1Yw13{bby4LzOVD{gXhPe+jx zSy|aj&)d~S1=<%(ayim`-GdrYTXM=Z3{=TWPs41y(eHmYI(t$B?3_X=H^r zvY?~+;At*$6EKvCOb;Aa*-4+ z=Bo&~asm#;l7dsB>vGJU-Ps;=q9K>=DCe$XMdP?qcvNG)yD7thCMzrJNakx392{s^ z559pmjlr`PnQDpTEewRRN|u&F0huI6wI*phTkG+Sh1wo`ElW!_Pf6a`kmiK5V!&nD zMgjsvV-Go&H^sq~Z9p-Rx8%xd8|yfs2p}LTsW>A%33wL%{~5VX;9L0n6C+U2d1H6a z5QABRA!uuwd7ph(7wBfzmowg__(t!C2zv>#l2%YqD(wXEHRiWPaz7rA7y)ICLS?1R9r&(W3@XkXi@Us z=-DZqtNr*-q+Vz}cO39j8uokQ=ltjezyH?2+2UZeXn+1OA|~jnN2bk^Is5Q2#v8x< zwf7h{Lo95=W?1s_g$`tz)dj2t$(c_C~ANS9|&>lXJ1RjK5 zYnGf2oQXMoIVMKa-mhz)0g>3(TV}-h@bZpmq$@vi!Q3DB@{}=)oNa8TPksS|-#y5{ z?6|m5TKZ&uqr2)n?H_VqVRoE$cc1;~fFHhZj-G}&-qaa5o?Tj27P(-?k3mzP!>2td zqxb$|BW1Cf^YU@OV17`Lk(u`CEHSr76fkRkx)c|ekevLqRd$!`)R$rQwN?E3>#rWF z^Ohf(@+uO)N=sLG)R($^&d(N2?;s7V8+bGRSPhC zS1b$rbfZDWyT(>v7R7h>^h7S0_N`Bs-`jPddK$F+?RkG~rbBY`JTHqu3(tG<)d~Oi zEB~by3wrJTciE=Is$<&w^1mLjm6WEltpB zB5j*xw%IcIL{)p*DNA$nODi>DWKnoZy6njrg`_tfp%KYxWMA=MJ>P-%eLGJ@Mkc*}?ckGR zm$WnEDK(c}@vAg7Rlh8j6k7PE^eXLMDz8D^$;m1H>Q!SFi}m=)lk)zKnj}{P1A`MM zPhPrzU%Tk6ifLb8U&zI_HZ}wy_UKUG%I)4)_b*)iKEpPlUBzN|za(Lml1s@SYqhYB zj~4y>^Uu}=B;Wpb&+Z>woYYr6R;QlHs!F!a>Z{y&G9=`DCcouMa&mGR9SJ($-9?eX zL~j3`1!*%dcaQ8|0dL;9>G9^!{sm%JX3Sb}`N|ao=&LO6uJaPsfyEIxqt$*Nc0;rL zym;{iOFJK(`Sa(m#BtA-mX`KDSZ2>Gj#7h=s_OoFZ^Lkgl#HUHBJ2588ib9LN-q)N((f)_ zym&b?(>f&8Z_q<+=GEyJBZ}5Yt z{{^G3L~Eh-FlyVO{*HuGr%o-XF*i3C4tZx!Jg>CfEvwBIiC66s5)*H|Z;Gw5542sl z{Xh%!|V+%e89W>!;yc5)1X~jY;GFQm^tqc zxYhhdii=BPz=u7n^|QinDtI*MbLwCe=4nTr+-%8)#iFtSqJ1if8**`lf-|!qL~>ea z#Po^Bq_9}=3s$Qq-aD|c9Uea*rGa0pR-GkGICSVxs@p3($ET(#HZgHaUU6mkBGi6) z#gYVW#!mIx*%*N)dFhET_ z_;q7r-*1=tvEk0bmY#x^9u^E2 zkL)^CulF_wvVZE+_9%OHK@UdaMf;K0%itkYcgE$U2VZJ`vVZ=nraqCP)8i$s4Y_sH zdZolwowGY-9yV(#K;n`W=5mt-k)+)EBN$*S-gx99B z%A|_I6$_~;moDiH4|Fc{u7z&+_Pz4*M$>hVEPqDxfe@O`ty>w=Bt?K#*~G@i=5|x; z(@It+wSbhCmIm_@dgz+?{0+7+r(9g1AFjm5Q_v*w?Np0Y_ow!>6j$w8sQ$cF?kyK4 zU+%A)H!Ctr)^t>-5rsnGP=7{V5+EBwXRiVN6-+v#z-N)m0BT{_@`v)24hM9Un3QXUtS{;}mS^6$`|8}3~ZOj3LLIhU1 z-dzsx*qa>-NYMI^3kt5mP3Dn(s|;YYKx4PRI1Evmw_XT3w>G2-jd{ zVR7oGpMJv9AI__&s`3)e%ZjLV%-Bk(zUh8p|GX9Fj~tnoGt`#w_|c=Qb~{s<$6*L4 zIzL(NT9=hphFWUt)~dJ9E-1Ra+)*d2vV|78rpAR^-r);#yH0Q0^s|~ZUoHSId8HT8 zmjJK8QEOhyO*w>ao0CYS>P&u|yu7?%u-hr_9$Hb>iT4!m7f2U^zfKI#y9e8iZ?9;4c(kk_bOTV6 z{pT4dIAAaj9ZV+9wTNtUVEWUWqy07J*4C|p<+4lE5az;QWS53MM!$pHUaKx~d-(dn z;l8$n6jh)uI>l%jVlYP@T$D$@*POk%;QO;(%@I6y^OG~=1JksgDNN1{x?KFkp+9U$ z)|%GR^yW?L#%$xFn`krEql+|M& z4pgVILXU~7UAe(1Ru%i{7u~7u`H3;W4p?~Gb~X&1%I!MJ18gkJocD{e@wIE$s&Dz+ zC|f;4ZSPcVewqdgDGI%&MrxN_YtmZ*`m&i!H`Oi5|2qD{ty?y;XU`UOq*Y1ytHqC0 zb5b_R$x%9Q^&AU*g+dcXv;vx?$I;R8^2Lkyf$}X1i9l~-f+#}+ohiF#%=%SP(Of2s z2(T6w!nUfE=(3o}FRq0z^5Khb?=9320E^q-BsKBPT_*sOUAE5)0Dx!F+K`tQFj#1> zDz*}Rcy1YVr0l&AN#afVVpScF#S`C%p>$6nhPSAmHgW$7{!|0Q&X`{&d%)N{*Fo|Nl8h#aT5oJ^UhDt#t#jN z2qr>dng^es7BDKBFTULhaM3_bvN@lBW9b&FbFc~nCT(kti#{chw>&&NytkCNmG$w% ztCh05f~3f2FYlSPklNWb+%Rs}Fw%@eF#h|wOW(r02`C^%`*k0LWy#Fi`UQVh|M>dM zN%3HLeBYm~^y)UW(%ITt8!;6R7!AKw0*--}e)HxHx&q(=z}DwSM*KK+@7}qXo0;i% z=kZMq4UbeNT3-MZkpS$(tJeL1M0|R>1<(P}K%G}bNy(a&s)QFe7{sjb+9uEJG|vud zwQb<>uj(Gw6E&ZeGwX0 z+^6YBLaE01pb<0!d*FQ?EZfg+x;G;1v^lf&`8E1Vb-$Z1C$v2L2;52aL^n&&8q!=G zopN~DR#h=XW-x2!xIxI;prT?8tYoqq*JsouUA!pq;K74UQc}9W(n2Nt`zkCE_Tx7n zVYAsyUEbxTrRxebGGPKe6K${E5bbv!@Ch*_%D>-Qt+zz0GM9W-IR^02l}nc{-SY&< z9jRW=g;Qm<#zypJ&p+bluB*8Ovr$ym*H2CzWQ62wEqT2DX>=Dg0~#DQVx zzT`uP=A1lvvK5$YIYKFcVw?pR@FRpV@--9}>K2X(@c;F?g~f!E)PAro+mA zQKlw!s@C|seo9ES`fU@L`VbF&0t*G-!@ z7gFtPZCxdC()PKx?(u3qLvB?f7g)!$SW?7^(9jFLy@H>YE^PyH6d;Gi;o4g?#xoxV zuTIJC%-somJ|iST+`pHi*7HO`tUd#n6vPpu?Ol5TiU~(PcD1VsY+w7$|2}Au4}kYR z`|&<|#MH}TKFYl+oS3xGSLX&=TL&4RyoJzdfeu}I4+)qjgEL&@bi8daN2W0`0o zb*C4QTth>urEqG)?{0L9aw<7VUfemUwu}POfdyhFuC7TEC_aYE(tfM=Zk(UDJYXQZ zf%WeBwMwy6C0@OYBrb`XW}q?rqBwD&CdW79(BZ@878bG4)_NfT>_=a}BdA$y1H}7F zvBbsiWPE&l)Pz@d|Nf!fe!RMkNm|@Gp%S3Mb=weyj^AkbKpuWxoqJMKY;`sD(#4C9 z8oR85{|HT%TB@j|1UFhyUcPbDCISSLC~~xV(qK@Tj>sW5DguEMpUF#$)L+uNtiUM%(2w_!L+qV@Lf83rU$#jMqS=fcBR zl1|HN<_%S(07i}-exGd=A{hDT4pKz>g<$nL{jxL5>1pdrH&c1}gPN%Xef^R%3XHQ0 zE|{k3p$CB1j*Wjn4ET5rILAYdTNeHsL##S@-RK|a%4?7h#jrh z^2Pb8M}nBalQ#>Pat*|?Me`O9`7U<&A&n3!6@TkiZL93Mp9)Bn-+J~iuR9j}gk+Ug z+3Dxi{?t@qz1ksb__mYu`&u)IrK$WDvXi5ul^5}D-`dPg25te1NX|v!mtd7p7Sr7^ zb>r-Pypxx63FZMTz(uXb>z01HWC*kfP~f9ZF;d`{U-jR*T!>yfp`fyY1 z#yf+W*n+1|D<|52{hI3apN?F!L?5EpclY*=)$d)tOC}`Z{C6$0o=Jdy)KVPn_04Aw z%GBo``swUy?NA8Oaavb(d?yz|3})~20um-@*Tf8(@kHlSW1hBaAO$0`k6w$;Om)rs zy+d4)-{cXPphw!UVy{^))jJ?{AHzRhyF^l1<9DgpDzCD}&qPF9>dS{Lydnlu1%QBN z*pnwuxUSJ*VJ%JWZ~NZ(i-!|8myIhM8|Wo4k?SnU~S{ab5U-bes9h zp8bcnnfs5oInftgI#{`!+z9d2pKfW5JOUD*Cc$kR37pRCV_VN3Z0)P-{rKq+%<+SN z4x(wz>(Hq2`|+QiY#ACKjfa^IB7dBk`lr+Ea9sE20G{l`U+lFPm$hfNc72Y6>lXj5 z2mf1|=HKbTp!bFUAp71(PRPEFl+OGt^}JX6SDiR>CVm-czTsWHtz~*Z`ax!_yiB?9 z+lbkn_V`gHC8b|CZHk|@t}T3Ycz^?8hgO5SfJ?yQ|aMltw*8tXd-wtyH+43ntUDR9N?gs|?2mKBmJz8VfOQY_mmlWv?!fz|2oQt47aIv}ZIU7pK!)MOZ0TU#TQi%Ja# zaGd5p?8%7;3Hb?RVg&LP7*Eao{c|oR6c92qGpTt2IWrfEC%(FOVA!?l3V^z~OSc$% zcw9FzHAU&Coi#Nb1DC?=N&y!r?z)X3omHmHS}2}sS96uN2k9wmwf0}WeEHGV%uU9( z5SNP{A7KzmvxM#Cun^eZbkhtGRM~pP-@JKK>s{XXXlGv$FTixurcKnVyLQXi-Fglo z)%fggb+2=8LQ*+ZGIgT%B(w@3GG%G$Rpbg?T;lu1vUA(!$K$oBK0VH(O&c_x-SX)< zarSH?5Xha2{dY@QU%DN-=MH&e4Jpf0h_Bov`0mg7z2if&P3C!|r^2Fm<;oQkP`cm- zHWDfudYX&M&=Lu~1F3gn=$k9_3I`G!WS23R2BgR(9zi^F}@9l(hi_$sRoYS znlpEHd3kSm&dX2!$&`N6BJ z%HvIwnxwM5=gyha+Z1YK5b{26JOGye1h`$8ZwS-|jNG)Z9Up1YI2j&(r7qhmAvRVQ z^(p|nk&uXYujln<&XCpskq?FktkSW7<~XnJ?h98HUg_|Km~_qy&5}=RZcyersmyp@ zL#}9SutoU#{_LNZEZMkWgI>sV^7U%cf|=W5TyU+`6|=45N`74PXE?vZ$>ER_c5*Wx z7|5W00u5QlHcO(lP3UM~a0psil2917wF)o`+jSd^cz@8g13DYRZ%PmVVXssbTz(p* zR>PV|7x-)!2WcGoVv~#v8Kyin-Q|o#YeGtHNKFS?Ud>ER*8|cxb!sVLtblaz;K549 zAT&*4QzXdn77NvS|42(&yn%cf9s=-f>s4Er&T+wKYOXsS=4Q1@jdt(?LnR`&`iA|M* zgRMq^S0K^E>8#H|DFW5|wpLcm(OFvvL!Zz-V4ZB`8UF8g6xs)l3e`T`o>4jWG;&+q z&6~<;sj1)9BE=+U?w29(XSo8>ix)2{8vs~YK!Yt11C1o4g6kpse6~|Ip(>gDNOj*a z%%5xH5A`UE0dpG~3w}_7VwT3o-7p9bB6+?tDLJ`6n`@KlaS1ZouA^!YMr#0ph0D?R zpW0&mlWgXu64eg);>lB|+7Ns2`*;;PpnYISZJ_o7;HxW_FY5!ZNj;T8;sUGf9(GA8 z*akf~Fl-Y7yzfoVplV(64Nfa`0-#xfii!#(>?|Q*%Gli8mEF+X>^uqsvg%`3qY!u_ zXQ;Qe^49IMD*WEg(yAYUwM}>dx(l}si&a&k;T;hke&UZm&S@Q3z^c9Jj#AE$S?hzj zKn+&ww>oeWmV-&|fo|yfVTF>G1*x}yp}M=fTa3aQa40yqD$G7$h1)7<+g_P3V7sj4 zeFA_fb>Q>Su2%@r<-3u2vQ3gOnd~5ju)_ckgUb<58T;z%UpM$Y3%s zhah$_fj>8?XZjcE1+SLa9bE>cCpMY(KBot_#%YYklcToB16D-{K5(RQ^|t#tkY^ly z$I$4hF#`F*-Xqj@GDJrxxt1nc-$)#VvBw0tKfUvNkc8^@eTjAsoolmZDWVYr!wZu#T&Ld^rPd2K4~2tAICQwT{ae=ZeLcnZ+%p zunLcLhvmJ6==@%N{`5mACN~Yw<)R~g$w!~;PUke52Cx1Ye5kd#jmSCdRNa0X&5f%YccvH$KD)IVt zd6@7&FIz^;Qsv1))))xV+X_p2b6wqsij5~Pf=rQVBhy~HedNPEB<9?1>>p}R;oSfI zM*~B{^5B5p^%HVOW`WGp9nsrLJf=*$?1x5rYx^bpNdBF`4OjQi68C;N4bncyCsGB; zr?w38Fu@|bKraTZT#B~Ts<9s=YF{yvc*I=r4e2EgR)7FD0KqhSS+mJJ7UL_|`74$D@8f8|K0)XKnEjg! z#HzY#^{rcr3mQw>pM42l&<+0CgX-W+XkE1Hs(3<-l$qyu4}sTD$tBB1{UKI|gocV8 zo#wp$lgKa?5q>2I3>=jHKM{Sm>v>x@ZF&ZZ{x3Be>t=kGKcb^M1wW+!O`?e#8eXU| zV!UF-iev9q9p5)8fut$}PzKdEa>2c&9k9?(ycW%wSdU+62ilvM;2joc9r=7P=&{#t z+?eX%AKS)q!BnSr?3ZWht~I^zsGFpvkLo>pmG#G@RIwCN27pY!xVBR!?C{raKI4ri z{x}g8RXh{Z8&K8OY5BFS57Oo46v}Zwb^FumD@1cYHJ<~xiNX~*r^;yPzTd93DMkv@ z(^tdZouOf2%Vej$8Xah;0)DGn#|88UCK#Kr{%6mhL%!u=)V%CHlgSK~EyI_SlNWf~ z7*92Hkno%iD%LGOU9ZE%Hg_U=Dk`xO)xw#S>!M>%X^?cGJ_ayqANkn(c& z-auD<3m7PnuZd2-fLu8cyW2aBG)A$zekzVPjq63?EOB^EW`U0c?v?0S!#x3Irxxq% zj!py*;u8~}w93MnFujYC8<6PG^1=U>S0VU}0Kp{XB_t#uk?-zZJV5VF3JRt*8N9Hj zz?N8Wx@+m|$!?5oiUic8sl5-~U{ndC^91l5$ZK@GxSF479x5Ol`EcOiX>&x>ciU zhRf0vPduF6tIl|OmV(Fs_Gu7k+T@{LnHU6>)*e%2NG={LAVGp89LI}7pkfLS5`ZFP z-)V^swA(;FYYd(x4-XFsf?lw=IRG*kh%xYFkK6m&j_ZlH3Ia>3yv#v2p#%ivrUg$X zX9PZZo;4XceA9D{CLkh1dLswKU`P@E0mKo;N>XF%-?K4|r9)#Wx4I9lT)7gtQ=qq7 z4{yya0XrK6L;-NS8q{O}&5+pxl7f&EDr+r^O9QVCDN@xuB-N>K>Cu&n1_u_du7q63 zl2xk`iZ#Y>g;MwF>jW+=({Y`D$vwOkK?pMW*vtjLT!y|X1gyK_j?@p$OT%_^hi@Xz zKb$x2oAFx3?PU-!(pa#!fr9=PCSV2ddI^HYSXc0b%c(sI@QVLX?=x@>F{Fn-+*??Y z!Lvt8J1_!dBkBupZ8@*NZ+`MDC2*88i4aQm+zddvxsfQs==!^foqvTMB3vM<@lAWc2B*b*n#*PgWGr9BZ%| z<^hite*8c~;{fRo+{`yi=`;00Ln4r50tyfF&Eu0Orv}HzUm}K4?RP*Tq%Vo*Ur5bM zO*I3oj$m1u3;5T-<#SzPP!gweH-XOdmOpi`U{iXJ5-hzbUfTHvJQNVT@N z4^-LtBHAr#iS#-<4{6T7|2h&?nf<+Ji)~Hb3`yM4UsOt|JTP(v(gWu5;(9l8yB57U}a!dTvmy1#hF?zUH_)b)c-GhHm~O<@=ZP<=pW$0zz<118R1-{Rxz_1bO}tKX;#t5adZ#0MiLIWz(k8^F68Ankne<413Q$V^R{@Dfv> zyKdRP8u--y{WC#-=%~xC;tc|p?;Y(7vZhdA95VD`;hAXtKIPGF%j>8w--jl_F_r>B`RErpVnLT@d~K@xx={ zv4^Wy8Z=^w;1GqBBQN_{9pA1a%9@j5JIv^%)~j z{=D2lXW+Dg*1_8$Sohc3{w>=Yp4IU3lK3r8h2EoR4grzWBb0)}ED`C?kRn7{u>=b^ zP35?VOX9XY&Xlcyj!OYq39Km4WO?F9g_LsF(m_M%3J?w!e)a;IGjSE%peR4~y3P_%vK5E53i^FJv!+QJyPh|J{?V2zQ zM+J`gjJD^EB_Js}veQp*b->4%zMHyneR<q(24uAxZ1dSOQ8Vbt4u?<>bIOJXN9Z2&pS-m<%j{cAd;yze@36?y4IZ-cj zKG=S|>V#q7H;^DOilZuytWW^u?6Z%f`%Ph_I8R$@$oI4io3rs~*%n?--zk6viFsS? zb0<8spvw&*tr22SwR%g%`!i{tHEDsmDRn)5mr_C_-qrVrqBhs|R~|E|it4OE2IYX_ z>p9@sw)5>!1VxZKn+zTZNMw`+jt>Oxj0SZ{3{7#s)3K0kwyMdH0?)0@X_vw7H)zm* z0z4oD@m?pB&|FaXs5W0MzT-l(sQMxy)XdIKycgEJS?BMh4Gn`;VR&>yYWt?a!9j>i z768}Y=M30^!2`Uu`&vU|GJdb-N%*Z$C`1boNS__`IW-dj70uNoo6ORFl@!l2#qdnU zz3IMDDZN5ae<$X6O+)4;a4I34{31tV^+)hqB=v2)l@28$eXkF1H3&gQEU+`N-`8e} zS}%o3*phxj0$|`sejxM3b*Dk3^tYoi=Ar%omcTK+P$?2HB6vWI+PS{>q}nuO404;97ZOqK zkthpBz|!)7?CWZa=AV7rNi;A}_1`O5a^~`=?%gmsg`ECs&Pt7dTWCE3H4ggD3er#( zc+$C+TXyYd(srkFqH7%ow%Zqs59aKyH*Fi@?^_LS9%LB+GeNk#(;;D1tc7RstWy_3 z&~0rP>u3P83etBWCDPiF(a^uMC0}Fg8d#oRWxUI5i7Lb8cFl*0`jCoDN}F5mngQfY zk6Mf#WX*Muoe0R*{NV7i$OWKXVtzLmiJFOdb|-#p4E$>4FYoLTdx2X=EDu=!s&M(b zX~hFM0bpG~?r&h07FUZeT>5LN7y9cB^-^5RjcJ&i=+MzoF!;?<>$<=ASjXz)YuS0n zKRC^kY8=eHvkk~B7gm|LdslVFKWD{1rT*Qr@{B<8hQgBza<^S5!$O#^?`0}jK4BIC%I z7`kX!>2De5xZzIkV=^c$@Q$=K@cUtYvxSf|WB%e9zS9 ze#<_Qd^t%BJMY|pe2)J&4F!~)+nCFDbUOWS!+hnm`2Ve8*7Sl!6A1+Wxsf2P~JHAh`)#2=WaT|FnpE|nx9gz>lhgF*IHF6U-W-4R zXa@%eEYxIx8R-eNUOlsw+Mb$$Jff@BZ32hNq6P1s%jKXH3uHx=xV6tObop)*&#=P=!?n9R6~^=um58uts{_F}OZdG5z|bQ|Ie(A5?;zi72-TPy%EIYGJ~aE6@aS zAORg+p`Z&07BN6b44wx`1QnbCj{md*qAX6YY@0njz%#oF>IL1{e{_z^@Vmk`?>cL7qA|Z|d3GLqgc%bMeEr2rH zQ`6}A3q}j`Mu=e=7wRHIlZBR-fP_KvAJx4CO`8EO;CQf#fQNKvyE9l#z&Q{y9vK0n zEY-PaWu!)+9aJ`EH6Gh0K|m!$1_q_z=}Z8Y3(O9s4@)bWq`=yj#;Ixp=85uM0QOYx zTY`lFjIkymg7MLT&FboQAR5B>KLttnuEzXrXY()U1z!mqAA?kJ38)lcoauYCJ{YRT zZkK~@gRl!KTbnay4!BGCMOBEM!03XLw1QWNdebc?mK^zOJRO<%b_o9BK=^tM`6040 zyJO07yxRYu9B({0Cp$R!ih9qu#`)umq;P+f7@pdJq3=2LU18*+L$w><8)VG+KBjHm zvx@08=2zBSwg2_z!;_Z|nO$WodTyd5tdUugfPe7Aug|Zn(Vahi_IdnXw+-LnA2p2* z(*sl@2=bvc`2s)XmOj7jPaA1Z&z{LVT`cr>e#($3)-MGexjc`7N0aJ#{*B!>dYOMAb{T^3|jXZJsr!>Exn!g*$eRW$&-22!0k{16`0a=CEWL_ zvB`O3eJMxnGLabuEZ-JRPM1ze?{prGc&ah(*Pa(9y>n@A`Vd?QdDfBi3vp|r4z&X$ zsw9hKn|PhWOOZ#laxANe1P-f!6q+l)4D`}bI}HNlhe7e=^adPN#DV{PBTzw*;mq;^ zM$x=EDz?o6W#HfE4s!|f^RGcI^FTvj9;&1OD+(0`e?Nzt1s=GOs0%>A!Min2wA0Qm z3RJwrs?x@;jWAF9vCXyY!vDLua&`Iew=m;g8sS)qW4c7?Fg_eXkBtZzX6G5a5>4hu~k^S5Tq$3ks zVTCy=fJA{CC}kTob669H3mT4Ss5K6?G5CQldv(@>lsa3q)7ttBQZ|ro3yp_zML^kC z`u`g<(IcW1pU|;+#W)dp=5h@Aa5n8 z(y(4DDW^+DfWrc^SjYu2+K)Wp$hHmEi`&go1zu^8^4WrFDxaK|Qw5z}Xmh!U1S-ji z!Z+9LhPQRzGKEl)&P~xc2Ax+1`74zD03$joq6R4$Ad)3$M7qJ^Nf6nQB(i7b{8Ct( z))F*%p%;bimFUv!hTI%rY%$P)y3xqR`4rCw2k|dbPbM71DoWcn&bys!nDU8k7%F*x z*ac1^nQEH0c3t%4Z4ou_ouD26G(>_9LeVR#s{vyR&_Su{SK8k}xBpYTy zKqW;6^Hi;a%hAD@MJz0s_wUl~`XQhnM!&J=F6Lcf@6eyj>2~J9X7G(G8=eN=__SsZ z7f<8mLH^*^_>%=GE7T*FfVJh|EZTQYN(eQebi9O^0Qe|Dq576;-T zi+*z$L*N>{Qooy`b$)u8^uHLk^zJD-6IM2q(0I0B{hDSpX3=X0@!S;6 zg#5QMGRHiZPm>#;JPw1QPM@O5(bfm`0mB{413xb6`Apc)NtXP5oTNa7{99b!ASo3} z2L?ae<4b;;qN}T_(>N#;>;3hSVkE;Oo}<^9tU@Mxl z3Em5}WGBaI2H2o&0Pi00Tz>S+7OjN+Wm}f{0l!&F2<|d)jf9W*7ggpXFH7p#65Z_KY7?E za@Bi{W$pV$)tmVDMw=9T?v7P97}Zg{nh4&*1)=+g3l?|p9c_6Z&=xit^Mu|s8{X&- z-SCcAv|Vnkcz;A@^mdwt&)tCjjHhF#UV3Cwd%N7ZeAGx^JS66s-ojavP51xYyXPOg z@c{Y#tJez$dfkzuMYoP-Iimia%Q&p`uUHerq< ztmlr!-|rG{`{}EY_2p?ww5OdukoDbHpkfX6^R(YJ3roQ?$H2Bne+5^+Fa12-pHCw# z3G(?LejCaZ*Z~1ZrvTXX@9JDY88C7KhHDyzI_x1A3m9%`w;W?t#=n#apO(ltZe3Pu zd|cFZZYNao0@!_VHV_JPgSwdKmA9ou6M5Mk=`%BcT#&${2z$e*B;;=w^8&3V>y^LN2jrfv{MDPKrAc6(Ld9Y0hn@pIWtYVsh3dY`kbrwwZJ(DJ zA5VX(#05)sMnk|(Ff~+wS)dmcu0m-@jQVJ=DWQM-E~xH1&L4d30t671Wx{G5Fkc0- z`;fOd3_O9wo%3UK#f%Hz=*gE@D1j&kPp~4;a!@F!!-o$YLZuVPumb1|LbnB&5Kw{! zO4R1BkkNuzxc5#g%MF0W7d*`M}wg5tvB#@JVJ0`&1H>V`pJCOztJA{CB`Wi5!3oTK;WKi2Jfc7A{EG2iy0GbX2 zuu@P-M#b~e-4XkO92-gzU3?f3@tYW5$(=Z6f7;B_k^_1RqI>m$#a_fb!0y=2)OF zo4}rdN-7|~k9Doag0sNHC?+6(urVJMrD1FKhzJ&9UJ1Lxz<9Hkbln zaH9OfjS?n^(aXkSi%~xMp-N+D+h~)S(;ZH6RL%e+(YK zjK&PqkCd8Q+DZ~&`XLstK8pxNAr=rXk>vu)KPo%rI1v$G`9zx^`S0(WtPFr8{vIV< zo!hDQNYIcJY3R7 zFVAQ6z5LcXqO9`|6doqfx;*DteJl<(sH#E&oYq3MC08@!uhE}MaSO)dWLDA#wi?0{ zON4KHYEjo53pJm05aNe|$3HGa8WzvG-ko9g||>iRYK+_4GnuR$lu^5 zZMtX(tZyx7T&Tp_CY+9N3EHKCdl*F2`7$asSV;%;lTHGz1?) z`3;7VKP(<0V=3Qa>XL&2`;#F*4Pz=4G>te;a;C1Bs^F@)2IPTL^2V~DAm*a=4QC|Q z!~B;5V+MH0sSy0b6&S}v1MicdQW|aPW@#%tiIhgz#*7KRn5?m(c1cASLlNvKhpJ^z z*%1_%m;(ieJswwMV~tl|kWo*-9sLHx2r@N<%0FQ3ygj49(~;ysP0Wl3EeMQiv#V>( z#!ufkxbyX&qctOw5R#nPyb|_%cs`V!4u2uV7gg~At47)uXbg#sNQ#FgVk2x96!`Jp zYA~vD>!56373q6W3t090kL!^FxXGJehM+e1q!77>-S38=KS3w_28ri=upR>$85vGc zoCytuZO+UPk7gP&dX}fbLXZ8{LJwmvNUTKL{e8l)DgWxM|M0L2nFRsg8Y*xu3_!aF znZeCc!~f4ivrkX+zd-f>8Q9J`7)n%1=hN0lzj&8=yx7IF`Vm8^JLu?kw}%fuUOzs| zg%LU{Mt-zwopd!l-;-ORm#Uluu=Ds|l%hq_XKn5t#21-%uV|Uxy}}c(uC;FAOD*x| zs~YDX+#VF%+HMlt65ZMr=R?l-MzetNPsK)<`H2nKgvdk;(E$8o%A2FWK9-IdkTc{s^7YEEA0c3e-vL`+`R%_u={rXa(n%zX?}+GVe)F@ zhQSMq=wV$#s_k^ly1&S^YpBYZdlz5(eZjW1(dgFE1)Fx~$mRa=XvW9WxiU_ou`}YU z!}Dbt+fyNCYVZG27~7HR<}rJN!;fOnTIkl%1s~1urTeWtKI?;=zwP7R5d}4MY_RRa zDi%Pl-{*ZPR0T0#HT^`szfKjk7Th|z;2*R4AAZC`yVHlVPQcb7B0r{h_K}n_6rNUm zsXGEFDUdw-yN&?07Th|z;M#@nj&|*XcF*q>ZYt|3?)_M@-E)O~Tm>K+`7b3uV5Ff$ zBsd^0Z$o$dDi9H&6ivL^KJPQc4X8;`xhcl78syg z!3C8gbL#`%ALMh*&Z_VtQt!LR2i+O!2}{NT$z`jL;eX+lT~c0R24 z>mlXUgo~1xT)f1YFEdR4SF=fZzu>p>v@n)kz7hNYPyvTr zd&u1fKo5ld3TRU(d$^NT{5Nvs&MxVa`7W^$(`E136b~*a&MQ@(RM!$%u${ubo$`3_zzn?)0TFJsEEP%}M*Z z)-;0_2OtFQ-4rqfy#Qo66<5qr35L+c(=#1h0~-s*ACsUo_Zc{(5I;!T zo1uc|Ha)8SUrfo>E${sK^STkiQNHh0Y<)Y@){gszmrd-?Y3u&rXQ;Qf^*jT~L;9%L zyR;A(c8>qWVVCq3m8nU|>8Ao%Hu<#S!$c|$oGFQ;kW`%{F|Z+&UtT`{&t&9kR6*j} z`FC9Q3v1rQW=}) zFQ2azOl=qnD2&3RY)q-DnRpshq6Ml|#+@;kr5tdbph4a)|79Nf8(TH{LoAMv7 zx1HF8N1wd_;?H+-f;*Ae2fL{hfq(#eo>YzjiJreo$q@D+YOBjen|h)0aLBN11c?Zh zsev;9ZC`?R>4xnFLi~;rLFYln3asZltM;xo98OVa=)W~@I8_dZXh5DCIHItiQTp;FZDErjw3JG5lYO{SAT;p{owwLLB)4*ae7U2&SCJ3e`6t_ z^8Gkp2j1)9``P1BSXKzCI)lY20m6X@Hk1=USVxOHD1fkOZVIa6g0)U;yLSoO2FyCf zy1M%zTvP2^ooqKolLcSCmsmRhv-%hlO1yHa|qWx4<5NRf~;qeL#wp3AH8}q<)_ELW2a7{5Se^GPPv1>x`Qyod4tJ+De=(e@aP1q4>9+Q0zT|NtQi9O0E~G2#3P{% zIUDG`9W|EV69W+pW*E$_8+MyP(TB3Bj)uI3tOlgWZq`#=CR|W3@}Gj!J2^*r|F9xV zXk^XjAOIsB7y0L64v5d0kd=hUp@+7>OU%RZ>Jw2N7;M82>r+~P6)0UoJ2?2|gPyw+BX-Nrb+>)Ex@O-YVjsPjCVLmkBmKsz4)QXF>cpRNk9~J& z*c~5f{SmJXPCY-YLA!`_KU6;!$Gl~Rclw+IYUhW%YXsCQ0?gVxTfx>+!@$1e+goWb zoPE3oKT2l^=_h`GuzhS~@?$0o*{#E)q#})??H~`Y#+{gb5~eRT+R3Clgn@{X`9VD0 z1R#j)pl3koknf1o!xm^I*ojHlE^EP1c-{g8RtL#!Hcr462>3VRi;%_a)&1qr%~Z{t zsQt3LKJ{sT*gq)_IwYaKfAQsY{3WYR`tCD_7s1Mm=S5~zgbmGx6PeJk%k0SDqC)ex zyDt3M>z>~L=0mACZT%K`;~B~r@sPxqQ$qI~=nA^uS5`=1IKgsu8DIPm>n`#`Xjban zI-NFR@XIQQPhR2=%#H5}v#7`VB~P7mOQh2KeIjoPM_(!zx$HGi!o2zvC4C$*pOd0g z8U^Qr!0VOZ%YedpSn5^%lG!vdZ%;MUH8?@Nv5H8v8Qm1r$M}dM+`1#$a5QekY2t=c25TSJ(kO_PdKR?8&p6YphpN+6&Cjc77!DCE za|i3S7Tn7S-5@CF@OFw!lLZl#;wfofeGPJ|ItzXzeWB{3XkJ|Mn}gJ-VuMwdY*(#= z?IL^Ip5^V*#^TAqkD{>@L|9RRGhx;^ko8(Tnp=LQUw%?b z{ItRgEf<0?|B+rKR zuJxucdo}sDI?|j%=yQQ)pv1vj?^<>DRG4jRZMH#gO??d{sJ3hh4if3D^jjt|dd?o) z`gib<3z;^k$S|*iXz*2!te81q?#|&KU(Ju#qKh;cgkbft=~vTHV9mo-ijci3&qMLz zd4C#+bBG`WPY0t@_eb0fG1@w}?jRTtalR4m$e+u>1x9BtPINt-car-nT**BT9u$Jz zc9E%5_hN=YCpP^<-K3!?0aS@V!MmPPHOTd|1^boG~mU`bJz}7HGLF4Dy-%482~T_23bCC|U(~K}WH@f@E%sk0IZl&E8p}gfy&@v;9Zx7l;%I*f&r_|G> z;YrwS+KSUBLhrF6HBLI5|E*1X%pF7;x-U6b@MQnuJUdg>I*WtFy@wns^@CZP{&ss{ z)8P_;XJ9Qg;`3tJq)^5T(fxrnrm03-SV8Q8U9{j4 zL)U^6^3OC0=ozS`heVd;*Jp z>nag)6|4$-7B_NumDyF1qW!Ptl3aJQs_IQFI&EAk_k|V}N+iqsRW4UeQNn50S<2&7 z%^IwCEjKoxG0^bw@LN55d^HYU_PT8AW02Ae`J9k#^`t1RkW-hv4h2t<@3aq$vuUo}o8`Ze%BrT4TuhdVk<6Mq3bKYSnC(WKGi@x0~K zdD|&thlfl~wJDT4j~v?`_|O3j?R9t?lmeQ_UUVEbk9ypzj-7iI7v3o4UN z8_?~3cu8ci_GJEEdVR6@QCm4FQA6G$5j+I_2(ZFJ^P~IW&2w@}?5<#Jo@UlNdkxPU%i7=s_kfO#` z()_t(4@A>%D8{soNyB3B9V2vkT8g(>(c;4frEmsB0OTU9Bp6BWT@D=p19SCJNNZr8 z3{KW*ida>u*bHzJgb{{-!-6_W;3*bPNGC^6+=_@>$d>SZ1|QGt#IDXl3W5Q9tD4$w zG4a86A?)I4E?c)MT;B=mK&7NSrhtq2BOvE4tReZ%TLt+`H} zAV#feue={M^DHZh zb3XC~zj`RGcPFam&+xKNtQ>#7b30+hPAPgA<-7o20Qi+Yq=C8R-&qu7OyPPH^t-}@&R^$P-PR+ zj^K(rU(wZZ8SJ*k1+uC%x59R^riop06(^YfO%FS?QrPa4Cg*q|U><^QVTK|cbS^k> zAabWhTOXu$Sq;b>`zodWDJ}WBoSuXfn&U1!$-m$akOpB!#(_~Y^CmkkO;IAVR%;&4 zca3|xN1*}9+ZmfTu4BTMYiNOO-yP&4?~>-1rDR&Z>U~88wfE4*;XMtvfXIJTZ6^Z-P4hKgK5^9cnr4O2IQm5lp`l|z~dd=}ut z0Nj-3$F_Kih#90VI2~X!(m#qsf*A#%n8|MoN5AaSGN^P3D0j=* zr6Kgoqy4IP$9Ez<*rN0(UuRJ$#8;EBtTj37CK!kOdR)*sdjw`al(^wbOkk&U#o0R1N^FV{IB#)YOYX^hziJ=@6@rj2nuTQxnRe&X3q9BUhu$j zqkq6`advz!tCvh1>;$>)!`XAf>{vz?^?7fiyKC; z!HeOpMxNVJlsuVkT>zoB^r|=Qy0CTk*2}R7!i5BJ`|}hfN`(centI^gk^I8N+n*kp zR+of6P)x^QzB{wU#m=ob`Mw@t4wZ5`y`8y~g+oOky=OcHqsKw}Q5Zez zl$tW-=9~E=2h!?TyF?u61|)q93rCM^P?O;sT^krg1-{@?lVuR2WVPn%7#Bsr)Ca2} zQa*Anh~NgHn19O3$FruS6im!Iopm>==8b}IHhg_8kY$q%5Kg^yppv)RIc?Os@2Mw*OJUgb^*;g`2z;ugCUgv;XtjLM^sw6M+;4P{>3h zlPnBwB9vv+fl=~^2(luy;p@FWcA>Uy%~Xq;jFmJ<1(yzu7(^Gpy2;_-)<<{SX|xU? zQ40MLq4$nJnQSZx3$(OAeY621dXBcfo{7LN7rE~16t|%m7qmXhP=(;+u)I#H=9FeD z6{W4GVr==f82=Iw4~W$ErhZ3}<12=>XN@ORyEVCtye!>r#2(#7Xwzcbql0SdFMEA% zL{=PZiZU`gBf@T$>wN(AAtx3{U(#jZ`+80wC-4wlj2Reub{n{nMjAr4mQmIceUTVU zOcNC2TJyxqiQc?7<2%)rNDG zH}-^n+K1!$4`rug|1E}f7h1>~ z++^pmXn+=ZFA@uLA6e5pQ?WC&w{t!jw#j_i_(sxaA;}xk1Wvoo&$H7CHo5F2U6?aK@D)%tPQ2GXSjGtZQP1Jml=)PDR`PMF!ei6_+l*L+j-38q!ys4 z`{iKMjeC`T=oW)e=hJ>7b30D4y&%Oqq4rIC(D`RH{uRY5;bhy4Is?&mlN4zuoGW)`-UFcHItvI;9jWR>-*ibdE&wu(BNE;PG zZ?HB%4`gp^lsyaETW{=>)OD|`AX6^*u(r0dws(Tg!%G*rQnK^-4wX8ldP!M@5POB? zMJ>4#yG|##{HeFux}CV~x`qovpQu_srg~WUGM{s`#|?dtRwD#fO~Da^9R z!XQe$A!J&9R-FcV9Qd-u@Ah~%%gjjhbEYW@nr(m(fK_-pj<2Yib=hm%WQc$? zbnO*Rj-LwoB&uZcl)IIrO(J-ml8S&PnIqp`54|S!?{qT2d0rQZ>!#-T>=23zp%9w7 zi4nK%f#764Zb5^lM(`@{k<*B4iSp+k>`uu*_c>Lp zs4&YAuq@cf4x$h#yfwr?$|DAd=nfHz z)yYvpuclVMt4i{A)J*e#rW31=apkW1PU{tBZk7q24xba}=mOk|n0AEOyMdp{uSSZ; z#8R+?jF|EuF#IXU5EBe&J}YigwL4p`yUmMU?L?@bBE$hW{`5a~qnhr-Tbnwwx*RMS zLL=UE21J0-CFDvaGerqiQ4V`|+SOnHL+vuX=nlb6sokXo06K~!J9o-uJ1RyQVx*tv zLs-~bKNLy5Gh6&D)ndJwYW0ugbKkPIw>G?jsVlyfrWw?rPlyKUAGZJfnlD3|D;}v7 zA^GE-l@EL@fk;FBM2LZbyBGe657R-355x@CT@N~8kfZ^yB|cdKc?Y!R;}6Tw9-#JS zhnt(*pe5plV0S>Pv@KP4z>>fi~FjyB%PEX-7AH0F$%qgKK!&>WNF6jd-<7>w@wM(aDyw*kDP z?Wg2c3c$cnfviVX#$Y}C`(dpRy}1d~jnfIQz@JEMJi(n;<3FTj47Q6w=b&TXc$DHY zz!d^bSdFVjst)INBVzkhN*ud_bqYDJ$f(a68`I^$lXcG#e5(6DQz-`fKC^-Z3rSzf zG<4*P%{#aNGd~t1NsdO%km>`IZ{pg^hSjzGWXS$qG0x|Tz_+sRtW}}P<`{z(#1kkxZE{kSo|IWUDnfr_h$c5lqt{|9L=5ofUkBt+WLaQ{o$Dz= zGvSb=I8=J+$!kALR{o&^)F8MK@`JlHPm#S9u_E9}m((pdY*Th@`a|3qW9_e< znCL9^$n}D4klqFDT;HU8;msQ#>NSK}ZFbW=XnTFgmV2{gwN@r^ zotmt6Pwj@t5a*_AlZQS%b+Mwk>#$hW@6WGPycW6Q(B>GQBWZAH-R}D=d*QYt_TG6X zNuO?Vpr)6j<%^Tu`RsgqG=X1j+y96c-#$Y0!UGC>8M0QP+vG*kn4@m?mG^5V`$X-F ze!gd3(*ZYWlk&-NS``D-Pf6>|&J+%fJt`;_qL;0(MTBcShMN?S0@(30_(_t_IpekFtP^B;MC5W`)RdpB8f7>v9FlZHN)CV!&fv=+Nj zIT&In$S^r#Dmo}2Nq51#kkwihi!LbfrK@ePn#IRvJU-29Ilt_7~PD=nLh%KdL`M#aI@UqrP6UMg%>99!g${ zUI}6iIB;$0#e-S?Jo3vmo6p*>8hz^akVE$O1R4jQl9#)yX_uVak~~ODUT^^8#_>hN z9q+v6jxY6d5q8{yV1^h7DQ`hmsY)%9lF~^kz@Q2wWeD)43?eFwQAEe#mzbO2Z`vsA zI?&l`+o-0|%TLX>oD#?Itmd*@?te2;i_7>X@}9$k_>A>T9L(oJ^;V`I{| zU*+(8t|ur#%zT+;io9Y{3qjAf5jX;+1-CIvDC>eEhd_N6pF=?mk%Dgw598M1!{b(; z>|8clwD1?N@1BlVO~UF5;ZqW|Ll`4BU)la~$1ck|+OZl?ayH63LcAYa>IbGbwVNEipBq#<~pG&z^@|BOJ`0*oG;B%A`sOawTV6= z%=6D4@P6zC2*=v$B&1F{Xq;< z>Kd&%LOMKK5D{N{WYl_PFvI3Kf9}}}#v&NM@KD*dUsZ0(jvPE({K>S6nTICo)UUE1 zAo7N%es`zn?;Tt_-6aPy_ZqC;X#T^LbZt7V>{y3Ol(N6Shxyy-Yug>&Ai7 zX?aJaSB|W3D{`HGK&10v(K_K(Fc|6H6Y7o?+y zfx#$Dz0<&>E6`1@cKcFcS2YlrhgrQ z{cg7x@GvIGGgTXR!e&X@zgdfJ{>VKt^SFCQu?tRBX=XNm|kuEq5{yLEV z_DQbO5ni}st-WDCPqMF-;0dQxXXmMLnq>yBFqZT~>F~qCe_ao!#bVy zeYg1kFT66-3j7D5?Mv!+$^V|9ltSVSgenyBKGM;YUl5N|V?U)!D0Fv4d*0R){<0mH z>kD>_Z(F^kv4tnwL=3>PmqGCD%h!PS1xX^R_!i1X_G3{PXse{l{^-ms~Fi^_3Jay}~_+%~<9+ zlRfcF?%kSN|J=A~^d!!9uTARzxCjf+Q9!2i@5hb+zClLa8sh^iJRMtZ3KObr3pTkG z3h`=UI-t5gd?MEpO*VxlK)03Fey_H!?%ZmTv-mH(2}qk$JSI=k;P^KMwdtjPq|3u_ zn_L?00dSe5sja-4Yex!enhl|&1B9`LwpydEHecfwGLnS0 zV2np{c6$26W2Js88l!(%Zf+j;8X2CrS~M{1(UMsl3{Z~1zz^xnw<_LVChJ`eIx;4{ zIO}-og+ZSA%EmBbWjg#kodPcQzi-Drl*OM<<4Npn2qgO;*#dVSGkcc~9c*-*zQL+=HW;O1@*yAlT$<%j@E1Eg;K zp!ZBJb4ktPkE6eSufw``yH%N>R8ygeL8jYf`~m_L z0bLO)3^JBXj3hcBvF|&ALg=AMh293+c! z<5W6Bw#E`|C(1URW?ymrl=$i2u=@#JJ8k6+t=4NM#xAmZUMkh-x_Gb~4#dE!yhe z0@hMN2`hxedMe0}ru;EytVc3IL&onQ&3(yoq_p|Hx4r!kDM~f%2AS5gl$J`^^oL0^ zvwIbb^4TiEW&rId?4IV$L#03jri)D5^&3kHA0kCa(CqbslsG>yc91x^iS$OF(&&xP zg|5;*&*Y2_)&NtPlvSw`RTFR&+8A(OE_qSvN7_po#WGb)*l5l4l6jHu22zmDh6ib} z5Tl`1J!Ae(@K+?~26lGZQs4zX;=VTSK>_<0NEEgpg4vp>WhU~G4u+j^_9#?oW!>Gr z!Y!hUUVEN~k8yBvnECpyR(JDUPa~$@hL%AL#c3^vbF>pW*{>^Di%#hHoIe!bCbLOX zC&Bpz%I8r{X>hyuTf1E17AFD;;wqNx2ShJZvHS?LOfwEna>rkj@yG$uDujBA^g2z5 zC-1Fp;t)j|Wx9CxYkp>3T(dW`8au?u)WI=gs_&x0me-5YtRqOrLTd+|5n`FVMsNmh zQvfTCFQJ#B|N{7WujI&#B2QHaLoB|Yn+BZ*Vz+g|^?}a9 zytx5t&n%iv;U5{34o>^}M=rO{JHAan-{q@O1+-ZV@mgezLqJ%uPt?azVWocI zF=ncP(Wf#5jMj|V5#KeHa@E0M+d}jdljiG%J%}mEIM!)OjdA46B6~X`AF+|@pn}Q4 z_0x(DZdGrGPj3JL>SV1r=j~loUk6^*mrKa{ao5t#^DsN>JsHeWCF|~RcbKUL(1twH znyM6xO&Il7!-q$*FiV5Ede7WTE$(gda*R_u|BzeVI=(HuzzBLLlgBSC1Es%0khiqGxtT;4dyAwFhd#eKI04Xg>F#|KyW94`<5ikzB!Co+jG>h!4 zw|6l9k&<;_=fQDNET?CcC?)Qz;dZuDxSU*v$nRmM2~VhpvSc&Pa#kall=Dq#8G`$8 zO_NU;Ras0LnP%UbfiCiLD8%tNBJPBsRE`yoBF!AaZFhXPkHIUphDOzO63!uwu_XEQ z4e>(4{AZ1I6GQ9_)=%;LYDVA*Al5_meHmV1 z2;^lwKBQ9*b!adEMW*=DB5b<$g^8~2sTZKc$63a_n--y_Y3RUmKi_F@AQR$iWzVjk?U`I$hJ^ozhXpEJU4oq`P@X|81CtX5CGU zEJl5ZSOzNHP;punfFlTekSe(r8D?4FKw+SKj@P)y+u}SfuAAg1!O2JQY$6U=c)4&S zL5kAs(|ZS%2ZR|0sKzLp4A&b=CXN-$q`dKS9Ai8ch!IBK1e)OH)aM`y=d&_QR0G3S z_w@2RJsY=259v#kPY0P`3$n^7

i8C{CYsJK2%-s3dG}Bz=)8kOzA_B7C;Y$*DCt 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 + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/python/pyarmnn/scripts/generate_docs.py b/python/pyarmnn/scripts/generate_docs.py new file mode 100644 index 0000000..66eff6d --- /dev/null +++ b/python/pyarmnn/scripts/generate_docs.py @@ -0,0 +1,50 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +"""Generate PyArmNN documentation.""" +import os +import tarfile + +import pyarmnn as ann +import shutil + +from typing import List, Union + +from pdoc.cli import main + + +def __copy_file_to_dir(file_paths: Union[List[str], str], target_dir_path: str): + file_paths = [] + file_paths + + if not (os.path.exists(target_dir_path) and os.path.isdir(target_dir_path)): + os.makedirs(target_dir_path) + + for file_path in file_paths: + if not (os.path.exists(file_path) and os.path.isfile(file_path)): + raise RuntimeError('Not a file: {}'.format(file_path)) + + file_name = os.path.basename(file_path) + shutil.copyfile(file_path, os.path.join(str(target_dir_path), file_name)) + + +def copy_doc_images(): + __copy_file_to_dir(file_paths=['./images/pyarmnn.png'], + target_dir_path='docs/pyarmnn/images') + + +def archive_docs(path, version): + + output_filename = f'pyarmnn_docs-{version}.tar' + + with tarfile.open(output_filename, "w") as tar: + tar.add(path) + + +if __name__ == "__main__": + with open('./README.md', 'r') as readme_file: + top_level_pyarmnn_doc = ''.join(readme_file.readlines()) + ann.__doc__ = top_level_pyarmnn_doc + + main() + + copy_doc_images() + archive_docs('./docs', ann.__version__) diff --git a/python/pyarmnn/setup.py b/python/pyarmnn/setup.py new file mode 100644 index 0000000..5f81088 --- /dev/null +++ b/python/pyarmnn/setup.py @@ -0,0 +1,252 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import logging +import os +import sys +from functools import lru_cache +from pathlib import Path +from itertools import chain + +from setuptools import setup +from distutils.core import Extension +from setuptools.command.build_py import build_py +from setuptools.command.build_ext import build_ext + +logger = logging.Logger(__name__) + +__version__ = None +__arm_ml_version__ = None + + +def check_armnn_version(*args): + pass + + +exec(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src', 'pyarmnn', '_version.py')).read()) + + +class ExtensionPriorityBuilder(build_py): + """ + Runs extension builder before other stages. Otherwise generated files are not included to the distribution. + """ + + def run(self): + self.run_command('build_ext') + return super().run() + + +class ArmnnVersionCheckerExtBuilder(build_ext): + + def __init__(self, dist): + super().__init__(dist) + self.failed_ext = [] + + def build_extension(self, ext): + try: + super().build_extension(ext) + except Exception as err: + self.failed_ext.append(ext) + logger.warning('Failed to build extension %s. \n %s', ext.name, str(err)) + + if ext.name == 'pyarmnn._generated._pyarmnn_version': + sys.path.append(os.path.abspath(os.path.join(self.build_lib, str(Path(ext._file_name).parent)))) + from _pyarmnn_version import GetVersion + check_armnn_version(GetVersion(), __arm_ml_version__) + + def copy_extensions_to_source(self): + + for ext in self.failed_ext: + self.extensions.remove(ext) + super().copy_extensions_to_source() + + +def linux_gcc_lib_search(): + """ + Calls the `gcc` to get linker default system paths. + Returns: + list of paths + """ + cmd = 'gcc --print-search-dirs | grep libraries' + cmd_res = os.popen(cmd).read() + cmd_res = cmd_res.split('=') + if len(cmd_res) > 1: + return tuple(cmd_res[1].split(':')) + return None + + +def find_includes(armnn_include_env: str = 'ARMNN_INCLUDE'): + armnn_include_path = os.getenv(armnn_include_env, '') + return [armnn_include_path] if armnn_include_path else ['/usr/local/include', '/usr/include'] + + +@lru_cache(maxsize=1) +def find_armnn(lib_name: str, + optional: bool = False, + armnn_libs_env: str = 'ARMNN_LIB', + default_lib_search: tuple = linux_gcc_lib_search()): + """ + Searches for ArmNN installation on the local machine. + + Args: + lib_name: lib name to find + optional: Do not fail if optional. Default is False - fail if library was not found. + armnn_include_env: custom environment variable pointing to ArmNN headers, default is 'ARMNN_INCLUDE' + armnn_libs_env: custom environment variable pointing to ArmNN libraries location, default is 'ARMNN_LIBS' + default_lib_search: list of paths to search for ArmNN if not found within path provided by 'ARMNN_LIBS' + env variable + + Returns: + tuple containing name of the armnn libs, paths to the libs + """ + + armnn_lib_path = os.getenv(armnn_libs_env, "") + + lib_search = [armnn_lib_path] if armnn_lib_path else default_lib_search + + armnn_libs = dict(map(lambda path: (':{}'.format(path.name), path), + chain.from_iterable(map(lambda lib_path: Path(lib_path).glob(lib_name), + lib_search)))) + if not optional and len(armnn_libs) == 0: + raise RuntimeError("""ArmNN library {} was not found in {}. Please install ArmNN to one of the standard + locations or set correct ARMNN_INCLUDE and ARMNN_LIB env variables.""".format(lib_name, + lib_search)) + + # gives back tuple of names of the libs, set of unique libs locations and includes. + return list(armnn_libs.keys()), list(set( + map(lambda path: str(path.absolute().parent), armnn_libs.values()))) + + +class LazyArmnnFinderExtension(Extension): + """ + Derived from `Extension` this class adds ArmNN libraries search on the user's machine. + SWIG options and compilation flags are updated with relevant ArmNN libraries files locations (-L) and headers (-I). + + Search for ArmNN is executed only when attributes include_dirs, library_dirs, runtime_library_dirs, libraries or + swig_opts are queried. + + """ + + def __init__(self, name, sources, armnn_libs, include_dirs=None, define_macros=None, undef_macros=None, + library_dirs=None, + libraries=None, runtime_library_dirs=None, extra_objects=None, extra_compile_args=None, + extra_link_args=None, export_symbols=None, language=None, optional=None, **kw): + self._include_dirs = None + self._library_dirs = None + self._runtime_library_dirs = None + self._armnn_libs = armnn_libs + # self.__swig_opts = None + super().__init__(name, sources, include_dirs, define_macros, undef_macros, library_dirs, libraries, + runtime_library_dirs, extra_objects, extra_compile_args, extra_link_args, export_symbols, + language, optional, **kw) + + @property + def include_dirs(self): + return self._include_dirs + find_includes() + + @include_dirs.setter + def include_dirs(self, include_dirs): + self._include_dirs = include_dirs + + @property + def library_dirs(self): + library_dirs = self._library_dirs + for lib in self._armnn_libs: + _, lib_path = find_armnn(lib) + library_dirs = library_dirs + lib_path + + return library_dirs + + @library_dirs.setter + def library_dirs(self, library_dirs): + self._library_dirs = library_dirs + + @property + def runtime_library_dirs(self): + library_dirs = self._runtime_library_dirs + for lib in self._armnn_libs: + _, lib_path = find_armnn(lib) + library_dirs = library_dirs + lib_path + + return library_dirs + + @runtime_library_dirs.setter + def runtime_library_dirs(self, runtime_library_dirs): + self._runtime_library_dirs = runtime_library_dirs + + @property + def libraries(self): + libraries = self._libraries + for lib in self._armnn_libs: + lib_names, _ = find_armnn(lib) + libraries = libraries + lib_names + + return libraries + + @libraries.setter + def libraries(self, libraries): + self._libraries = libraries + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.name == other.name + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return self.name.__hash__() + +if __name__ == '__main__': + # mandatory extensions + pyarmnn_module = LazyArmnnFinderExtension('pyarmnn._generated._pyarmnn', + sources=['src/pyarmnn/_generated/armnn_wrap.cpp'], + extra_compile_args=['-std=c++14'], + language='c++', + armnn_libs=['libarmnn.so'] + ) + pyarmnn_v_module = LazyArmnnFinderExtension('pyarmnn._generated._pyarmnn_version', + sources=['src/pyarmnn/_generated/armnn_version_wrap.cpp'], + extra_compile_args=['-std=c++14'], + language='c++', + armnn_libs=['libarmnn.so'] + ) + extensions_to_build = [pyarmnn_v_module, pyarmnn_module] + + + # optional extensions + def add_parsers_ext(name: str, ext_list: list): + pyarmnn_optional_module = LazyArmnnFinderExtension('pyarmnn._generated._pyarmnn_{}'.format(name.lower()), + sources=['src/pyarmnn/_generated/armnn_{}_wrap.cpp'.format( + name.lower())], + extra_compile_args=['-std=c++14'], + language='c++', + armnn_libs=['libarmnn.so', 'libarmnn{}.so'.format(name)] + ) + ext_list.append(pyarmnn_optional_module) + + + add_parsers_ext('CaffeParser', extensions_to_build) + add_parsers_ext('OnnxParser', extensions_to_build) + add_parsers_ext('TfParser', extensions_to_build) + add_parsers_ext('TfLiteParser', extensions_to_build) + + setup( + name='pyarmnn', + version=__version__, + author='Arm ltd', + author_email='support@linaro.org', + description='Arm NN python wrapper', + url='https://www.arm.com', + license='MIT', + package_dir={'': 'src'}, + packages=[ + 'pyarmnn', + 'pyarmnn._generated', + 'pyarmnn._quantization', + 'pyarmnn._tensor', + 'pyarmnn._utilities' + ], + python_requires='>=3.5', + install_requires=['numpy'], + cmdclass={'build_py': ExtensionPriorityBuilder, 'build_ext': ArmnnVersionCheckerExtBuilder}, + ext_modules=extensions_to_build + ) diff --git a/python/pyarmnn/src/pyarmnn/__init__.py b/python/pyarmnn/src/pyarmnn/__init__.py new file mode 100644 index 0000000..265880f --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/__init__.py @@ -0,0 +1,143 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import inspect +import sys +import logging + +from ._generated.pyarmnn_version import GetVersion, GetMajorVersion, GetMinorVersion + +# Parsers +try: + from ._generated.pyarmnn_caffeparser import ICaffeParser +except ImportError as err: + logger = logging.getLogger(__name__) + message = "Your ArmNN library instance does not support Caffe models parser functionality. " + logger.warning("%s Skipped ICaffeParser import.", message) + logger.debug(str(err)) + + + def ICaffeParser(): + """In case people try importing without having Arm NN built with this parser.""" + raise RuntimeError(message) + +try: + from ._generated.pyarmnn_onnxparser import IOnnxParser +except ImportError as err: + logger = logging.getLogger(__name__) + message = "Your ArmNN library instance does not support Onnx models parser functionality. " + logger.warning("%s Skipped IOnnxParser import.", message) + logger.debug(str(err)) + + + def IOnnxParser(): + """In case people try importing without having Arm NN built with this parser.""" + raise RuntimeError(message) + +try: + from ._generated.pyarmnn_tfparser import ITfParser +except ImportError as err: + logger = logging.getLogger(__name__) + message = "Your ArmNN library instance does not support TF models parser functionality. " + logger.warning("%s Skipped ITfParser import.", message) + logger.debug(str(err)) + + + def ITfParser(): + """In case people try importing without having Arm NN built with this parser.""" + raise RuntimeError(message) + +try: + from ._generated.pyarmnn_tfliteparser import ITfLiteParser +except ImportError as err: + logger = logging.getLogger(__name__) + message = "Your ArmNN library instance does not support TF lite models parser functionality. " + logger.warning("%s Skipped ITfLiteParser import.", message) + logger.debug(str(err)) + + + def ITfLiteParser(): + """In case people try importing without having Arm NN built with this parser.""" + raise RuntimeError(message) + +# Network +from ._generated.pyarmnn import Optimize, OptimizerOptions, IOptimizedNetwork, IInputSlot, \ + IOutputSlot, IConnectableLayer, INetwork + +# Backend +from ._generated.pyarmnn import BackendId +from ._generated.pyarmnn import IDeviceSpec + +# Tensors +from ._generated.pyarmnn import TensorInfo, TensorShape + +# Runtime +from ._generated.pyarmnn import IRuntime, CreationOptions, INetworkProperties + +# Profiler +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 +from ._generated.pyarmnn import DataLayout_NCHW, DataLayout_NHWC + +from ._generated.pyarmnn import ActivationFunction_Abs, ActivationFunction_BoundedReLu, ActivationFunction_LeakyReLu, \ + ActivationFunction_Linear, ActivationFunction_ReLu, ActivationFunction_Sigmoid, ActivationFunction_SoftReLu, \ + ActivationFunction_Sqrt, ActivationFunction_Square, ActivationFunction_TanH, ActivationDescriptor +from ._generated.pyarmnn import ArgMinMaxFunction_Max, ArgMinMaxFunction_Min, ArgMinMaxDescriptor +from ._generated.pyarmnn import BatchNormalizationDescriptor, BatchToSpaceNdDescriptor +from ._generated.pyarmnn import ComparisonDescriptor, ComparisonOperation_Equal, ComparisonOperation_Greater, \ + ComparisonOperation_GreaterOrEqual, ComparisonOperation_Less, \ + ComparisonOperation_LessOrEqual, ComparisonOperation_NotEqual +from ._generated.pyarmnn import UnaryOperation_Abs, UnaryOperation_Exp, UnaryOperation_Sqrt, UnaryOperation_Rsqrt, \ + UnaryOperation_Neg, ElementwiseUnaryDescriptor +from ._generated.pyarmnn import Convolution2dDescriptor, DepthToSpaceDescriptor, DepthwiseConvolution2dDescriptor, \ + DetectionPostProcessDescriptor, FakeQuantizationDescriptor, FullyConnectedDescriptor, \ + InstanceNormalizationDescriptor, LstmDescriptor, L2NormalizationDescriptor, MeanDescriptor +from ._generated.pyarmnn import NormalizationAlgorithmChannel_Across, NormalizationAlgorithmChannel_Within, \ + NormalizationAlgorithmMethod_LocalBrightness, NormalizationAlgorithmMethod_LocalContrast, NormalizationDescriptor +from ._generated.pyarmnn import PadDescriptor +from ._generated.pyarmnn import PermutationVector, PermuteDescriptor +from ._generated.pyarmnn import OutputShapeRounding_Ceiling, OutputShapeRounding_Floor, \ + PaddingMethod_Exclude, PaddingMethod_IgnoreValue, PoolingAlgorithm_Average, PoolingAlgorithm_L2, \ + PoolingAlgorithm_Max, Pooling2dDescriptor +from ._generated.pyarmnn import ResizeMethod_Bilinear, ResizeMethod_NearestNeighbor, ResizeDescriptor, \ + ReshapeDescriptor, SliceDescriptor, SpaceToBatchNdDescriptor, SpaceToDepthDescriptor, StandInDescriptor, \ + StackDescriptor, StridedSliceDescriptor, SoftmaxDescriptor, TransposeConvolution2dDescriptor, \ + SplitterDescriptor +from ._generated.pyarmnn import ConcatDescriptor, CreateDescriptorForConcatenation + +from ._generated.pyarmnn import LstmInputParams, QuantizedLstmInputParams + +# Public API +# Quantization +from ._quantization.quantize_and_dequantize import quantize, dequantize + +# Tensor +from ._tensor.tensor import Tensor +from ._tensor.const_tensor import ConstTensor +from ._tensor.workload_tensors import make_input_tensors, make_output_tensors, workload_tensors_to_ndarray + +# Utilities +from ._utilities.profiling_helper import ProfilerData, get_profiling_data + +from ._version import __version__, __arm_ml_version__ + +ARMNN_VERSION = GetVersion() + + +def __check_version(): + from ._version import check_armnn_version + check_armnn_version(ARMNN_VERSION) + + +__check_version() + +__all__ = [] + +__private_api_names = ['__check_version'] + +for name, obj in inspect.getmembers(sys.modules[__name__]): + if inspect.isclass(obj) or inspect.isfunction(obj): + if name not in __private_api_names: + __all__.append(name) diff --git a/python/pyarmnn/src/pyarmnn/_generated/__init__.py b/python/pyarmnn/src/pyarmnn/_generated/__init__.py new file mode 100644 index 0000000..5ffb3e0 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_generated/__init__.py @@ -0,0 +1,2 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT diff --git a/python/pyarmnn/src/pyarmnn/_quantization/__init__.py b/python/pyarmnn/src/pyarmnn/_quantization/__init__.py new file mode 100644 index 0000000..e6b021b --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_quantization/__init__.py @@ -0,0 +1,4 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT + +from .quantize_and_dequantize import quantize, dequantize diff --git a/python/pyarmnn/src/pyarmnn/_quantization/quantize_and_dequantize.py b/python/pyarmnn/src/pyarmnn/_quantization/quantize_and_dequantize.py new file mode 100644 index 0000000..f276ea6 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_quantization/quantize_and_dequantize.py @@ -0,0 +1,74 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +""" +This file contains functions relating to quantizing and dequantizing values. +""" +from .._generated.pyarmnn import Quantize_uint8_t, Quantize_int8_t, Quantize_int16_t, Quantize_int32_t, \ + Dequantize_uint8_t, Dequantize_int8_t, Dequantize_int16_t, Dequantize_int32_t + +__DTYPE_TO_QUANTIZE_FUNCTION = { + 'uint8': Quantize_uint8_t, + 'int8': Quantize_int8_t, + 'int16': Quantize_int16_t, + 'int32': Quantize_int32_t + } + +__DTYPE_TO_DEQUANTIZE_FUNCTION = { + 'uint8': ((0, 255), Dequantize_uint8_t), + 'int8': ((-128, 127), Dequantize_int8_t), + 'int16': ((-32768, 32767), Dequantize_int16_t), + 'int32': ((-2147483648, 2147483647), Dequantize_int32_t) + } + + +def quantize(value: float, scale: float, offset: int, target_dtype: str) -> int: + """Quantize the given value to the given target datatype using Arm NN. + + This function can be used to convert a 32-bit floating point value into 8/16/32-bit signed + integer or 8-bit unsigned integer values. + + Args: + value (float): The value to be quantized. + scale (float): A numeric constant that the value is multiplied by. + offset (int): A 'zero-point' used to 'shift' the integer range. + target_dtype (str): The target data type. Supported values: 'unit8', 'int8', 'int16', 'int32'. + + Returns: + int: A quantized 8-bit unsigned integer value or 8/16/32-bit signed integer value. + """ + + if target_dtype not in __DTYPE_TO_QUANTIZE_FUNCTION: + raise ValueError("""Unexpected target datatype {} given. + Armnn currently supports quantization to {} values.""".format(target_dtype, list(__DTYPE_TO_QUANTIZE_FUNCTION.keys()))) + + return __DTYPE_TO_QUANTIZE_FUNCTION[target_dtype](float(value), scale, offset) + + +def dequantize(value: int, scale: float, offset: float, from_dtype: str) -> float: + """Dequantize the given value from the given datatype using Arm NN. + + This function can be used to convert an 8-bit unsigned integer value or 8/16/32-bit signed + integer value into a 32-bit floating point value. Typically used when decoding an + output value from an output tensor on a quantized model. + + Args: + value (int): The value to be dequantized. Value could be numpy numeric data type. + scale (float): A numeric constant that the value is multiplied by. + offset (float): A 'zero-point' used to 'shift' the integer range. + from_dtype (str): The data type 'value' represents. Supported values: 'unit8', 'int8', 'int16', 'int32'. + + Returns: + float: A dequantized 32-bit floating-point value. + """ + + # specifies which function to use with given datatype and the value range for that data type. + if from_dtype not in __DTYPE_TO_DEQUANTIZE_FUNCTION: + raise ValueError("""Unexpected value datatype {} given. + Armnn currently supports dequantization from {} values.""".format(from_dtype, list(__DTYPE_TO_DEQUANTIZE_FUNCTION.keys()))) + + input_range = __DTYPE_TO_DEQUANTIZE_FUNCTION[from_dtype][0] + + if not input_range[0] <= value <= input_range[1]: + raise ValueError('Value is not within range of the given datatype {}'.format(from_dtype)) + + return __DTYPE_TO_DEQUANTIZE_FUNCTION[from_dtype][1](int(value), scale, offset) diff --git a/python/pyarmnn/src/pyarmnn/_tensor/__init__.py b/python/pyarmnn/src/pyarmnn/_tensor/__init__.py new file mode 100644 index 0000000..4db192d --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_tensor/__init__.py @@ -0,0 +1,6 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT + +from .const_tensor import ConstTensor +from .tensor import Tensor +from .workload_tensors import make_input_tensors, make_output_tensors, workload_tensors_to_ndarray diff --git a/python/pyarmnn/src/pyarmnn/_tensor/const_tensor.py b/python/pyarmnn/src/pyarmnn/_tensor/const_tensor.py new file mode 100644 index 0000000..94995bd --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_tensor/const_tensor.py @@ -0,0 +1,171 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +""" +This file contains the custom python implementation for Arm NN Const Tensor objects. +""" +import numpy as np + +from .._generated.pyarmnn import DataType_QAsymmU8, DataType_QSymmS8, DataType_QSymmS16, DataType_Signed32, \ + DataType_QAsymmS8, DataType_Float32, DataType_Float16 +from .._generated.pyarmnn import ConstTensor as AnnConstTensor, TensorInfo, Tensor + + +class ConstTensor(AnnConstTensor): + """Creates a PyArmNN ConstTensor object. + + A ConstTensor is a Tensor with an immutable data store. Typically, a ConstTensor + is used to input data into a network when running inference. + + This class overrides the swig generated Tensor class. The aim of + this is to have an easy to use public API for the ConstTensor objects. + + """ + + def __init__(self, *args): + """ + Supported tensor data types: + `DataType_QAsymmU8`, + `DataType_QAsymmS8`, + `DataType_QSymmS16`, + `DataType_QSymmS8`, + `DataType_Signed32`, + `DataType_Float32`, + `DataType_Float16` + + Examples: + Create empty ConstTensor + >>> import pyarmnn as ann + >>> import numpy as np + >>> ann.ConstTensor() + + Create ConstTensor given tensor info and input data + >>> input_data = np.array(...) + >>> ann.ConstTensor(ann.TensorInfo(...), input_data) + + Create ConstTensor from another ConstTensor i.e. copy ConstTensor + >>> ann.ConstTensor(ann.ConstTensor()) + + Create ConstTensor from tensor + >>> ann.ConstTensor(ann.Tensor()) + + Args: + tensor (Tensor, optional): Create a ConstTensor from a Tensor. + const_tensor (ConstTensor, optional): Create a ConstTensor from a ConstTensor i.e. copy. + tensor_info (TensorInfo, optional): Tensor information. + input_data (ndarray): The numpy array will be transformed to a + buffer according to type returned by `TensorInfo.GetDataType`. + Input data values type must correspond to data type returned by + `TensorInfo.GetDataType`. + + Raises: + TypeError: Unsupported input data type. + ValueError: Unsupported tensor data type and incorrect input data size. + """ + self.__memory_area = None + + # TensorInfo as first argument and numpy array as second + if len(args) > 1 and isinstance(args[0], TensorInfo): + if isinstance(args[1], np.ndarray): + self.__create_memory_area(args[0].GetDataType(), args[0].GetNumBytes(), args[0].GetNumElements(), + args[1]) + super().__init__(args[0], self.__memory_area.data) + else: + raise TypeError('Data must be provided as a numpy array.') + + # copy constructor - reference to memory area is passed from copied const + # tensor and armnn's copy constructor is called + elif len(args) > 0 and isinstance(args[0], (ConstTensor, Tensor)): + self.__memory_area = args[0].get_memory_area() + super().__init__(args[0]) + + # empty tensor + elif len(args) == 0: + super().__init__() + + else: + raise ValueError('Incorrect number of arguments or type of arguments provided to create Const Tensor.') + + def __copy__(self) -> 'ConstTensor': + """ Make copy of a const tensor. + + Make const tensor copyable using the python copy operation. + + Note: + The tensor memory area is NOT copied. Instead, the new tensor maintains a + reference to the same memory area as the old tensor. + + Example: + Copy empty tensor + >>> from copy import copy + >>> import pyarmnn as ann + >>> tensor = ann.ConstTensor() + >>> copied_tensor = copy(tensor) + + Returns: + Tensor: a copy of the tensor object provided. + + """ + return ConstTensor(self) + + @staticmethod + def __check_size(data: np.ndarray, num_bytes: int, num_elements: int): + """ Check the size of the input data against the number of bytes provided by tensor info. + + Args: + data (ndarray): Input data. + num_bytes (int): Number of bytes required by tensor info. + num_elements: Number of elements required by tensor info. + + Raises: + ValueError: number of bytes in input data does not match tensor info. + + """ + size_in_bytes = data.nbytes + elements = data.size + + if size_in_bytes != num_bytes: + raise ValueError( + "ConstTensor requires {} bytes, {} provided. " + "Is your input array data type ({}) aligned with TensorInfo?".format(num_bytes, size_in_bytes, + data.dtype)) + if elements != num_elements: + raise ValueError("ConstTensor requires {} elements, {} provided.".format(num_elements, elements)) + + def __create_memory_area(self, data_type: int, num_bytes: int, num_elements: int, data: np.ndarray): + """ Create the memory area used by the tensor to output its results. + + Args: + data_type (int): The type of data that will be stored in the memory area. + See DataType_*. + num_bytes (int): Determines the size of the memory area that will be created. + num_elements (int): Determines number of elements in memory area. + data (ndarray): Input data as numpy array. + + """ + np_data_type_mapping = {DataType_QAsymmU8: np.uint8, + DataType_QAsymmS8: np.int8, + DataType_QSymmS8: np.int8, + DataType_Float32: np.float32, + DataType_QSymmS16: np.int16, + DataType_Signed32: np.int32, + DataType_Float16: np.float16} + + if data_type not in np_data_type_mapping: + raise ValueError("The data type provided for this Tensor is not supported: {}".format(data_type)) + + if np_data_type_mapping[data_type] != data.dtype: + raise TypeError("Expected data to have type {} for type {} but instead got numpy.{}".format(np_data_type_mapping[data_type], data_type, data.dtype)) + + self.__check_size(data, num_bytes, num_elements) + + self.__memory_area = data + self.__memory_area.flags.writeable = False + + def get_memory_area(self) -> np.ndarray: + """ Get values that are stored by the tensor. + + Returns: + ndarray: Tensor data (as numpy array). + + """ + return self.__memory_area diff --git a/python/pyarmnn/src/pyarmnn/_tensor/tensor.py b/python/pyarmnn/src/pyarmnn/_tensor/tensor.py new file mode 100644 index 0000000..9b9a1d3 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_tensor/tensor.py @@ -0,0 +1,126 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +""" +This file contains the custom python implementation for Arm NN Tensor objects. +""" +import numpy as np + +from .._generated.pyarmnn import Tensor as annTensor, TensorInfo, DataType_QAsymmU8, DataType_QSymmS8, \ + DataType_QAsymmS8, DataType_Float32, DataType_QSymmS16, DataType_Signed32, DataType_Float16 + + +class Tensor(annTensor): + """Creates a PyArmNN Tensor object. + + This class overrides the swig generated Tensor class. The aim of + this is to create an easy to use public api for the Tensor object. + + Memory is allocated and managed by this class, avoiding the need to manage + a separate memory area for the tensor compared to the swig generated api. + + """ + + def __init__(self, *args): + """ Create Tensor object. + + Supported tensor data types: + `DataType_QAsymmU8`, + `DataType_QAsymmS8`, + `DataType_QSymmS16`, + `DataType_QSymmS8`, + `DataType_Signed32`, + `DataType_Float32`, + `DataType_Float16` + + Examples: + Create an empty tensor + >>> import pyarmnn as ann + >>> ann.Tensor() + + Create tensor given tensor information + >>> ann.Tensor(ann.TensorInfo(...)) + + Create tensor from another tensor i.e. copy a tensor + >>> ann.Tensor(ann.Tensor()) + + Args: + tensor(Tensor, optional): Create Tensor from a Tensor i.e. copy. + tensor_info (TensorInfo, optional): Tensor information. + + Raises: + TypeError: unsupported input data type. + ValueError: appropriate constructor could not be found with provided arguments. + + """ + self.__memory_area = None + + # TensorInfo as first argument, we need to create memory area manually + if len(args) > 0 and isinstance(args[0], TensorInfo): + self.__create_memory_area(args[0].GetDataType(), args[0].GetNumElements()) + super().__init__(args[0], self.__memory_area.data) + + # copy constructor - reference to memory area is passed from copied tensor + # and armnn's copy constructor is called + elif len(args) > 0 and isinstance(args[0], Tensor): + self.__memory_area = args[0].get_memory_area() + super().__init__(args[0]) + + # empty constructor + elif len(args) == 0: + super().__init__() + + else: + raise ValueError('Incorrect number of arguments or type of arguments provided to create Tensor.') + + def __copy__(self) -> 'Tensor': + """ Make copy of a tensor. + + Make tensor copyable using the python copy operation. + + Note: + The tensor memory area is NOT copied. Instead, the new tensor maintains a + reference to the same memory area as the old tensor. + + Example: + Copy empty tensor + >>> from copy import copy + >>> import pyarmnn as ann + >>> tensor = ann.Tensor() + >>> copied_tensor = copy(tensor) + + Returns: + Tensor: a copy of the tensor object provided. + + """ + return Tensor(self) + + def __create_memory_area(self, data_type: int, num_elements: int): + """ Create the memory area used by the tensor to output its results. + + Args: + data_type (int): The type of data that will be stored in the memory area. + See DataType_*. + num_elements (int): Determines the size of the memory area that will be created. + + """ + np_data_type_mapping = {DataType_QAsymmU8: np.uint8, + DataType_QAsymmS8: np.int8, + DataType_QSymmS8: np.int8, + DataType_Float32: np.float32, + DataType_QSymmS16: np.int16, + DataType_Signed32: np.int32, + DataType_Float16: np.float16} + + if data_type not in np_data_type_mapping: + raise ValueError("The data type provided for this Tensor is not supported.") + + self.__memory_area = np.empty(shape=(num_elements,), dtype=np_data_type_mapping[data_type]) + + def get_memory_area(self) -> np.ndarray: + """ Get values that are stored by the tensor. + + Returns: + ndarray : Tensor data (as numpy array). + + """ + return self.__memory_area diff --git a/python/pyarmnn/src/pyarmnn/_tensor/workload_tensors.py b/python/pyarmnn/src/pyarmnn/_tensor/workload_tensors.py new file mode 100644 index 0000000..b33356c --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_tensor/workload_tensors.py @@ -0,0 +1,126 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +""" +This file contains functions relating to WorkloadTensors. +WorkloadTensors are the inputTensors and outputTensors that are consumed by IRuntime.EnqueueWorkload. +""" +from typing import Union, List, Tuple + +import numpy as np + +from .tensor import Tensor +from .const_tensor import ConstTensor + + +def make_input_tensors(inputs_binding_info: List[Tuple], + input_data: List[np.ndarray]) -> List[Tuple[int, ConstTensor]]: + """Returns `inputTensors` to be used with `IRuntime.EnqueueWorkload`. + + This is the primary function to call when you want to produce `inputTensors` for `IRuntime.EnqueueWorkload`. + The output is a list of tuples containing ConstTensors with a corresponding input tensor id. + The output should be used directly with `IRuntime.EnqueueWorkload`. + This function works for single or multiple input data and binding information. + + Examples: + Creating inputTensors. + >>> import pyarmnn as ann + >>> import numpy as np + >>> + >>> parser = ann.ITfLiteParser() + >>> ... + >>> example_image = np.array(...) + >>> input_binding_info = parser.GetNetworkInputBindingInfo(...) + >>> + >>> input_tensors = ann.make_input_tensors([input_binding_info], [example_image]) + + Args: + inputs_binding_info (list of tuples): (int, `TensorInfo`) Binding information for input tensors obtained from + `GetNetworkInputBindingInfo`. + input_data (list ndarrays): Tensor data to be used for inference. + + Returns: + list: `inputTensors` - A list of tuples (`int` , `ConstTensor`). + + + Raises: + ValueError: If length of `inputs_binding_info` and `input_data` are not the same. + """ + if len(inputs_binding_info) != len(input_data): + raise ValueError("Length of 'inputs_binding_info' does not match length of 'input_data'") + + input_tensors = [] + + for in_bind_info, in_data in zip(inputs_binding_info, input_data): + in_tensor_id = in_bind_info[0] + in_tensor_info = in_bind_info[1] + input_tensors.append((in_tensor_id, ConstTensor(in_tensor_info, in_data))) + + return input_tensors + + +def make_output_tensors(outputs_binding_info: List[Tuple]) -> List[Tuple[int, Tensor]]: + """Returns `outputTensors` to be used with `IRuntime.EnqueueWorkload`. + + This is the primary function to call when you want to produce `outputTensors` for `IRuntime.EnqueueWorkload`. + The output is a list of tuples containing Tensors with a corresponding output tensor id. + The output should be used directly with `IRuntime.EnqueueWorkload`. + + Examples: + Creating outputTensors. + >>> import pyarmnn as ann + >>> + >>> parser = ann.ITfLiteParser() + >>> ... + >>> output_binding_info = parser.GetNetworkOutputBindingInfo(...) + >>> + >>> output_tensors = ann.make_output_tensors([output_binding_info]) + + Args: + outputs_binding_info (list of tuples): (int, `TensorInfo`) Binding information for output tensors obtained from + `GetNetworkOutputBindingInfo`. + + Returns: + list: `outputTensors` - A list of tuples (`int`, `Tensor`). + """ + output_tensors = [] + + for out_bind_info in outputs_binding_info: + out_tensor_id = out_bind_info[0] + out_tensor_info = out_bind_info[1] + output_tensors.append((out_tensor_id, Tensor(out_tensor_info))) + + return output_tensors + + +def workload_tensors_to_ndarray(workload_tensors: List[Tuple[int, Union[Tensor, ConstTensor]]]) -> List[np.ndarray]: + """Returns a list of the underlying tensor data as ndarrays from `inputTensors` or `outputTensors`. + + We refer to `inputTensors` and `outputTensors` as workload tensors because + they are used with `IRuntime.EnqueueWorkload`. + Although this function can be used on either `inputTensors` or `outputTensors` the main use of this function + is to collect results from `outputTensors` after `IRuntime.EnqueueWorkload` has been called. + + Examples: + Getting results after inference. + >>> import pyarmnn as ann + >>> + >>> ... + >>> runtime = ann.IRuntime(...) + >>> ... + >>> runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + >>> + >>> inference_results = workload_tensors_to_ndarray(output_tensors) + + Args: + workload_tensors (inputTensors or outputTensors): `inputTensors` or `outputTensors` to get data from. See + `make_input_tensors` and `make_output_tensors`. + + Returns: + list: List of `ndarrays` for the underlying tensor data from given `inputTensors` or `outputTensors`. + """ + arrays = [] + for index, (_, tensor) in enumerate(workload_tensors): + arrays.append(tensor.get_memory_area().reshape(list(tensor.GetShape()))) + print("Workload tensor {} shape: {}".format(index, tensor.GetShape())) + + return arrays diff --git a/python/pyarmnn/src/pyarmnn/_utilities/__init__.py b/python/pyarmnn/src/pyarmnn/_utilities/__init__.py new file mode 100644 index 0000000..e44403d --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_utilities/__init__.py @@ -0,0 +1,4 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT + +from .profiling_helper import ProfilerData, get_profiling_data diff --git a/python/pyarmnn/src/pyarmnn/_utilities/profiling_helper.py b/python/pyarmnn/src/pyarmnn/_utilities/profiling_helper.py new file mode 100644 index 0000000..f8751fa --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_utilities/profiling_helper.py @@ -0,0 +1,97 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +""" +This file contains functions relating to the use of the Arm NN profiler within PyArmNN. +""" +import json +from collections import namedtuple + +ProfilerData = namedtuple('ProfilerData', ['inference_data', 'per_workload_execution_data']) +ProfilerData.__doc__ = """Container to hold the profiling inference data, and the profiling data per workload. + +Contains: + inference_data (dict): holds end-to-end inference performance data. Keys: + 'time_unit' - timer units. + 'execution_time' - list of total inference execution times for each inference run. + per_workload_execution_data (dict): holds per operation performance data, key is a operation name + Each operation has + 'time_unit' - timer units. + 'execution_time' - list of total execution times for each inference run. + 'backend' - backend used for this operation. + +Examples: + + >>> data = get_profiling_data(profiler) + >>> print(data) + >>> ProfilerData(inference_data={'time_unit': 'us', + 'execution_time': [8901372.972]}, + per_workload_execution_data={'CopyMemGeneric_Execute_#3': {'time_unit': 'us', + 'execution_time': [28.941], + 'backend': 'Unknown'}, + 'RefConvolution2dWorkload_Execute_#5': {'time_unit': 'us', + 'execution_time': [126838.071], + 'backend': 'CpuRef'}, + 'RefDepthwiseConvolution2dWorkload_Execute_#6': {'time_unit': 'us', + 'execution_time': [49886.208], + 'backend': 'CpuRef'} + ...etc + } + ) +""" + + +def get_profiling_data(profiler: 'IProfiler') -> ProfilerData: + """Reads IProfiler object passed in, extracts the relevant data + and returns it in a ProfilerData container. + + Args: + profiler (IProfiler): The IProfiler object to be parsed. + + Returns: + ProfilerData: A container containing the relevant data extracted from the Profiler output. + """ + + top_level_dict = json.loads(profiler.as_json()) + armnn_data = top_level_dict["ArmNN"] + inference_measurements = armnn_data["inference_measurements_#1"] + execution_data = inference_measurements["Execute_#2"] + + workload_data = {} + inference_data = {} + for exec_key, exec_value in execution_data.items(): + # Check all items with a type. + if "type" in exec_value and exec_value["type"] == "Event": + for event_key, event_value in exec_value.items(): + if event_key.startswith("Wall clock time_#") and event_value["type"] == "Measurement": + time_data = __get_wall_clock_times__(event_value) + time_data["backend"] = __get_backend(exec_key) + workload_data[exec_key] = time_data + # This is the total inference time map + if exec_key.startswith("Wall clock time_#") and exec_value["type"] == "Measurement": + time_data = __get_wall_clock_times__(exec_value) + inference_data.update(time_data) + return ProfilerData(inference_data=inference_data, per_workload_execution_data=workload_data) + + +def __get_wall_clock_times__(wall_clock_item): + execution_times = wall_clock_item["raw"] + time_data = {} + raw_data = [] + for time in execution_times: + raw_data.append(time) + time_data["time_unit"] = wall_clock_item["unit"] + time_data["execution_time"] = raw_data + return time_data + + +def __get_backend(exec_key): + if "ref" in exec_key.lower(): + return "CpuRef" + elif "neon" in exec_key.lower(): + return "CpuAcc" + elif "cl" in exec_key.lower(): + return "GpuAcc" + elif "ethos" in exec_key.lower(): + return "EthosNAcc" + else: + return "Unknown" diff --git a/python/pyarmnn/src/pyarmnn/_version.py b/python/pyarmnn/src/pyarmnn/_version.py new file mode 100644 index 0000000..c565520 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/_version.py @@ -0,0 +1,26 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +version_info = (20, 2, 0) + +__dev_version_env = os.getenv("PYARMNN_DEV_VER", "") + +if __dev_version_env: + __dev_version = "dev0" + try: + __dev_version = "dev{}".format(int(__dev_version_env)) + except ValueError: + __dev_version = str(__dev_version_env) + + version_info = (*version_info, __dev_version) + +__version__ = '.'.join(str(c) for c in version_info) +__arm_ml_version__ = '2{:03d}{:02d}{:02d}'.format(version_info[0], version_info[1], version_info[2]) + + +def check_armnn_version(installed_armnn_version, expected_armnn_version=__arm_ml_version__): + expected_armnn_version = expected_armnn_version[:-2] # cut off minor patch version + installed_armnn_version = installed_armnn_version[:-2] # cut off minor patch version + assert expected_armnn_version == installed_armnn_version, \ + "Expected ArmNN version is {} but installed ArmNN version is {}".format(expected_armnn_version, installed_armnn_version) diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn.i b/python/pyarmnn/src/pyarmnn/swig/armnn.i new file mode 100644 index 0000000..2227c9b --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/armnn.i @@ -0,0 +1,27 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%module pyarmnn +%{ +#define SWIG_FILE_WITH_INIT +#include "armnn/Types.hpp" +%} + +//typemap definitions and other common stuff +%include "standard_header.i" + +//armnn api submodules +%include "modules/armnn_backend.i" +%include "modules/armnn_types.i" +%include "modules/armnn_descriptors.i" +%include "modules/armnn_lstmparam.i" +%include "modules/armnn_network.i" +%include "modules/armnn_profiler.i" +%include "modules/armnn_runtime.i" +%include "modules/armnn_tensor.i" +%include "modules/armnn_types_utils.i" + +// Clear exception typemap. +%exception; + diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn_caffeparser.i b/python/pyarmnn/src/pyarmnn/swig/armnn_caffeparser.i new file mode 100644 index 0000000..538b486 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/armnn_caffeparser.i @@ -0,0 +1,103 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%module pyarmnn_caffeparser +%{ +#define SWIG_FILE_WITH_INIT +#include "armnnCaffeParser/ICaffeParser.hpp" +#include "armnn/INetwork.hpp" +%} + +//typemap definitions and other common stuff +%include "standard_header.i" + +namespace std { + %template(BindingPointInfo) pair; + %template(MapStringTensorShape) map; + %template(StringVector) vector; +} + +namespace armnnCaffeParser +{ + +%feature("docstring", +" +Interface for creating a parser object using Caffe (http://caffe.berkeleyvision.org/) caffemodel files. + +Parsers are used to automatically construct Arm NN graphs from model files. + +") ICaffeParser; + +%nodefaultctor ICaffeParser; +class ICaffeParser +{ +public: + // Documentation + %feature("docstring", + " + Retrieve binding info (layer id and tensor info) for the network input identified by the given layer name. + + Args: + name (str): Name of the input. + + Returns: + tuple: (`int`, `TensorInfo`) + ") GetNetworkInputBindingInfo; + + %feature("docstring", + " + Retrieve binding info (layer id and `TensorInfo`) for the network output identified by the given layer name. + + Args: + name (str): Name of the output. + + Returns: + tuple: (`int`, `TensorInfo`) + ") GetNetworkOutputBindingInfo; + + std::pair GetNetworkInputBindingInfo(const std::string& name); + std::pair GetNetworkOutputBindingInfo(const std::string& name); +}; + +%extend ICaffeParser { + // This is not a substitution of the default constructor of the Armnn class. It tells swig to create custom __init__ + // method for ICaffeParser python object that will use static factory method to do the job. + + ICaffeParser() { + return armnnCaffeParser::ICaffeParser::CreateRaw(); + } + + // The following does not replace a real destructor of the Armnn class. + // It creates a functions that will be called when swig object goes out of the scope to clean resources. + // so the user doesn't need to call ICaffeParser::Destroy himself. + // $self` is a pointer to extracted ArmNN ICaffeParser object. + + ~ICaffeParser() { + armnnCaffeParser::ICaffeParser::Destroy($self); + } + + %feature("docstring", + " + Create the network from a Caffe caffemodel binary file on disk. + + Args: + graphFile: Path to the caffe model to be parsed. + inputShapes (tuple): (`string`, `TensorShape`) A tuple containing the input name and TensorShape information for the network. + requestedOutputs (list): A list of the output tensor names. + + Returns: + INetwork: INetwork object for the parsed Caffe model. + ") CreateNetworkFromBinaryFile; + + %newobject CreateNetworkFromBinaryFile; + armnn::INetwork* CreateNetworkFromBinaryFile(const char* graphFile, + const std::map& inputShapes, + const std::vector& requestedOutputs) { + return $self->CreateNetworkFromBinaryFile(graphFile, inputShapes, requestedOutputs).release(); + } +} +} + +// Clear exception typemap. +%exception; diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn_onnxparser.i b/python/pyarmnn/src/pyarmnn/swig/armnn_onnxparser.i new file mode 100644 index 0000000..2598c08 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/armnn_onnxparser.i @@ -0,0 +1,96 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%module pyarmnn_onnxparser +%{ +#define SWIG_FILE_WITH_INIT +#include "armnnOnnxParser/IOnnxParser.hpp" +#include "armnn/INetwork.hpp" +%} + +//typemap definitions and other common stuff +%include "standard_header.i" + +namespace std { + %template(BindingPointInfo) pair; + %template(MapStringTensorShape) map; + %template(StringVector) vector; +} + +namespace armnnOnnxParser +{ +%feature("docstring", +" +Interface for creating a parser object using ONNX (https://onnx.ai/) onnx files. + +Parsers are used to automatically construct Arm NN graphs from model files. + +") IOnnxParser; + +%nodefaultctor IOnnxParser; +class IOnnxParser +{ +public: + %feature("docstring", + " + Retrieve binding info (layer id and tensor info) for the network input identified by the given layer name. + + Args: + name (string): Name of the input node. + + Returns: + tuple: (`int`, `TensorInfo`) + ") GetNetworkInputBindingInfo; + std::pair GetNetworkInputBindingInfo(const std::string& name); + + %feature("docstring", + " + Retrieve binding info (layer id and `TensorInfo`) for the network output identified by the given layer name. + + Args: + name (string): Name of the output node. + + Returns: + tuple: (`int`, `TensorInfo`) + ") GetNetworkOutputBindingInfo; + std::pair GetNetworkOutputBindingInfo(const std::string& name); +}; + +%extend IOnnxParser { + // This is not a substitution of the default constructor of the Armnn class. It tells swig to create custom __init__ + // method for IOnnxParser python object that will use static factory method to do the job. + IOnnxParser() { + return armnnOnnxParser::IOnnxParser::CreateRaw(); + } + + // The following does not replace a real destructor of the Armnn class. + // It creates a functions that will be called when swig object goes out of the scope to clean resources. + // so the user doesn't need to call IOnnxParser::Destroy himself. + // $self` is a pointer to extracted ArmNN IOnnxParser object. + ~IOnnxParser() { + armnnOnnxParser::IOnnxParser::Destroy($self); + } + + %feature("docstring", + " + Create the network from a binary file on disk. + + Args: + graphFile (str): Path to the onnx model to be parsed. + + Returns: + INetwork: Parsed network. + + Raises: + RuntimeError: If model file was not found. + ") CreateNetworkFromBinaryFile; + %newobject CreateNetworkFromBinaryFile; + armnn::INetwork* CreateNetworkFromBinaryFile(const char* graphFile) { + return $self->CreateNetworkFromBinaryFile(graphFile).release(); + } +} + +} +// Clear exception typemap. +%exception; diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn_tfliteparser.i b/python/pyarmnn/src/pyarmnn/swig/armnn_tfliteparser.i new file mode 100644 index 0000000..825b104 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/armnn_tfliteparser.i @@ -0,0 +1,132 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%module pyarmnn_tfliteparser +%{ +#include "armnnTfLiteParser/ITfLiteParser.hpp" +#include "armnn/Types.hpp" +#include "armnn/INetwork.hpp" +%} + +//typemap definitions and other common stuff +%include "standard_header.i" + +namespace std { + %template(BindingPointInfo) pair; + %template(MapStringTensorShape) map; + %template(StringVector) vector; +} + +namespace armnnTfLiteParser +{ +%feature("docstring", +" +Interface for creating a parser object using TfLite (https://www.tensorflow.org/lite) tflite files. + +Parsers are used to automatically construct Arm NN graphs from model files. + +") ITfLiteParser; +%nodefaultctor ITfLiteParser; +class ITfLiteParser +{ +public: + %feature("docstring", + " + Retrieve binding info (layer id and tensor info) for the network input identified by the given layer name and subgraph id. + Args: + subgraphId (int): The subgraph id. + name (str): Name of the input. + + Returns: + tuple: (`int`, `TensorInfo`). + ") GetNetworkInputBindingInfo; + std::pair GetNetworkInputBindingInfo(size_t subgraphId, const std::string& name); + + %feature("docstring", + " + Retrieve binding info (layer id and `TensorInfo`) for the network output identified by the given layer name and subgraph id. + + Args: + subgraphId (int): The subgraph id. + name (str): Name of the output. + + Returns: + tuple: (`int`, `TensorInfo`). + ") GetNetworkOutputBindingInfo; + std::pair GetNetworkOutputBindingInfo(size_t subgraphId, const std::string& name); + + %feature("docstring", + " + Return the number of subgraphs in the parsed model. + Returns: + int: The number of subgraphs. + ") GetSubgraphCount; + size_t GetSubgraphCount(); + + %feature("docstring", + " + Return the input tensor names for a given subgraph. + + Args: + subgraphId (int): The subgraph id. + + Returns: + list: A list of the input tensor names for the given model. + ") GetSubgraphInputTensorNames; + std::vector GetSubgraphInputTensorNames(size_t subgraphId); + + %feature("docstring", + " + Return the output tensor names for a given subgraph. + + Args: + subgraphId (int): The subgraph id + + Returns: + list: A list of the output tensor names for the given model. + ") GetSubgraphOutputTensorNames; + std::vector GetSubgraphOutputTensorNames(size_t subgraphId); +}; + +%extend ITfLiteParser { +// This is not a substitution of the default constructor of the Armnn class. It tells swig to create custom __init__ +// method for ITfLiteParser python object that will use static factory method to do the job. + + ITfLiteParser() { + return armnnTfLiteParser::ITfLiteParser::CreateRaw(); + } + +// The following does not replace a real destructor of the Armnn class. +// It creates a functions that will be called when swig object goes out of the scope to clean resources. +// so the user doesn't need to call ITfLiteParser::Destroy himself. +// $self` is a pointer to extracted ArmNN ITfLiteParser object. + + ~ITfLiteParser() { + armnnTfLiteParser::ITfLiteParser::Destroy($self); + } + + %feature("docstring", + " + Create the network from a flatbuffers binary file. + + Args: + graphFile (str): Path to the tflite model to be parsed. + + Returns: + INetwork: Parsed network. + + Raises: + RuntimeError: If model file was not found. + ") CreateNetworkFromBinaryFile; + + %newobject CreateNetworkFromBinaryFile; + armnn::INetwork* CreateNetworkFromBinaryFile(const char* graphFile) { + return $self->CreateNetworkFromBinaryFile(graphFile).release(); + } + +} + +} +// Clear exception typemap. +%exception; diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn_tfparser.i b/python/pyarmnn/src/pyarmnn/swig/armnn_tfparser.i new file mode 100644 index 0000000..03729ab --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/armnn_tfparser.i @@ -0,0 +1,102 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%module pyarmnn_tfparser +%{ +#define SWIG_FILE_WITH_INIT +#include "armnnTfParser/ITfParser.hpp" +#include "armnn/INetwork.hpp" +%} + +//typemap definitions and other common stuff +%include "standard_header.i" + +namespace std { + %template(BindingPointInfo) pair; + %template(MapStringTensorShape) map; + %template(StringVector) vector; +} + +namespace armnnTfParser +{ +%feature("docstring", +" +Interface for creating a parser object using TensorFlow (https://www.tensorflow.org/) frozen pb files. + +Parsers are used to automatically construct Arm NN graphs from model files. + +") ITfParser; +%nodefaultctor ITfParser; +class ITfParser +{ +public: + %feature("docstring", + " + Retrieve binding info (layer id and `TensorInfo`) for the network input identified by the given layer name. + + Args: + name (str): Name of the input. + + Returns: + tuple: (`int`, `TensorInfo`). + ") GetNetworkInputBindingInfo; + std::pair GetNetworkInputBindingInfo(const std::string& name); + + %feature("docstring", + " + Retrieve binding info (layer id and `TensorInfo`) for the network output identified by the given layer name. + + Args: + name (str): Name of the output. + + Returns: + tuple: (`int`, `TensorInfo`). + ") GetNetworkOutputBindingInfo; + std::pair GetNetworkOutputBindingInfo(const std::string& name); +}; + +%extend ITfParser { + // This is not a substitution of the default constructor of the Armnn class. It tells swig to create custom __init__ + // method for ITfParser python object that will use static factory method to do the job. + + ITfParser() { + return armnnTfParser::ITfParser::CreateRaw(); + } + + // The following does not replace a real destructor of the Armnn class. + // It creates a functions that will be called when swig object goes out of the scope to clean resources. + // so the user doesn't need to call ITfParser::Destroy himself. + // $self` is a pointer to extracted ArmNN ITfParser object. + + ~ITfParser() { + armnnTfParser::ITfParser::Destroy($self); + } + + %feature("docstring", + " + Create the network from a pb Protocol buffer file. + + Args: + graphFile (str): Path to the tf model to be parsed. + inputShapes (dict): A dict containing the input name as a key and `TensorShape` as a value. + requestedOutputs (list of str): A list of the output tensor names. + + Returns: + INetwork: Parsed network. + + Raises: + RuntimeError: If model file was not found. + ") CreateNetworkFromBinaryFile; + %newobject CreateNetworkFromBinaryFile; + armnn::INetwork* CreateNetworkFromBinaryFile(const char* graphFile, + const std::map& inputShapes, + const std::vector& requestedOutputs) { + return $self->CreateNetworkFromBinaryFile(graphFile, inputShapes, requestedOutputs).release(); + } + +} + +} +// Clear exception typemap. +%exception; diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn_version.i b/python/pyarmnn/src/pyarmnn/swig/armnn_version.i new file mode 100644 index 0000000..b21fbb1 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/armnn_version.i @@ -0,0 +1,58 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%module pyarmnn_version + +%include "std_string.i" + +%{ +#define SWIG_FILE_WITH_INIT +#include "armnn/Version.hpp" +%} + +%{ + std::string GetVersion() + { + return ARMNN_VERSION; + }; + + std::string GetMajorVersion() + { + return STRINGIFY_VALUE(ARMNN_MAJOR_VERSION); + }; + + std::string GetMinorVersion() + { + return STRINGIFY_VALUE(ARMNN_MINOR_VERSION); + }; +%} +%feature("docstring", +" + Returns Arm NN library full version: MAJOR + MINOR + INCREMENTAL. + + Returns: + str: Full version of Arm NN installed. + +") GetVersion; +std::string GetVersion(); + +%feature("docstring", +" + Returns Arm NN library major version. The year of the release. + + Returns: + str: Major version of Arm NN installed. + +") GetMajorVersion; +std::string GetMajorVersion(); + +%feature("docstring", +" + Returns Arm NN library minor version. Month of the year of the release. + + Returns: + str: Minor version of Arm NN installed. + +") GetMinorVersion; +std::string GetMinorVersion(); diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend.i new file mode 100644 index 0000000..e9cb5dd --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend.i @@ -0,0 +1,65 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/BackendId.hpp" +%} + +namespace std { + %template(BackendIdVector) vector; + %template(BackendIdSet) unordered_set; +} + +namespace armnn +{ + +class BackendId +{ +public: + %feature("docstring", + " + Creates backend id instance. + Supported backend ids: 'CpuRef', 'CpuAcc', 'GpuAcc', 'EthosNAcc'. + + Args: + id (str): Computation backend identification. + ") BackendId; + + BackendId(const std::string& id); + + %feature("docstring", + " + Checks if backend is cpu reference implementation. + Returns: + bool: True if backend supports cpu reference implementation, False otherwise. + + ") IsCpuRef; + bool IsCpuRef(); + + %feature("docstring", + " + Returns backend identification. + + >>> backendId = BackendId('CpuRef') + >>> assert 'CpuRef' == str(backendId) + >>> assert 'CpuRef' == backendId.Get() + + Returns: + str: Backend identification. + + ") Get; + const std::string& Get(); +}; + +%extend BackendId { + + std::string __str__() { + return $self->Get(); + } + +} + +using BackendIdVector = std::vector; +using BackendIdSet = std::unordered_set; +} diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_descriptors.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_descriptors.i new file mode 100644 index 0000000..75b875e --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_descriptors.i @@ -0,0 +1,1022 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/Descriptors.hpp" +#include "armnn/Types.hpp" +%} + +namespace std { + %template() vector; + %template() vector; + %template() vector>; + %template(TensorShapeVector) vector; +} + +%include "typemaps/vectors.i" + +%typemap(out) const uint32_t* +%{ +{ + auto len = arg1->GetNumViews(); + $result = PyList_New(len); + if (!$result) { + Py_XDECREF($result); + return PyErr_NoMemory(); + } + for (unsigned int i = 0; i < len; ++i) { + + PyList_SetItem($result, i, PyLong_FromUnsignedLong($1[i])); + } +} +%} + +namespace armnn +{ + +%list_to_vector( std::vector ); +%list_to_vector( std::vector ); +%list_to_vector( std::vector> ); + +%feature("docstring", + " + A configuration for the Activation layer. See `INetwork.AddActivationLayer()`. + + Contains: + m_Function (int): Specifies the activation function to use. + (`ActivationFunction_Sigmoid`, `ActivationFunction_TanH`, `ActivationFunction_Linear`, + `ActivationFunction_ReLu`, `ActivationFunction_BoundedReLu`, `ActivationFunction_SoftReLu`, + `ActivationFunction_LeakyReLu`, `ActivationFunction_Abs`, `ActivationFunction_Sqrt`, + `ActivationFunction_Square`). + Default: `ActivationFunction_Sigmoid`. + m_A (float): Alpha upper bound value used by the activation functions. (`ActivationFunction_BoundedReLu`, + `ActivationFunction_Linear`, `ActivationFunction_TanH`). Default: 0. + m_B (float): Beta lower bound value used by the activation functions. (`ActivationFunction_BoundedReLu`, + `ActivationFunction_Linear`, `ActivationFunction_TanH`). Default: 0. + + ") ActivationDescriptor; +struct ActivationDescriptor +{ + ActivationDescriptor(); + + ActivationFunction m_Function; + float m_A; + float m_B; + + bool operator ==(const ActivationDescriptor &rhs) const; +}; + + +%feature("docstring", + " + A descriptor for the ArgMinMax layer. See `INetwork.AddArgMinMaxLayer()`. + + Contains: + m_Function (int): Specify if the function is to find Min or Max with `ArgMinMaxFunction_Min` or `ArgMinMaxFunction_Max`. + Default: `ArgMinMaxFunction_Min`. + m_Axis (int): Axis to reduce across the input tensor. Default: -1. + + ") ArgMinMaxDescriptor; +struct ArgMinMaxDescriptor +{ + ArgMinMaxDescriptor(); + + ArgMinMaxFunction m_Function; + int m_Axis; + + bool operator ==(const ArgMinMaxDescriptor &rhs) const; +}; + +%feature("docstring", + " + A descriptor for the BatchNormalization layer. See `INetwork.AddBatchNormalizationLayer()`. + + Contains: + m_Eps (float): Value to add to the variance. Used to avoid dividing by zero. Default: 0.0001. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") BatchNormalizationDescriptor; +struct BatchNormalizationDescriptor +{ + BatchNormalizationDescriptor(); + + float m_Eps; + DataLayout m_DataLayout; + + bool operator ==(const BatchNormalizationDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the BatchToSpaceNd layer. See `INetwork.AddBatchToSpaceNdLayer()`. + + Contains: + m_BlockShape (list of int): Block shape values. Default: (1, 1). Underlying C++ type is unsigned int. + m_Crops (list of tuple): The values to crop from the input dimension. Default: [(0, 0), (0, 0)]. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") BatchToSpaceNdDescriptor; +struct BatchToSpaceNdDescriptor +{ + BatchToSpaceNdDescriptor(); + BatchToSpaceNdDescriptor(std::vector blockShape, + std::vector> crops); + + std::vector m_BlockShape; + std::vector> m_Crops; + DataLayout m_DataLayout; + + bool operator ==(const BatchToSpaceNdDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Comparison layer. See `INetwork.AddComparisonLayer()`. + + Contains: + m_Operation (int): Specifies the comparison operation to execute. + (`ComparisonOperation_Equal`, `ComparisonOperation_Greater`, `ComparisonOperation_GreaterOrEqual`, + `ComparisonOperation_Less`, `ComparisonOperation_LessOrEqual`, `ComparisonOperation_NotEqual`) + Default: `ComparisonOperation_Equal`. + ") ComparisonDescriptor; +struct ComparisonDescriptor +{ + ComparisonDescriptor(); + + ComparisonDescriptor(ComparisonOperation operation); + + bool operator ==(const ComparisonDescriptor &rhs) const; + + ComparisonOperation m_Operation; +}; + +%feature("docstring", + " + Creates a configuration/descriptor for a Concatenation layer. See `INetwork.AddConcatLayer()`. + Number of Views must be equal to the number of inputs, and their order must match e.g. first view corresponds to the first input, second view to the second input, etc. + + Contains: + numViews (int): Number of views, the value must be equal to the number of outputs of a layer. + numDimensions (int): Number of dimensions. Default value is 4. + + ") ConcatDescriptor; +struct ConcatDescriptor +{ + ConcatDescriptor(); + + ConcatDescriptor(uint32_t numViews, uint32_t numDimensions = 4); + + %feature("docstring", + " + Get the number of views. + Returns: + int: Number of views. + ") GetNumViews; + uint32_t GetNumViews() const; + + %feature("docstring", + " + Get the number of dimensions. + Returns: + int: Number of dimensions. + ") GetNumDimensions; + uint32_t GetNumDimensions() const; + + %feature("docstring", + " + Get the view origin input by index. + + Each view match the inputs order, e.g. first view corresponds to the first input, second view to the second input, etc. + + Args: + idx (int): Index to get view from. + + Returns: + list: View origin (shape) specified by the int value `idx` as a list of ints. + ") GetViewOrigin; + + const uint32_t* GetViewOrigin(uint32_t idx) const; + + %feature("docstring", + " + Set the concatenation dimension. + Args: + concatAxis (int): Concatenation axis index. + ") SetConcatAxis; + void SetConcatAxis(unsigned int concatAxis); + + %feature("docstring", + " + Get the concatenation dimension. + Returns: + int: Concatenation axis index. + ") GetConcatAxis; + unsigned int GetConcatAxis() const; + + bool operator ==(const ConcatDescriptor& rhs) const; +}; +%extend ConcatDescriptor{ + %feature("docstring", + " + Set the coordinates of a specific origin view input. + + Args: + view (int): Origin view index. + coord (int): Coordinate of the origin view to set. + value (int): Value to set. + Raises: + RuntimeError: If the `view` is greater than or equal to GetNumViews(). + RuntimeError: If the `coord` is greater than or equal to GetNumDimensions(). + ") SetViewOriginCoord; + void SetViewOriginCoord(uint32_t view, uint32_t coord, uint32_t value) { + armnn::Status status = $self->SetViewOriginCoord(view, coord, value); + if(status == armnn::Status::Failure) + { + throw armnn::Exception("Failed to set view origin coordinates."); + } + }; +} + +%feature("docstring", + " + A descriptor for the Convolution2d layer. See `INetwork.AddConvolution2dLayer()`. + + Contains: + m_PadLeft (int): Underlying C++ data type is uint32_t. Padding left value in the width dimension. Default: 0. + m_PadRight (int): Underlying C++ data type is uint32_t. Padding right value in the width dimension. Default: 0. + m_PadTop (int): Underlying C++ data type is uint32_t. Padding top value in the height dimension. Default: 0. + m_PadBottom (int): Underlying C++ data type is uint32_t. Padding bottom value in the height dimension. Default: 0. + m_StrideX (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the width dimension. Default: 0. + m_StrideY (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the height dimension. Default: 0. + m_DilationX (int): Underlying C++ data type is uint32_t. Dilation along x axis. Default: 1. + m_DilationY (int): Underlying C++ data type is uint32_t. Dilation along y axis. Default: 1. + m_BiasEnabled (bool): Enable/disable bias. Default: false. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") Convolution2dDescriptor; +struct Convolution2dDescriptor +{ + Convolution2dDescriptor(); + + uint32_t m_PadLeft; + uint32_t m_PadRight; + uint32_t m_PadTop; + uint32_t m_PadBottom; + uint32_t m_StrideX; + uint32_t m_StrideY; + uint32_t m_DilationX; + uint32_t m_DilationY; + bool m_BiasEnabled; + DataLayout m_DataLayout; + + bool operator ==(const Convolution2dDescriptor& rhs) const; +}; + + +%feature("docstring", + " + A descriptor for the DepthToSpace layer. See `INetwork.AddDepthToSpaceLayer()`. + + Contains: + m_BlockSize (int): Underlying C++ type is `unsigned int`. Scalar specifying the input block size. It must be >= 1. Default: 1. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NHWC`. + + ") DepthToSpaceDescriptor; +struct DepthToSpaceDescriptor +{ + DepthToSpaceDescriptor(); + DepthToSpaceDescriptor(unsigned int blockSize, DataLayout dataLayout); + + unsigned int m_BlockSize; + DataLayout m_DataLayout; +}; + + +%feature("docstring", + " + A descriptor for the DepthwiseConvolution2d layer. See `INetwork.AddDepthwiseConvolution2dLayer()`. + + Contains: + m_PadLeft (int): Underlying C++ data type is uint32_t. Padding left value in the width dimension. Default: 0. + m_PadRight (int): Underlying C++ data type is uint32_t. Padding right value in the width dimension. Default: 0. + m_PadTop (int): Underlying C++ data type is uint32_t. Padding top value in the height dimension. Default: 0. + m_PadBottom (int): Underlying C++ data type is uint32_t. Padding bottom value in the height dimension. Default: 0. + m_StrideX (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the width dimension. Default: 0. + m_StrideY (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the height dimension. Default: 0. + m_DilationX (int): Underlying C++ data type is uint32_t. Dilation along x axis. Default: 1. + m_DilationY (int): Underlying C++ data type is uint32_t. Dilation along y axis. Default: 1. + m_BiasEnabled (bool): Enable/disable bias. Default: false. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") DepthwiseConvolution2dDescriptor; +struct DepthwiseConvolution2dDescriptor +{ + DepthwiseConvolution2dDescriptor(); + + uint32_t m_PadLeft; + uint32_t m_PadRight; + uint32_t m_PadTop; + uint32_t m_PadBottom; + uint32_t m_StrideX; + uint32_t m_StrideY; + uint32_t m_DilationX; + uint32_t m_DilationY; + bool m_BiasEnabled; + DataLayout m_DataLayout; + + bool operator ==(const DepthwiseConvolution2dDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the DetectionPostProcess layer. See `INetwork.AddDetectionPostProcessLayer()`. + + This layer is a custom layer used to process the output from SSD MobilenetV1. + + Contains: + m_MaxDetections (int): Underlying C++ data type is uint32_t. Maximum numbers of detections. Default: 0. + m_MaxClassesPerDetection (int): Underlying C++ data type is uint32_t. Maximum numbers of classes per detection, used in Fast NMS. Default: 1. + m_DetectionsPerClass (int): Underlying C++ data type is uint32_t. Detections per classes, used in Regular NMS. Default: 1. + m_NmsScoreThreshold (float): Non maximum suppression score threshold. Default: 0. + m_NmsIouThreshold (float): Intersection over union threshold. Default: 0. + m_NumClasses (int): Underlying C++ data type is uint32_t. Number of classes. Default: 0. + m_UseRegularNms (bool): Use Regular Non maximum suppression. Default: false. + m_ScaleX (float): Center size encoding scale x. Default: 0. + m_ScaleY (float): Center size encoding scale y. Default: 0. + m_ScaleW (float): Center size encoding scale weight. Default: 0. + m_ScaleH (float): Center size encoding scale height. Default: 0. + + ") DetectionPostProcessDescriptor; +struct DetectionPostProcessDescriptor +{ + DetectionPostProcessDescriptor(); + + uint32_t m_MaxDetections; + uint32_t m_MaxClassesPerDetection; + uint32_t m_DetectionsPerClass; + float m_NmsScoreThreshold; + float m_NmsIouThreshold; + uint32_t m_NumClasses; + bool m_UseRegularNms; + float m_ScaleX; + float m_ScaleY; + float m_ScaleW; + float m_ScaleH; + + bool operator ==(const DetectionPostProcessDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the FakeQuantization layer. + + Contains: + m_Min (float): Minimum value for quantization range. Default: -6.0. + m_Max (float): Maximum value for quantization range. Default: 6.0. + + ") FakeQuantizationDescriptor; +struct FakeQuantizationDescriptor +{ + FakeQuantizationDescriptor(); + + float m_Min; + float m_Max; + + bool operator ==(const FakeQuantizationDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the FullyConnected layer. See `INetwork.AddFullyConnectedLayer()`. + + Contains: + m_BiasEnabled (bool): Enable/disable bias. Default: false. + m_TransposeWeightMatrix (bool): Enable/disable transpose weight matrix. Default: false. + + ") FullyConnectedDescriptor; +struct FullyConnectedDescriptor +{ + FullyConnectedDescriptor(); + + bool m_BiasEnabled; + bool m_TransposeWeightMatrix; + + bool operator ==(const FullyConnectedDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for InstanceNormalization layer. See `INetwork.AddInstanceNormalizationLayer()`. + + Contains: + m_Gamma (float): Gamma, the scale scalar value applied for the normalized tensor. Default: 1.0. + m_Gamma (float): Beta, the offset scalar value applied for the normalized tensor. Default: 0.0. + m_Gamma (float): Epsilon, small scalar value added to variance to avoid dividing by zero. Default: 1e-12. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") InstanceNormalizationDescriptor; +struct InstanceNormalizationDescriptor +{ + InstanceNormalizationDescriptor(); + + float m_Gamma; + float m_Beta; + float m_Eps; + DataLayout m_DataLayout; + + bool operator ==(const InstanceNormalizationDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the LSTM layer. See `INetwork.AddLstmLayer()`. + + Contains: + m_ActivationFunc (int): Underlying C++ data type is uint32_t. The activation function to use. 0: None, 1: Relu, 3: Relu6, 4: Tanh, 6: Sigmoid. + Default: 1. + m_ClippingThresCell (float): Clipping threshold value for the cell state. Default: 0.0. + m_ClippingThresProj (float): Clipping threshold value for the projection. Default: 0.0. + m_CifgEnabled (bool): Enable/disable cifg (coupled input & forget gate). Default: true. + m_PeepholeEnabled (bool): Enable/disable peephole. Default: false. + m_ProjectionEnabled (bool): Enable/disable the projection layer. Default: false. + m_LayerNormEnabled (bool): Enable/disable layer normalization. Default: false. + + ") LstmDescriptor; +struct LstmDescriptor +{ + LstmDescriptor(); + + uint32_t m_ActivationFunc; + float m_ClippingThresCell; + float m_ClippingThresProj; + bool m_CifgEnabled; + bool m_PeepholeEnabled; + bool m_ProjectionEnabled; + bool m_LayerNormEnabled; + + bool operator ==(const LstmDescriptor& rhs) const; +}; + +%feature("docstring", + " + A Descriptor for the L2Normalization layer. See `INetwork.AddL2NormalizationLayer()`. + + Contains: + m_Eps (float): Used to avoid dividing by zero. Default: 1e-12. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") L2NormalizationDescriptor; +struct L2NormalizationDescriptor +{ + L2NormalizationDescriptor(); + + float m_Eps; + DataLayout m_DataLayout; + + bool operator ==(const L2NormalizationDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Mean layer. See `INetwork.AddMeanLayer()`. + + Contains: + m_Axis (list of int): Underlying C++ data type is std::vector. Used to avoid dividing by zero. Values for the dimensions to reduce. + m_KeepDims (bool): Enable/disable keep dimensions. If true, then the reduced dimensions that are of length 1 are kept. Default: False. + + ") MeanDescriptor; +struct MeanDescriptor +{ + MeanDescriptor(); + MeanDescriptor(const std::vector& axis, bool keepDims); + + std::vector m_Axis; + bool m_KeepDims; + + bool operator ==(const MeanDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Normalization layer. See `INetwork.AddNormalizationLayer()`. + + Contains: + m_NormChannelType (int): Normalization channel algorithm to use (`NormalizationAlgorithmMethod_Across`, `NormalizationAlgorithmMethod_Within`). + Default: `NormalizationAlgorithmChannel_Across`. + m_NormMethodType (int): Normalization method algorithm to use (`NormalizationAlgorithmMethod_LocalBrightness`, `NormalizationAlgorithmMethod_LocalContrast`). + Default: `NormalizationAlgorithmMethod_LocalBrightness`. + m_NormSize (int): Underlying C++ data type is uint32_t. Depth radius value. Default: 0. + m_Alpha (float): Alpha value for the normalization equation. Default: 0.0. + m_Beta (float): Beta value for the normalization equation. Default: 0.0. + m_K (float): Kappa value used for the across channel normalization equation. Default: 0.0. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") NormalizationDescriptor; +struct NormalizationDescriptor +{ + NormalizationDescriptor(); + + NormalizationAlgorithmChannel m_NormChannelType; + NormalizationAlgorithmMethod m_NormMethodType; + uint32_t m_NormSize; + float m_Alpha; + float m_Beta; + float m_K; + DataLayout m_DataLayout; + + bool operator ==(const NormalizationDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Pad layer. See `INetwork.AddPadLayer()`. + + Contains: + m_PadList (list of tuple): specifies the padding for input dimension. + The first tuple value is the number of values to add before the tensor in the dimension. + The second tuple value is the number of values to add after the tensor in the dimension. + The number of pairs should match the number of dimensions in the input tensor. + m_PadValue (bool): Optional value to use for padding. Default: 0. + + ") PadDescriptor; +struct PadDescriptor +{ + PadDescriptor(); + PadDescriptor(const std::vector>& padList, const float& padValue = 0); + + std::vector> m_PadList; + float m_PadValue; + + bool operator ==(const PadDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the ElementwiseUnary layer. See `INetwork.AddElementwiseUnaryLayer()`. + + Contains: + m_Operation (int): Indicates which Unary operation to use. (`UnaryOperation_Abs`, `UnaryOperation_Exp`, + `UnaryOperation_Neg`, `UnaryOperation_Rsqrt`, `UnaryOperation_Sqrt`) + Default: `UnaryOperation_Abs`. + + ") ElementwiseUnaryDescriptor; +struct ElementwiseUnaryDescriptor +{ + ElementwiseUnaryDescriptor(); + ElementwiseUnaryDescriptor(UnaryOperation operation); + + UnaryOperation m_Operation; + + bool operator ==(const ElementwiseUnaryDescriptor &rhs) const; +}; + + +%feature("docstring", + " + A descriptor for the Permute layer. See `INetwork.AddPermuteLayer()`. + + Contains: + m_DimMappings (PermutationVector): Indicates how to translate tensor elements from a given source into the target destination, + when source and target potentially have different memory layouts e.g. {0U, 3U, 1U, 2U}. + + ") PermuteDescriptor; +struct PermuteDescriptor +{ + PermuteDescriptor(); + PermuteDescriptor(const PermutationVector& dimMappings); + + PermutationVector m_DimMappings; + + bool operator ==(const PermuteDescriptor &rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Pooling2d layer. See `INetwork.AddPooling2dLayer()`. + + Contains: + m_PoolType (int): The pooling algorithm to use (`PoolingAlgorithm_Max`, `PoolingAlgorithm_Average`, `PoolingAlgorithm_L2`). Default: `PoolingAlgorithm_Max`. + m_PadLeft (int): Underlying C++ data type is uint32_t. Padding left value in the width dimension. Default: 0. + m_PadRight (int): Underlying C++ data type is uint32_t. Padding right value in the width dimension. Default: 0. + m_PadTop (int): Underlying C++ data type is uint32_t. Padding top value in the height dimension. Default: 0. + m_PadBottom (int): Underlying C++ data type is uint32_t. Padding bottom value in the height dimension. Default: 0. + m_PoolWidth (int): Underlying C++ data type is uint32_t. Pooling width value. Default: 0. + m_PoolHeight (int): Underlying C++ data type is uint32_t. Pooling height value. Default: 0. + m_StrideX (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the width dimension. Default: 0. + m_StrideY (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the height dimension. Default: 0. + m_OutputShapeRounding (int): The rounding method for the output shape. (`OutputShapeRounding_Floor`, `OutputShapeRounding_Ceiling`). + Default: `OutputShapeRounding_Floor`. + m_PaddingMethod (int): The padding method to be used. (`PaddingMethod_Exclude`, `PaddingMethod_IgnoreValue`). + Default: `PaddingMethod_Exclude`. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") Pooling2dDescriptor; +struct Pooling2dDescriptor +{ + Pooling2dDescriptor(); + + PoolingAlgorithm m_PoolType; + uint32_t m_PadLeft; + uint32_t m_PadRight; + uint32_t m_PadTop; + uint32_t m_PadBottom; + uint32_t m_PoolWidth; + uint32_t m_PoolHeight; + uint32_t m_StrideX; + uint32_t m_StrideY; + OutputShapeRounding m_OutputShapeRounding; + PaddingMethod m_PaddingMethod; + DataLayout m_DataLayout; + + bool operator ==(const Pooling2dDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Reshape layer. See `INetwork.AddReshapeLayer()`. + + Contains: + m_TargetShape (TensorShape): Target shape value. + + ") ReshapeDescriptor; +struct ReshapeDescriptor +{ + ReshapeDescriptor(); + ReshapeDescriptor(const armnn::TensorShape& shape); + + armnn::TensorShape m_TargetShape; + + bool operator ==(const ReshapeDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Resize layer. See `INetwork.AddResizeLayer()`. + + Contains: + m_TargetWidth (int): Underlying C++ data type is uint32_t. Target width value. Default: 0. + m_TargetHeight (int): Underlying C++ data type is uint32_t. Target height value. Default: 0. + m_Method (int): The Interpolation method to use (`ResizeMethod_Bilinear`, `ResizeMethod_NearestNeighbor`). + Default: `ResizeMethod_NearestNeighbor`. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + m_BilinearAlignCorners (bool): Align corners or not when resizing bilinearly. If True, corner pixel values are preserved after resizing. + Default: False + + ") ResizeDescriptor; +struct ResizeDescriptor +{ + ResizeDescriptor(); + + uint32_t m_TargetWidth; + uint32_t m_TargetHeight; + ResizeMethod m_Method; + DataLayout m_DataLayout; + bool m_BilinearAlignCorners; + + bool operator ==(const ResizeDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Slice layer. See `INetwork.AddSliceLayer()`. + + Contains: + m_Begin (list of int): Underlying C++ data type is std::vector. Beginning indices of the slice in each dimension. + m_Size (list of int): Underlying C++ data type is std::vector. Size of the slice in each dimension. + + ") SliceDescriptor; +struct SliceDescriptor +{ + SliceDescriptor(); + SliceDescriptor(const std::vector& begin, const std::vector& size); + + std::vector m_Begin; + std::vector m_Size; + + bool operator ==(const SliceDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Space To Batch N-dimensions layer. See `INetwork.AddSpaceToBatchNdLayer()`. + + Contains: + m_BlockShape (list of int): Underlying C++ data type is std::vector. Block shape values. Default: [1, 1]. + m_Crops (list of tuple): Specifies the padding values for the input dimension: + [heightPad - (top, bottom) widthPad - (left, right)]. + Default: [(0, 0), (0, 0)]. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + ") SpaceToBatchNdDescriptor; +struct SpaceToBatchNdDescriptor +{ + SpaceToBatchNdDescriptor(); + SpaceToBatchNdDescriptor(const std::vector& blockShape, + const std::vector>& padList); + + std::vector m_BlockShape; + std::vector> m_PadList; + DataLayout m_DataLayout; + + bool operator ==(const SpaceToBatchNdDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the SpaceToDepth layer. See `INetwork.AddSpaceToDepthLayer()`. + + Contains: + m_BlockSize (int): Underlying C++ type is unsigned int. Scalar specifying the input block size. It must be >= 1. Default: 1. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NHWC`. + + ") SpaceToDepthDescriptor; +struct SpaceToDepthDescriptor +{ + SpaceToDepthDescriptor(); + SpaceToDepthDescriptor(unsigned int blockSize, DataLayout dataLayout); + + unsigned int m_BlockSize; + DataLayout m_DataLayout; + + bool operator ==(const SpaceToDepthDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for a Splitter layer. See `INetwork.AddSplitterLayer()`. + + Args: + numViews (int): Number of views, the value must be equal to the number of outputs of a layer. + numDimensions (int): Number of dimensions. Default value is 4. + + ") SplitterDescriptor; +struct SplitterDescriptor +{ + + SplitterDescriptor(uint32_t numViews, uint32_t numDimensions = 4); + + SplitterDescriptor(); + + %feature("docstring", + " + Get the number of views. + Returns: + int: number of views. + ") GetNumViews; + uint32_t GetNumViews() const; + + %feature("docstring", + " + Get the number of dimensions. + + Returns: + int: Number of dimensions. + + ") GetNumDimensions; + uint32_t GetNumDimensions() const; + + %feature("docstring", + " + Get the output view origin (shape) by index, the order matches the outputs. + + e.g. first view corresponds to the first output, second view to the second output, etc. + Args: + idx (int): Index. + Returns: + list: View origin (shape) as a list of ints. + ") GetViewOrigin; + + const uint32_t* GetViewOrigin(uint32_t idx) const; + + %feature("docstring", + " + Get the view sizes by index. + Args: + idx (int): Index. + Returns: + list: Sizes for the specified index as a list of ints. + ") GetViewSizes; + const uint32_t* GetViewSizes(uint32_t idx) const; + + + %feature("docstring", + " + Get the view origins that describe how the splitting process is configured. + + The number of views is the number of outputs, and their order match. + Returns: + OriginsDescriptor: A descriptor for the origins view. + ") GetOrigins; + const ConcatDescriptor GetOrigins() const; + + bool operator ==(const SplitterDescriptor& rhs) const; +}; + +%extend SplitterDescriptor{ + %feature("docstring", + " + Set the value of a specific origin view input coordinate. + + Contains: + view (int): Origin view index. + coord (int): Coordinate of the origin view to set. + value (int): Value to set. + Raises: + RuntimeError: If the `view` is greater than or equal to GetNumViews(). + If the `coord` is greater than or equal to GetNumDimensions(). + ") SetViewOriginCoord; + void SetViewOriginCoord(uint32_t view, uint32_t coord, uint32_t value) { + armnn::Status status = $self->SetViewOriginCoord(view, coord, value); + if(status == armnn::Status::Failure) + { + throw armnn::Exception("Failed to set view origin coordinates."); + } + }; + + %feature("docstring", + " + Set the size of the views. + + Args: + view (int): View index. + coord (int): Coordinate of the origin view to set. + value (int): Value to set. + Raises: + RuntimeError: If the `view` is greater than or equal to GetNumViews(). + If the `coord` is greater than or equal to GetNumDimensions(). + ") SetViewSize; + void SetViewSize(uint32_t view, uint32_t coord, uint32_t value) { + armnn::Status status = $self->SetViewSize(view, coord, value); + if(status == armnn::Status::Failure) + { + throw armnn::Exception("Failed to set view size."); + } + } +} + +%feature("docstring", + " + A descriptor for the Stack layer. See `INetwork.AddStackLayer()`. + + Contains: + m_Axis (int): Underlying C++ type is unsigned int. 0-based axis along which to stack the input tensors. Default: 0. + m_NumInputs (int): Required shape of all input tensors. Default: 0. + m_InputShape (TensorShape): Required shape of all input tensors. + + ") StackDescriptor; +struct StackDescriptor +{ + StackDescriptor(); + StackDescriptor(uint32_t axis, uint32_t numInputs, const armnn::TensorShape& inputShape); + + uint32_t m_Axis; + uint32_t m_NumInputs; + armnn::TensorShape m_InputShape; + + bool operator ==(const StackDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the StandIn layer. See `INetwork.AddStandInLayer()`. + + Contains: + m_NumInputs (int): Underlying C++ type is unsigned int. Number of input tensors. Default: 0. + m_NumOutputs (int): Underlying C++ type is unsigned int. Number of output tensors. Default: 0. + + ") StandInDescriptor; +struct StandInDescriptor +{ + StandInDescriptor(); + + StandInDescriptor(uint32_t numInputs, uint32_t numOutputs); + + uint32_t m_NumInputs = 0; + uint32_t m_NumOutputs = 0; +}; + +%feature("docstring", + " + A descriptor for the StridedSlice layer. See `INetwork.AddStridedSliceLayer()`. + + Contains: + m_Begin (list of int): Underlying C++ data type is std::vector. Begin values for the input that will be sliced. + m_End (list of int): Underlying C++ data type is std::vector. End values for the input that will be sliced. + m_Stride (list of int): Underlying C++ data type is std::vector. Stride values for the input that will be sliced. + m_BeginMask (int): Underlying C++ data type is int32_t. Begin mask value. If set, then the begin is disregarded and + the fullest range is used for the dimension. Default: 0. + m_EndMask (int): Underlying C++ data type is int32_t. End mask value. If set, then the end is disregarded and + the fullest range is used for the dimension.Default: 0. + m_ShrinkAxisMask (int): Underlying C++ data type is int32_t. Shrink axis mask value. If set, the nth specification shrinks the dimensionality by 1. Default: 0. + m_EllipsisMask (int): Underlying C++ data type is int32_t. Ellipsis mask value. Default: 0. + m_NewAxisMask (int): Underlying C++ data type is int32_t. New axis mask value. If set, the begin, end and stride is disregarded and + a new 1 dimension is inserted to this location of the output tensor. Default: 0. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") StridedSliceDescriptor; +struct StridedSliceDescriptor +{ + StridedSliceDescriptor(); + StridedSliceDescriptor(const std::vector begin, + const std::vector end, + const std::vector stride); + + int GetStartForAxis(const armnn::TensorShape& inputShape, unsigned int axis) const; + int GetStopForAxis(const armnn::TensorShape& inputShape, unsigned int axis, int startForAxis) const; + + std::vector m_Begin; + std::vector m_End; + std::vector m_Stride; + + int32_t m_BeginMask; + int32_t m_EndMask; + int32_t m_ShrinkAxisMask; + int32_t m_EllipsisMask; + int32_t m_NewAxisMask; + DataLayout m_DataLayout; + + bool operator ==(const StridedSliceDescriptor& rhs) const; +}; + +%feature("docstring", + " + A descriptor for the Softmax layer. See `INetwork.AddSoftmaxLayer()`. + + Contains: + m_Beta (float): Exponentiation value. + m_Axis (int): Scalar, defaulted to the last index (-1), specifying the dimension the activation will be performed on. + ") SoftmaxDescriptor; +struct SoftmaxDescriptor +{ + SoftmaxDescriptor(); + + float m_Beta; + int m_Axis; + + bool operator ==(const SoftmaxDescriptor& rhs) const; +}; + + +%feature("docstring", + " + A descriptor for the TransposeConvolution2d layer. See `INetwork.AddTransposeConvolution2dLayer()`. + + Contains: + m_PadLeft (int): Underlying C++ data type is uint32_t. Padding left value in the width dimension. Default: 0. + m_PadRight (int): Underlying C++ data type is uint32_t. Padding right value in the width dimension. Default: 0. + m_PadTop (int): Underlying C++ data type is uint32_t. Padding top value in the height dimension. Default: 0. + m_PadBottom (int): Underlying C++ data type is uint32_t. Padding bottom value in the height dimension. Default: 0. + m_StrideX (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the width dimension. Default: 0. + m_StrideY (int): Underlying C++ data type is uint32_t. Stride value when proceeding through input for the height dimension. Default: 0. + m_BiasEnabled (bool): Enable/disable bias. Default: false. + m_DataLayout (int): The data layout to be used (`DataLayout_NCHW`, `DataLayout_NHWC`). Default: `DataLayout_NCHW`. + + ") TransposeConvolution2dDescriptor; +struct TransposeConvolution2dDescriptor +{ + TransposeConvolution2dDescriptor(); + + uint32_t m_PadLeft; + uint32_t m_PadRight; + uint32_t m_PadTop; + uint32_t m_PadBottom; + uint32_t m_StrideX; + uint32_t m_StrideY; + bool m_BiasEnabled; + DataLayout m_DataLayout; + + bool operator ==(const TransposeConvolution2dDescriptor& rhs) const; +}; + + +using ConcatDescriptor = OriginsDescriptor; +using LogSoftmaxDescriptor = SoftmaxDescriptor; +using SplitterDescriptor = ViewsDescriptor; + +%list_to_vector_clear(std::vector); +%list_to_vector_clear(std::vector); +%list_to_vector_clear(std::vector>); +} + +%{ + armnn::ConcatDescriptor CreateDescriptorForConcatenation(std::vector shapes, + unsigned int concatenationDimension) + { + return armnn::CreateDescriptorForConcatenation(shapes.begin(), shapes.end(), concatenationDimension); + }; +%} + +%feature("docstring", + " + Create a descriptor for a Concatenation layer. + Args: + shapes (list of TensorShape): Input shapes of tensors to concatenated. + concatenationDimension (int): Concatenation axis, must be >=0. + + Returns: + ConcatDescriptor: A descriptor object for a Concatenation layer. + ") CreateDescriptorForConcatenation; +armnn::ConcatDescriptor CreateDescriptorForConcatenation(std::vector shapes, + unsigned int concatenationDimension); + +%typemap(out) const uint32_t*; diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_lstmparam.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_lstmparam.i new file mode 100644 index 0000000..0b9735e --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_lstmparam.i @@ -0,0 +1,159 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/LstmParams.hpp" +#include "armnn/QuantizedLstmParams.hpp" +%} + +namespace armnn +{ + +%feature("docstring", + " + Long Short-Term Memory layer input parameters. + + See `INetwork.AddLstmLayer()`. + Operation described by the following equations: + + \[i_t=\sigma(W_{xi}x_t+W_{hi}h_{t-1}+W_{ci}C_{t-1}+b_i) \\\\ + f_t=\sigma(W_{xf}x_t+W_{hf}h_{t-1}+W_{cf}C_{t-1}+b_f) \\\\ + C_t=clip(f_t \odot C_{t-1} + i_t \odot g(W_{xc}x_t+W_{hc}h_{t-1}+b_c),\ t_{cell}) \\\\ + o_t = \sigma(W_{xo}x_t+W_{ho}h_{t-1}+W_{co}C_t+b_o) \\\\ + h_t = clip(W_{proj}(o_t \odot g(C_t))+b_{proj},\ t_{proj})\ if\ there\ is\ a\ projection; \\\\ + h_t = o_t \odot g(C_t)\ otherwise. \] + Where: + \(x_t\) - input; + \(i_t\) - input gate; + \(f_t\) - forget gate; + \(C_t\) - cell state; + \(o_t\) - output; + \(h_t\) - output state; + \(\sigma\) - logistic sigmoid function; + \(g\) - cell input and cell output activation function, see `LstmDescriptor.m_ActivationFunc`; + \(t_{cell}\) - threshold for clipping the cell state, see `LstmDescriptor.m_ClippingThresCell`; + \(t_{proj}\) - threshold for clipping the projected output, see `LstmDescriptor.m_ClippingThresProj`; + + Contains: + m_InputToInputWeights (ConstTensor): \(W_{xi}\), input-to-input weight matrix. + m_InputToForgetWeights (ConstTensor): \(W_{xf}\), input-to-forget weight matrix. + m_InputToCellWeights (ConstTensor): \(W_{xc}\), input-to-cell weight matrix. + m_InputToOutputWeights (ConstTensor): \(W_{xo}\), input-to-output weight matrix. + + m_RecurrentToInputWeights (ConstTensor): \(W_{hi}\), recurrent-to-input weight matrix. + m_RecurrentToForgetWeights (ConstTensor): \(W_{hf}\), recurrent-to-forget weight matrix. + m_RecurrentToCellWeights (ConstTensor): \(W_{hc}\), recurrent-to-cell weight matrix. + m_RecurrentToOutputWeights (ConstTensor): \(W_{ho}\), recurrent-to-output weight matrix. + + m_CellToInputWeights (ConstTensor): \(W_{ci}\), cell-to-input weight matrix. Has effect if `LstmDescriptor.m_PeepholeEnabled`. + m_CellToForgetWeights (ConstTensor): \(W_{cf}\), cell-to-forget weight matrix. Has effect if `LstmDescriptor.m_PeepholeEnabled`. + m_CellToOutputWeights (ConstTensor): \(W_{co}\), cell-to-output weight matrix. Has effect if `LstmDescriptor.m_PeepholeEnabled`. + + m_InputGateBias (ConstTensor): \(b_i\), input gate bias. + m_ForgetGateBias (ConstTensor): \(b_f\), forget gate bias. + m_CellBias (ConstTensor): \(b_c\), cell bias. + m_OutputGateBias (ConstTensor): \(b_o\), output gate bias. + + m_ProjectionWeights (ConstTensor): \(W_{proj}\), projection weight matrix. + Has effect if `LstmDescriptor.m_ProjectionEnabled` is set to True. + m_ProjectionBias (ConstTensor): \(b_{proj}\), projection bias. + Has effect if `LstmDescriptor.m_ProjectionEnabled` is set to True. + m_InputLayerNormWeights (ConstTensor): normalisation weights for input, + has effect if `LstmDescriptor.m_LayerNormEnabled` set to True. + m_ForgetLayerNormWeights (ConstTensor): normalisation weights for forget gate, + has effect if `LstmDescriptor.m_LayerNormEnabled` set to True. + m_CellLayerNormWeights (ConstTensor): normalisation weights for current cell, + has effect if `LstmDescriptor.m_LayerNormEnabled` set to True. + m_OutputLayerNormWeights (ConstTensor): normalisation weights for output gate, + has effect if `LstmDescriptor.m_LayerNormEnabled` set to True. + + ") LstmInputParams; +struct LstmInputParams +{ + LstmInputParams(); + + const armnn::ConstTensor* m_InputToInputWeights; + const armnn::ConstTensor* m_InputToForgetWeights; + const armnn::ConstTensor* m_InputToCellWeights; + const armnn::ConstTensor* m_InputToOutputWeights; + const armnn::ConstTensor* m_RecurrentToInputWeights; + const armnn::ConstTensor* m_RecurrentToForgetWeights; + const armnn::ConstTensor* m_RecurrentToCellWeights; + const armnn::ConstTensor* m_RecurrentToOutputWeights; + const armnn::ConstTensor* m_CellToInputWeights; + const armnn::ConstTensor* m_CellToForgetWeights; + const armnn::ConstTensor* m_CellToOutputWeights; + const armnn::ConstTensor* m_InputGateBias; + const armnn::ConstTensor* m_ForgetGateBias; + const armnn::ConstTensor* m_CellBias; + const armnn::ConstTensor* m_OutputGateBias; + const armnn::ConstTensor* m_ProjectionWeights; + const armnn::ConstTensor* m_ProjectionBias; + const armnn::ConstTensor* m_InputLayerNormWeights; + const armnn::ConstTensor* m_ForgetLayerNormWeights; + const armnn::ConstTensor* m_CellLayerNormWeights; + const armnn::ConstTensor* m_OutputLayerNormWeights; +}; + +%feature("docstring", + " + Quantized Long Short-Term Memory layer input parameters. + + See `INetwork.AddQuantizedLstmLayer()`. + Operation described by the following equations: + + \[i_t=\sigma(W_{xi}x_t+W_{hi}h_{t-1}+W_{ci}C_{t-1}+b_i) \\\\ + f_t=\sigma(W_{xf}x_t+W_{hf}h_{t-1}+W_{cf}C_{t-1}+b_f) \\\\ + C_t=clip(f_t \odot C_{t-1} + i_t \odot g(W_{xc}x_t+W_{hc}h_{t-1}+b_c),\ t_{cell}) \\\\ + o_t = \sigma(W_{xo}x_t+W_{ho}h_{t-1}+W_{co}C_t+b_o) \\\\ + h_t = clip(W_{proj}(o_t \odot g(C_t))+b_{proj},\ t_{proj})\ if\ there\ is\ a\ projection; \\\\ + h_t = o_t \odot g(C_t)\ otherwise. \] + Where: + \(x_t\) - input; + \(i_t\) - input gate; + \(f_t\) - forget gate; + \(C_t\) - cell state; + \(o_t\) - output; + \(h_t\) - output state; + \(\sigma\) - logistic sigmoid function; + \(g\) - cell input and cell output activation function, see `LstmDescriptor.m_ActivationFunc`; + \(t_{cell}\) - threshold for clipping the cell state, see `LstmDescriptor.m_ClippingThresCell`; + \(t_{proj}\) - threshold for clipping the projected output, see `LstmDescriptor.m_ClippingThresProj`; + + Contains: + m_InputToInputWeights (ConstTensor): \(W_{xi}\), input-to-input weight matrix. + m_InputToForgetWeights (ConstTensor): \(W_{xf}\), input-to-forget weight matrix. + m_InputToCellWeights (ConstTensor): \(W_{xc}\), input-to-cell weight matrix. + m_InputToOutputWeights (ConstTensor): \(W_{xo}\), input-to-output weight matrix. + + m_RecurrentToInputWeights (ConstTensor): \(W_{hi}\), recurrent-to-input weight matrix. + m_RecurrentToForgetWeights (ConstTensor): \(W_{hf}\), recurrent-to-forget weight matrix. + m_RecurrentToCellWeights (ConstTensor): \(W_{hc}\), recurrent-to-cell weight matrix. + m_RecurrentToOutputWeights (ConstTensor): \(W_{ho}\), recurrent-to-output weight matrix. + + m_InputGateBias (ConstTensor): \(b_i\), input gate bias. + m_ForgetGateBias (ConstTensor): \(b_f\), forget gate bias. + m_CellBias (ConstTensor): \(b_c\), cell bias. + m_OutputGateBias (ConstTensor): \(b_o\), output gate bias. + ") QuantizedLstmInputParams; +struct QuantizedLstmInputParams +{ + QuantizedLstmInputParams(); + + const armnn::ConstTensor* m_InputToInputWeights; + const armnn::ConstTensor* m_InputToForgetWeights; + const armnn::ConstTensor* m_InputToCellWeights; + const armnn::ConstTensor* m_InputToOutputWeights; + const armnn::ConstTensor* m_RecurrentToInputWeights; + const armnn::ConstTensor* m_RecurrentToForgetWeights; + const armnn::ConstTensor* m_RecurrentToCellWeights; + const armnn::ConstTensor* m_RecurrentToOutputWeights; + const armnn::ConstTensor* m_InputGateBias; + const armnn::ConstTensor* m_ForgetGateBias; + const armnn::ConstTensor* m_CellBias; + const armnn::ConstTensor* m_OutputGateBias; +}; + + +} diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i new file mode 100644 index 0000000..b065331 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i @@ -0,0 +1,1158 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/INetwork.hpp" +#include "armnn/BackendId.hpp" +#include "armnn/Types.hpp" +#include "armnn/Optional.hpp" +#include +%} + +%include + +namespace armnn +{ +%feature("docstring", +" +Struct for holding options relating to the Arm NN optimizer. See `Optimize`. + +Contains: + m_debug (bool): Add debug data for easier troubleshooting. + m_ReduceFp32ToFp16 (bool): Reduce Fp32 data to Fp16 for faster processing. + +") OptimizerOptions; +struct OptimizerOptions +{ + OptimizerOptions(); + + OptimizerOptions(bool reduceFp32ToFp16, bool debug); + + bool m_ReduceFp32ToFp16; + bool m_Debug; +}; + +%feature("docstring", +" +An input connection slot for a layer. Slot lifecycle is managed by the layer. + +The input slot can be connected to an output slot of the preceding layer in the graph. +Only one connection to the input slot is allowed. + +") IInputSlot; +%nodefaultctor IInputSlot; +%nodefaultdtor IInputSlot; +class IInputSlot +{ +public: + %feature("docstring", + " + Returns output slot of a preceding layer that is connected to the given input slot. + + Returns: + IOutputSlot: Borrowed reference to an output connection slot for a preceding layer. + + ") GetConnection; + + armnn::IOutputSlot* GetConnection(); +}; + +%feature("docstring", +" +An output connection slot for a layer. Slot lifecycle is managed by the layer. + +The output slot may be connected to 1 or more input slots of subsequent layers in the graph. +") IOutputSlot; +%nodefaultctor IOutputSlot; +%nodefaultdtor IOutputSlot; +class IOutputSlot +{ +public: + + %feature("docstring", + " + Returns the total number of connected input slots. + + The same result could be obtained by calling `len()`: + + >>> output_slot = ... + >>> size = len(output_slot) + >>> assert size == output_slot.GetNumConnections() + + Returns: + int: Number of connected input slots. + ") GetNumConnections; + unsigned int GetNumConnections(); + + + %feature("docstring", + " + Retrieves connected input slot by index. + + The same result could be obtained by using square brackets: + + >>> output_slot = ... + >>> connected_input_slot = output_slot[0] + + Args: + index (int): Slot index. + + Returns: + IInputSlot: Borrowed reference to connected input slot with given index. + + Raises: + RuntimeError: If index out of bounds. + ") GetConnection; + armnn::IInputSlot* GetConnection(unsigned int index); + + %feature("docstring", + " + Sets tensor info for output slot. + Operation does not change TensorInfo ownership. + Args: + tensorInfo (TensorInfo): Output tensor info. + + ") SetTensorInfo; + void SetTensorInfo(const armnn::TensorInfo& tensorInfo); + + %feature("docstring", + " + Gets tensor info for output slot. + + Args: + tensorInfo (TensorInfo): Output tensor info. + + ") GetTensorInfo; + const armnn::TensorInfo& GetTensorInfo(); + + %feature("docstring", + " + Checks if tensor info was set previously. + + Returns: + bool: True if output tensor info was set, False - otherwise. + + ") IsTensorInfoSet; + bool IsTensorInfoSet(); + + %feature("docstring", + " + Connects this output slot with given input slot. + Input slot is updated with this output connection. + + Args: + destination (IInputSlot): Output tensor info. + + Returns: + int: Total number of connections. + + Raises: + RuntimeError: If input slot was already connected. + + ") Connect; + int Connect(IInputSlot& destination); + + %feature("docstring", + " + Disconnects this output slot from given input slot. + + Args: + slot (IInputSlot): Input slot to disconnect from. + + ") Disconnect; + void Disconnect(IInputSlot& slot); + + %feature("docstring", + " + Calculates the index of this slot for the layer. + + Returns: + int: Slot index. + + ") CalculateIndexOnOwner; + unsigned int CalculateIndexOnOwner(); + + %feature("docstring", + " + Returns the index of the layer. Same value as `IConnectableLayer.GetGuid`. + + Returns: + int: Layer id. + + ") GetOwningLayerGuid; + unsigned int GetOwningLayerGuid(); + +}; + +%extend IOutputSlot { + + armnn::IInputSlot* __getitem__(unsigned int index) { + return $self->GetConnection(index); + } + + unsigned int __len__() const { + return $self->GetNumConnections(); + } + +} + +%feature("docstring", +" +Interface for a layer that is connectable to other layers via `IInputSlot` and `IOutputSlot`. +The object implementing this interface is returned by `INetwork` when calling `add*Layer` methods. + +") IConnectableLayer; +%nodefaultctor IConnectableLayer; +%nodefaultdtor IConnectableLayer; +class IConnectableLayer +{ +public: + %feature("docstring", + " + Returns the name of the layer. Name attribute is optional for a layer, thus + `None` value could be returned. + + Returns: + str: Layer name or `None`. + + ") GetName; + const char* GetName(); + + %feature("docstring", + " + Gets the number of input slots for the layer. + + Returns: + int: Number of input slots. + + ") GetNumInputSlots; + unsigned int GetNumInputSlots(); + + %feature("docstring", + " + Gets the number of output slots for the layer. + + Returns: + int: Number of output slots. + + ") GetNumOutputSlots; + unsigned int GetNumOutputSlots(); + + %feature("docstring", + " + Gets the input slot by index. + + Args: + index (int): Slot index. + + Returns: + IInputSlot: Borrowed reference to input slot. + + ") GetInputSlot; + armnn::IInputSlot& GetInputSlot(unsigned int index); + + %feature("docstring", + " + Gets the output slot by index. + + Args: + index (int): Slot index. + + Returns: + IOutputSlot: Borrowed reference to output slot. + + ") GetOutputSlot; + armnn::IOutputSlot& GetOutputSlot(unsigned int index); + + + %feature("docstring", + " + Gets the unique layer id (within one process). + Guid is generated and assigned automatically when the layer is created. + + Returns: + int: The unique layer id. + + ") GetGuid; + unsigned int GetGuid(); +}; + +%feature("docstring", + " + Interface for a network object. Network objects contain the whole computation graph, made up of different layers connected together. + + INetwork objects can be constructed manually or obtained by using parsers. INetwork objects are used to create optimized networks, see `Optimize`. + + ") INetwork; +%nodefaultctor INetwork; +%nodefaultdtor INetwork; +class INetwork +{ +public: + + %feature("docstring", + " + Adds an input layer to the network. Input layers are placed at the start of a network and used for feeding input data during inference. + + Args: + id (int): User generated id to uniquely identify a particular input. The same id needs to be specified + when passing the inputs to the IRuntime::EnqueueWorkload() function. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddInputLayer; + armnn::IConnectableLayer* AddInputLayer(int id, const char* name = nullptr); + + %feature("docstring", + " + Adds an addition layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddAdditionLayer; + armnn::IConnectableLayer* AddAdditionLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds an output layer to the network. Output layer is the final layer in your network. + + Args: + id (int): User generated id to uniquely identify a particular input. The same id needs to be specified + when passing the inputs to `IRuntime.EnqueueWorkload()`. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddOutputLayer; + armnn::IConnectableLayer* AddOutputLayer(int id, const char* name = nullptr); + + + %feature("docstring", + " + Adds an Activation layer to the network. Type of activation is decided by activationDescriptor. + + Args: + activationDescriptor (ActivationDescriptor): ActivationDescriptor to configure the activation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddActivationLayer; + armnn::IConnectableLayer* AddActivationLayer(const ActivationDescriptor& activationDescriptor, + const char* name = nullptr); + + + %feature("docstring", + " + Adds an ArgMinMax layer to the network. + + Args: + desc (ArgMinMaxDescriptor): Parameters for the ArgMinMax layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddArgMinMaxLayer; + armnn::IConnectableLayer* AddArgMinMaxLayer(const armnn::ArgMinMaxDescriptor& desc, + const char* name = nullptr); + + + %feature("docstring", + " + Adds a Batch Normalization layer to the network. + + Args: + mean (ConstTensor): Pre-calculated mean for each channel. + variance (ConstTensor): Pre-calculated variance for each channel. + beta (ConstTensor): Per-channel additive factor. + gamma (ConstTensor): Per-channel multiplicative factor. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddBatchNormalizationLayer; + armnn::IConnectableLayer* AddBatchNormalizationLayer(const armnn::BatchNormalizationDescriptor& desc, + const armnn::ConstTensor& mean, + const armnn::ConstTensor& variance, + const armnn::ConstTensor& beta, + const armnn::ConstTensor& gamma, + const char* name = nullptr); + + + %feature("docstring", + " + Adds a Batch To Space ND layer to the network. + + Args: + batchToSpaceNdDescriptor (BatchToSpaceNdDescriptor): Configuration parameters for the layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddBatchToSpaceNdLayer; + armnn::IConnectableLayer* AddBatchToSpaceNdLayer(const armnn::BatchToSpaceNdDescriptor& batchToSpaceNdDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Comparison layer to the network. + + Args: + comparisonDescriptor (ComparisonDescriptor): Configuration parameters for the layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddComparisonLayer; + armnn::IConnectableLayer* AddComparisonLayer(const armnn::ComparisonDescriptor& comparisonDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Concatenation layer to the network. + + Args: + concatDescriptor (ConcatDescriptor): Parameters to configure the Concatenation layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddConcatLayer; + armnn::IConnectableLayer* AddConcatLayer(const armnn::ConcatDescriptor& concatDescriptor, + const char* name = nullptr); + + + %feature("docstring", + " + Adds a layer with no inputs and a single output, which always corresponds to the passed in constant tensor. + + Args: + input (ConstTensor): Tensor to be provided as the only output of the layer. The layer will maintain + its own copy of the tensor data, meaning the memory referenced by input can + be freed or reused after this function is called. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddConstantLayer; + armnn::IConnectableLayer* AddConstantLayer(const armnn::ConstTensor& input, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Depth To Space layer to the network. + + Args: + depthToSpaceDescriptor (DepthToSpaceDescriptor): Parameters for the depth to space operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddDepthToSpaceLayer; + armnn::IConnectableLayer* AddDepthToSpaceLayer(const armnn::DepthToSpaceDescriptor& depthToSpaceDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Dequantize layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddDequantizeLayer; + armnn::IConnectableLayer* AddDequantizeLayer(const char* name = nullptr); + + + %feature("docstring", + " + Adds a Detection PostProcess layer to the network. Detection PostProcess is a custom layer for SSD MobilenetV1. + + Args: + descriptor (DetectionPostProcessDescriptor): Description of the Detection PostProcess layer. + anchors (ConstTensor): Tensor for anchors. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddDetectionPostProcessLayer; + armnn::IConnectableLayer* AddDetectionPostProcessLayer( + const armnn::DetectionPostProcessDescriptor& descriptor, + const armnn::ConstTensor& anchors, + const char* name = nullptr); + + + %feature("docstring", + " + Adds a Division layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddDivisionLayer; + armnn::IConnectableLayer* AddDivisionLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds an Elementwise Unary layer to the network. Type of unary operation to use is decided by elementwiseUnaryDescriptor. Unary operations supported are (Abs, Exp, Neg, Rsqrt, Sqrt) + + Args: + elementwiseUnaryDescriptor (ElementwiseUnaryDescriptor): ElementwiseUnaryDescriptor to configure the choice of unary operation added to the network. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddElementwiseUnaryLayer; + armnn::IConnectableLayer* AddElementwiseUnaryLayer(const ElementwiseUnaryDescriptor& elementwiseUnaryDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Floor layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddFloorLayer; + armnn::IConnectableLayer* AddFloorLayer(const char* name = nullptr); + + %feature("docstring", + " + Add Gather layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddGatherLayer; + armnn::IConnectableLayer* AddGatherLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds an Instance Normalization layer to the network. + + Args: + desc (InstanceNormalizationDescriptor): Parameters for the instance normalization operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddInstanceNormalizationLayer; + armnn::IConnectableLayer* AddInstanceNormalizationLayer(const armnn::InstanceNormalizationDescriptor& desc, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Log Softmax layer to the network. + + Args: + desc (SoftmaxDescriptor): parameters to configure the log softmax. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddLogSoftmaxLayer; + armnn::IConnectableLayer* AddLogSoftmaxLayer(const armnn::LogSoftmaxDescriptor& logSoftmaxDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds an L2 Normalization layer to the network. + Normalization is performed along dimension 1, but requires a 4d input. + + Args: + desc (L2NormalizationDescriptor): Parameters for the L2 normalization operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddL2NormalizationLayer; + armnn::IConnectableLayer* AddL2NormalizationLayer(const armnn::L2NormalizationDescriptor& desc, + const char* name = nullptr); + + %feature("docstring", + " + Add a Long Short-Term Memory layer to the network. + + Args: + descriptor (LstmDescriptor): Parameters for the Lstm operation. + params (LstmInputParams): Weights and biases for the LSTM cell. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddLstmLayer; + armnn::IConnectableLayer* AddLstmLayer(const armnn::LstmDescriptor& descriptor, + const armnn::LstmInputParams& params, + const char* name = nullptr); + + %feature("docstring", + " + Add a Maximum layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddMaximumLayer; + armnn::IConnectableLayer* AddMaximumLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Mean layer to the network. + + Args: + meanDescriptor (meanDescriptor): Parameters for the mean operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddMeanLayer; + armnn::IConnectableLayer* AddMeanLayer(const armnn::MeanDescriptor& meanDescriptor, const char* name = nullptr); + + %feature("docstring", + " + Adds a Merge layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddMergeLayer; + armnn::IConnectableLayer* AddMergeLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Minimum layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddMinimumLayer; + armnn::IConnectableLayer* AddMinimumLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Multiplication layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddMultiplicationLayer; + armnn::IConnectableLayer* AddMultiplicationLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Normalization layer to the network. + + Args: + normalizationDescriptor (NormalizationDescriptor): Parameters to configure the normalization. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddNormalizationLayer; + armnn::IConnectableLayer* AddNormalizationLayer(const armnn::NormalizationDescriptor& normalizationDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Pad layer to the network. + + Args: + padDescriptor (PadDescriptor): Padding configuration for the layer. See `PadDescriptor` for more details. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddPadLayer; + armnn::IConnectableLayer* AddPadLayer(const armnn::PadDescriptor& padDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Permute layer to the network. + + Args: + permuteDescriptor (PermuteDescriptor): Configuration of the permutation layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddPermuteLayer; + armnn::IConnectableLayer* AddPermuteLayer(const armnn::PermuteDescriptor& permuteDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Pooling layer to the network. Type of pooling is decided by the configuration. + + Args: + pooling2dDescriptor (Pooling2dDescriptor): Configuration for the pooling layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddPooling2dLayer; + armnn::IConnectableLayer* AddPooling2dLayer(const armnn::Pooling2dDescriptor& pooling2dDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a PReLU layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddPreluLayer; + armnn::IConnectableLayer* AddPreluLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Quantize layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddQuantizeLayer; + armnn::IConnectableLayer* AddQuantizeLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Quantized Long Short-Term Memory layer to the network. + + Args: + params (`QuantizedLstmInputParams`): The weights and biases for the Quantized LSTM cell. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddQuantizedLstmLayer; + armnn::IConnectableLayer* AddQuantizedLstmLayer(const armnn::QuantizedLstmInputParams& params, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Reshape layer to the network. + + Args: + reshapeDescriptor (ReshapeDescriptor): Parameters for the reshape operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddReshapeLayer; + armnn::IConnectableLayer* AddReshapeLayer(const armnn::ReshapeDescriptor& reshapeDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Resize layer to the network. + + Args: + resizeDescriptor (ResizeDescriptor): Configuration for the resize layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddResizeLayer; + armnn::IConnectableLayer* AddResizeLayer(const armnn::ResizeDescriptor& resizeDescriptor, + const char* name = nullptr); + + + %feature("docstring", + " + Adds a Slice layer to the network. + + Args: + sliceDescriptor (SliceDescriptor): Descriptor to configure the slice operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSliceLayer; + armnn::IConnectableLayer* AddSliceLayer(const armnn::SliceDescriptor& sliceDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Softmax layer to the network. + + If the data type is `DataType_QuantisedAsymm8`, then the output quantization parameters + must have a scale of 1/256 and an offset of 0. + + Args: + softmaxDescriptor (SoftmaxDescriptor): Configuration for the softmax layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSoftmaxLayer; + armnn::IConnectableLayer* AddSoftmaxLayer(const armnn::SoftmaxDescriptor& softmaxDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Space To Batch layer to the network. + + Args: + spaceToBatchNdDescriptor (SpaceToBatchNdDescriptor): Configuration for the space to batch layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSpaceToBatchNdLayer; + armnn::IConnectableLayer* AddSpaceToBatchNdLayer(const armnn::SpaceToBatchNdDescriptor& spaceToBatchNdDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a space to depth layer to the network. + + Args: + spaceToDepthDescriptor (SpaceToDepthDescriptor): Parameters for the space to depth operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSpaceToDepthLayer; + armnn::IConnectableLayer* AddSpaceToDepthLayer(const armnn::SpaceToDepthDescriptor& spaceToDepthDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Splitter layer to the network. + + Args: + splitterDescriptor (SplitterDescriptor): Parameters to configure the splitter layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSplitterLayer; + armnn::IConnectableLayer* AddSplitterLayer(const armnn::SplitterDescriptor& splitterDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Stack layer to the network. + + Args: + descriptor (StackDescriptor): Descriptor to configure the stack layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddStackLayer; + armnn::IConnectableLayer* AddStackLayer(const armnn::StackDescriptor& descriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a StandIn layer to the network. + + Args: + descriptor (StandInDescriptor): Parameters to configure the standIn layer. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddStandInLayer; + armnn::IConnectableLayer* AddStandInLayer(const armnn::StandInDescriptor& descriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Strided Slice layer to the network. + + Args: + stridedSliceDescriptor (StridedSliceDescriptor): Parameters for the strided slice operation. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddStridedSliceLayer; + armnn::IConnectableLayer* AddStridedSliceLayer(const armnn::StridedSliceDescriptor& stridedSliceDescriptor, + const char* name = nullptr); + + %feature("docstring", + " + Adds a Subtraction layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSubtractionLayer; + armnn::IConnectableLayer* AddSubtractionLayer(const char* name = nullptr); + + %feature("docstring", + " + Adds a Switch layer to the network. + + Args: + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddSwitchLayer; + armnn::IConnectableLayer* AddSwitchLayer(const char* name = nullptr); + +}; + +%extend INetwork { + + INetwork() { + return armnn::INetwork::CreateRaw(); + } + + ~INetwork() { + armnn::INetwork::Destroy($self); + } + + %feature("docstring", + " + Adds a Fully Connected layer to the network. Also known as a Linear or Dense layer. + + Args: + fullyConnectedDescriptor (FullyConnectedDescriptor): Description of the fully connected layer. + weights (ConstTensor): Tensor for the weights data. + biases (ConstTensor): Optional tensor for the bias data. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddFullyConnectedLayer; + armnn::IConnectableLayer* AddFullyConnectedLayer(const armnn::FullyConnectedDescriptor& fullyConnectedDescriptor, + const armnn::ConstTensor& weights, + armnn::ConstTensor* biases = nullptr, + const char* name = nullptr) { + + if (biases) { + return $self->AddFullyConnectedLayer(fullyConnectedDescriptor, weights, + armnn::Optional(*biases), name); + } else { + return $self->AddFullyConnectedLayer(fullyConnectedDescriptor, weights, + armnn::Optional(), name); + } + + } + + %feature("docstring", + " + Adds a 2D Transpose Convolution layer to the network. + + Args: + descriptor (TransposeConvolution2dDescriptor): Descriptor containing all parameters to configure this layer. + weights (ConstTensor): Tensor for the weights data. + biases (ConstTensor): Optional tensor for the bias data. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddTransposeConvolution2dLayer; + armnn::IConnectableLayer* AddTransposeConvolution2dLayer(const armnn::TransposeConvolution2dDescriptor& descriptor, + const armnn::ConstTensor& weights, + armnn::ConstTensor* biases = nullptr, + const char* name = nullptr){ + + if (biases) { + return $self->AddTransposeConvolution2dLayer(descriptor, weights, + armnn::Optional(*biases), name); + } else { + return $self->AddTransposeConvolution2dLayer(descriptor, weights, + armnn::Optional(), name); + } + } + + + %feature("docstring", + " + Adds a 2D Convolution layer to the network. + + Args: + convolution2dDescriptor (Convolution2dDescriptor): Description of the 2D convolution layer. + weights (ConstTensor): Tensor for the weights data. + biases (ConstTensor): Optional tensor for the bias data. If specified, must match the output tensor shape. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddConvolution2dLayer; + armnn::IConnectableLayer* AddConvolution2dLayer(const armnn::Convolution2dDescriptor& convolution2dDescriptor, + const armnn::ConstTensor& weights, + armnn::ConstTensor* biases = nullptr, + const char* name = nullptr) { + + if (biases) { + return $self->AddConvolution2dLayer(convolution2dDescriptor, weights, + armnn::Optional(*biases), name); + } else { + return $self->AddConvolution2dLayer(convolution2dDescriptor, weights, + armnn::Optional(), name); + } + } + + %feature("docstring", + " + Adds a 2D Depthwise Convolution layer to the network. + + Args: + convolution2dDescriptor (DepthwiseConvolution2dDescriptor): Description of the 2D depthwise convolution layer. + weights (ConstTensor): Tensor for the weights. Expected format: [channelMultiplier, inputChannels, height, width]. + biases (ConstTensor): Optional tensor for the bias data. If specified, must match the output tensor shape. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") AddDepthwiseConvolution2dLayer; + + armnn::IConnectableLayer* AddDepthwiseConvolution2dLayer( + const armnn::DepthwiseConvolution2dDescriptor& convolution2dDescriptor, + const armnn::ConstTensor& weights, + const armnn::ConstTensor* biases = nullptr, + const char* name = nullptr) { + + if (biases) { + return $self->AddDepthwiseConvolution2dLayer(convolution2dDescriptor, weights, + armnn::Optional(*biases), name); + } else { + return $self->AddDepthwiseConvolution2dLayer(convolution2dDescriptor, weights, + armnn::Optional(), name); + } + } +} + +%feature("docstring", + " + Interface class for an optimzied network object. Optimized networks are obtained after running `Optimize` on + an `INetwork` object. + Optimized networks are passed to `EnqueueWorkload`. + + Args: + convolution2dDescriptor (DepthwiseConvolution2dDescriptor): Description of the 2D depthwise convolution layer. + weights (ConstTensor): Tensor for the weights. Expected format: [channelMultiplier, inputChannels, height, width]. + biases (ConstTensor): Optional tensor for the bias data. If specified, must match the output tensor shape. + name (str): Optional name for the layer. + + Returns: + IConnectableLayer: Interface for configuring the layer. + ") IOptimizedNetwork; +%nodefaultctor IOptimizedNetwork; +%nodefaultdtor IOptimizedNetwork; +class IOptimizedNetwork +{ +}; + +%extend IOptimizedNetwork { + + ~IOptimizedNetwork() { + armnn::IOptimizedNetwork::Destroy($self); + } + + %feature("docstring", + " + Saves optimized network graph as dot file. + + Args: + fileName (str): File path to save to. + Raises: + RuntimeError: If serialization failure. + ") SerializeToDot; + + void SerializeToDot(const std::string& fileName) { + std::ofstream dot; + dot.open(fileName); + if(!dot.is_open()) + { + throw armnn::Exception("Failed to open dot file"); + } else { + armnn::Status status = $self->SerializeToDot(dot); + dot.close(); + if(status == armnn::Status::Failure) + { + throw armnn::Exception("Failed to serialize to dot"); + } + } + }; +} +} + +%{ + std::pair> Optimize(const armnn::INetwork* network, + const std::vector& backendPreferences, + const armnn::IDeviceSpec& deviceSpec, + const armnn::OptimizerOptions& options = armnn::OptimizerOptions()) + { + std::vector errorMessages; + armnn::IOptimizedNetwork* optimizedNetwork = armnn::Optimize(*network, backendPreferences, deviceSpec, + options, armnn::Optional &>(errorMessages)).release(); + + if(!optimizedNetwork) + { + std::string errorString; + + for (auto error : errorMessages) { + errorString.append(error); + } + + throw armnn::Exception(errorString); + } + + return std::make_pair(optimizedNetwork, errorMessages); + }; +%} + +%feature("docstring", + " + Create an optimized version of the given network. Should be called before loading a network into the runtime. + + Examples: + Optimize a loaded network ready for inference. + >>> parser = ann.ITfLiteParser() + >>> network = parser.CreateNetworkFromBinaryFile('./model.tflite') + >>> + >>> preferredBackends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] + >>> opt_network, messages = ann.Optimize(network, preferredBackends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + Args: + network (INetwork): INetwork description of the network to be optimized. + backendPreferences (list): The choice of the backend ordered by user preferences. See `BackendId`. + deviceSpec (IDeviceSpec): DeviceSpec object as queried from the runtime. See `IRuntime.GetDeviceSpec`. + options (OptimizerOptions): Object with optimizer configuration options. + + Returns: + tuple: (`IOptimizedNetwork`, a tuple of failures or warnings). + + Raises: + RuntimeError: If process fails. + ") Optimize; + +%optimize_typemap_out; +std::pair> Optimize(const armnn::INetwork* network, + const std::vector& backendPreferences, + const armnn::IDeviceSpec& deviceSpec, + const armnn::OptimizerOptions& options = OptimizerOptions()); +%clear_optimize_typemap_out; diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_profiler.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_profiler.i new file mode 100644 index 0000000..2449d77 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_profiler.i @@ -0,0 +1,82 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/IProfiler.hpp" +%} + +namespace armnn +{ + +%feature("docstring", +" +Interface for profiling Arm NN. See `IRuntime.GetProfiler`. + +IProfiler object allows you to enable profiling and get various profiling results. + +") IProfiler; +%nodefaultctor IProfiler; +%nodefaultdtor IProfiler; +class IProfiler +{ +public: + + %feature("docstring", + " + Sets the profiler to start/stop profiling. + + Args: + enableProfiling (bool): Flag to enable/disable profiling. + + ") EnableProfiling; + + void EnableProfiling(bool enableProfiling); + + %feature("docstring", + " + Checks if profiling is enabled. + + Returns: + bool: If profiling is enabled or not. + + ") IsProfilingEnabled; + + bool IsProfilingEnabled(); +}; + +%extend IProfiler { + + %feature("docstring", + " + Gets the string value of the profiling events analysis log. + + Returns: + str: The profiling events analysis log. + + ") event_log; + + std::string event_log() + { + std::ostringstream oss; + $self->AnalyzeEventsAndWriteResults(oss); + return oss.str(); + } + + %feature("docstring", + " + Gets the profiling log as the JSON string. + + Returns: + str: Profiling log as JSON formatted string. + + ") as_json; + + std::string as_json() + { + std::ostringstream oss; + $self->Print(oss); + return oss.str(); + } +} +} diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_runtime.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_runtime.i new file mode 100644 index 0000000..e9a895e --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_runtime.i @@ -0,0 +1,254 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/IRuntime.hpp" +#include +#include +#include +%} + +namespace std { + %template() pair; + %template(IntPair) pair; + %template(ConstTensorPair) pair; + %template(TensorPair) pair; + + %template(InputTensorsVector) vector>; + %template(OutputTensorsVector) vector>; +} + +%include + +%shared_ptr(IGpuAccTunedParameters); + +#pragma SWIG nowarn=SWIGWARN_PARSE_NESTED_CLASS + +%{ +typedef armnn::IRuntime::CreationOptions CreationOptions; +%} + +struct CreationOptions +{ + %feature("docstring", + " + Structure for holding creation options. For majority of cases it is fine to leave values at default. + + Contains: + m_GpuAccTunedParameters (IGpuAccTunedParameters): If set, uses the GpuAcc tuned parameters from the given object + when executing GPU workloads. It will also be updated with new + tuned parameters if it is configured to do so. + + m_EnableGpuProfiling (bool): Setting this flag will allow the user to obtain GPU profiling information from + the runtime. + + m_DynamicBackendsPath (string): Setting this value will override the paths set by the DYNAMIC_BACKEND_PATHS + compiler directive. Only a single path is allowed for the override. + + ") CreationOptions; + + CreationOptions(); + std::shared_ptr m_GpuAccTunedParameters; + bool m_EnableGpuProfiling; + std::string m_DynamicBackendsPath; +}; + +namespace armnn +{ + +struct INetworkProperties +{ + %feature("docstring", + " + Structure for holding network properties. + + Contains: + m_ImportEnabled (bool): Enable import. + + m_ExportEnabled (bool): Enable export. + + ") INetworkProperties; + INetworkProperties(bool importEnabled = false, bool exportEnabled = false); + + const bool m_ImportEnabled; + const bool m_ExportEnabled; +}; + +%feature("docstring", +" +Interface for runtime objects. + +Runtime objects are responsible for performing inference on an `IOptimizedNetwork`. + +Args: + options (CreationOptions): CreationOptions data struct. + +") IRuntime; +%nodefaultctor IRuntime; +class IRuntime +{ +public: + + %ignore + armnn::IRuntime::UnloadNetwork(NetworkId networkId); + + %ignore + armnn::IRuntime::EnqueueWorkload(NetworkId networkId, + const std::vector>& inputTensors, + const std::vector>& outputTensors); + + %feature("docstring", + " + Get information relating to networks input tensor. + + Args: + networkId (int): Unique ID of the network being run. + layerId (int): Unique ID of the input layer. + + Returns: + TensorInfo: Information relating to the input tensor a network. + ") GetInputTensorInfo; + armnn::TensorInfo GetInputTensorInfo(int networkId, int layerId); + + %feature("docstring", + " + Get information relating to networks output tensor. + + Args: + networkId (int): Unique ID of the network being run. + layerId (int): Unique ID of the output layer. + + Returns: + TensorInfo: Information relating to the output tensor a network. + ") GetOutputTensorInfo; + armnn::TensorInfo GetOutputTensorInfo(int networkId, int layerId); + + %feature("docstring", + " + Get information relating supported compute backends on current device. + + Returns: + IDeviceSpec: Device spec information detailing all supported backends on current platform. + ") GetDeviceSpec; + const IDeviceSpec& GetDeviceSpec(); +}; + +%extend IRuntime { + //tell python to disown the IOptimizedNetwork pointer + //because IRuntime takes ownership + %typemap(in) armnn::IOptimizedNetwork* { + if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, SWIG_POINTER_DISOWN))) { + SWIG_exception_fail(SWIG_TypeError, "in method '$symname', argument 2 of type armnn::IOptimizedNetwork*"); + } + } + + %feature("docstring", + " + Loads a complete network into the IRuntime. + The runtime takes ownership of the network once passed in. + Args: + network (IOptimizedNetwork): An optimized network to load into the IRuntime. + networkProperties (INetworkProperties): Properties that allows the user to opt-in to import/export behavior. Default: None. + Returns: + tuple: (int, str) Network id and non fatal failure or warning messsages. + Raises: + RuntimeError: If process fails. + ") LoadNetwork; + + std::pair LoadNetwork(armnn::IOptimizedNetwork* network, + const INetworkProperties* networkProperties = nullptr) + { + armnn::IOptimizedNetworkPtr netPtr(network, &armnn::IOptimizedNetwork::Destroy); + armnn::NetworkId networkIdOut; + std::string errorString; + armnn::Status status; + + if (networkProperties) { + status = $self->LoadNetwork(networkIdOut, std::move(netPtr), errorString, *networkProperties); + } else { + status = $self->LoadNetwork(networkIdOut, std::move(netPtr), errorString); + } + + if(status == armnn::Status::Failure) + { + throw armnn::Exception(errorString); + } + + auto net_id_int = static_cast(networkIdOut); + return std::make_pair(net_id_int, errorString); + }; + + %typemap(in) armnn::IOptimizedNetwork*; + %feature("docstring", + " + Calling this function will perform an inference on your network. + + Args: + networkId (int): Unique ID of the network to run. + inputTensors (list): A list of tuples (int, `ConstTensor`), see `make_input_tensors`. + outputTensors (list): A list of tuples (int, `Tensor`), see `make_output_tensors`. + + ") EnqueueWorkload; + void EnqueueWorkload(int networkId, const std::vector>& inputTensors, + const std::vector>& outputTensors) { + armnn::Status status = $self->EnqueueWorkload(networkId, inputTensors, outputTensors); + + if(status == armnn::Status::Failure) + { + throw armnn::Exception("Failed to enqueue workload for network."); + } + }; + + %feature("docstring", + " + Unload a currently loaded network from the runtime. + + Args: + networkId (int): Unique ID of the network to unload. + + ") UnloadNetwork; + void UnloadNetwork(int networkId) { + armnn::Status status = $self->UnloadNetwork(networkId); + if(status == armnn::Status::Failure) + { + throw armnn::Exception("Failed to unload network."); + } + }; + + %feature("docstring", + " + Returns the IProfiler instance registered against the working thread, and stored on the loaded network. + Be aware that if the runtime has unloaded the network, or if the runtime is destroyed, + that the IProfiler instance will also be destroyed, and will cause a segmentation fault. + + Args: + networkId (int): The ID of the loaded network you want to profile. + + Returns: + IProfiler: IProfiler instance the given loaded network has stored. + + Raises: + RuntimeError: If no profiler is found. + ") GetProfiler; + + armnn::IProfiler* GetProfiler(int networkId) { + std::shared_ptr profiler = $self->GetProfiler(networkId); + if (nullptr == profiler) { + throw armnn::Exception("Failed to get profiler"); + } + return profiler.get(); + }; + + ~IRuntime() { + armnn::IRuntime::Destroy($self); + } + + IRuntime(const CreationOptions& options) { + return armnn::IRuntime::CreateRaw(options); + } + +} + +} + diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_tensor.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_tensor.i new file mode 100644 index 0000000..0edf67d --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_tensor.i @@ -0,0 +1,333 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/Tensor.hpp" +%} + +%include +%include + +namespace armnn +{ + +%feature("docstring", +" +Class for holding the shape information of an Arm NN tensor. + +This class is iterable. You can iterate over it to get each value of the Tensor shape. + +Examples: + Obtain tensor shape information as a list. + >>> import pyarmnn as ann + >>> import numpy as np + >>> + >>> tensor_info = ann.TensorInfo(ann.TensorShape((4, 2, 1, 3)), ann.DataType_Float32) + >>> tensor = ann.ConstTensor(tensor_info, np.ones([4, 2, 1, 3], dtype=np.float32)) + >>> print(list(tensor.GetShape())) + [4, 2, 1, 3] + +") TensorShape; +class TensorShape +{ + // Make TensorShape iterable so we can return shape dims easily. + %pythoncode %{ + def __iter__(self): + for dim in range(self.GetNumDimensions()): + yield self[dim] + %} + + +public: + %tensor_shape_typemap(unsigned int numDimensions, const unsigned int* dimensionSizes); + TensorShape(unsigned int numDimensions, const unsigned int* dimensionSizes); + %clear_tensor_shape_typemap(unsigned int numDimensions, const unsigned int* dimensionSizes); + + %feature("docstring", + " + Returns the number of dimensions in this TensorShape. + + Returns: + int: The number of dimensions in this TensorShape. + + ") GetNumDimensions; + unsigned int GetNumDimensions() const; + + %feature("docstring", + " + Returns the total number of elements for a tensor with this TensorShape. + + Returns: + int: The total number of elements for a tensor with this TensorShape. + + ") GetNumElements; + unsigned int GetNumElements() const; + +}; + +%extend TensorShape { + + unsigned int __getitem__(unsigned int i) const { + return $self->operator[](i); + } + void __setitem__(unsigned int i, unsigned int val) { + $self->operator[](i) = val; + } + + std::string __str__() { + std::string dim = "NumDimensions: " + std::to_string($self->GetNumDimensions()); + std::string elm = "NumElements: " + std::to_string($self->GetNumElements()); + + std::string shapeStr = "TensorShape{Shape("; + + auto numDimensions = $self->GetNumDimensions(); + auto sizeDims = $self->GetNumDimensions(); + for (unsigned int i = 0; i < numDimensions; i++) { + shapeStr += std::to_string($self->operator[](i)); + + if (sizeDims - 1 > 0) { + shapeStr += ", "; + } + sizeDims--; + } + shapeStr = shapeStr + "), " + dim + ", " + elm + "}"; + return shapeStr; + } + +} + + +%feature("docstring", +" +Class for holding the tensor information of an Arm NN tensor such as quantization, datatype, shape etc. + +") TensorInfo; +class TensorInfo +{ +public: + TensorInfo(); + + TensorInfo(const TensorInfo& other); + + TensorInfo(const TensorShape& shape, DataType dataType, + float quantizationScale = 0.0f, int32_t quantizationOffset = 0); + + %feature("docstring", + " + Get the tensor shape. + + Return: + TensorShape: Current shape of the tensor. + + ") GetShape; + TensorShape& GetShape(); + + %feature("docstring", + " + Set the tensor shape. Must have the same number of elements as current tensor. + + Args: + newShape (TensorShape): New tensor shape to reshape to. + + ") SetShape; + void SetShape(const TensorShape& newShape); + + %feature("docstring", + " + Returns the number of dimensions in this Tensor. + + Returns: + int: The number of dimensions in this Tensor. + + ") GetNumDimensions; + unsigned int GetNumDimensions() const; + + %feature("docstring", + " + Returns the total number of elements for this Tensor. + + Returns: + int: The total number of elements for this Tensor. + + ") GetNumElements; + unsigned int GetNumElements() const; + + %feature("docstring", + " + Get the tensor datatype. + + Returns: + DataType: Current tensor DataType. + + ") GetDataType; + DataType GetDataType() const; + + %feature("docstring", + " + Set the tensor datatype. + + Args: + type (DataType): DataType to set the tensor to. + + ") SetDataType; + void SetDataType(DataType type); + + %feature("docstring", + " + Get the value of the tensors quantization scale. + + Returns: + float: Tensor quantization scale value. + + ") GetQuantizationScale; + float GetQuantizationScale() const; + + %feature("docstring", + " + Get the value of the tensors quantization offset. + + Returns: + int: Tensor quantization offset value. + + ") GetQuantizationOffset; + int32_t GetQuantizationOffset() const; + + %feature("docstring", + " + Set the value of the tensors quantization scale. + + Args: + scale (float): Scale value to set. + + ") SetQuantizationScale; + void SetQuantizationScale(float scale); + + %feature("docstring", + " + Set the value of the tensors quantization offset. + + Args: + offset (int): Offset value to set. + + ") SetQuantizationOffset; + void SetQuantizationOffset(int32_t offset); + + %feature("docstring", + " + Returns true if the tensor is a quantized data type. + + Returns: + bool: True if the tensor is a quantized data type. + + ") IsQuantized; + bool IsQuantized() const; + + + + %feature("docstring", + " + Check that the types are the same and, if quantize, that the quantization parameters are the same. + + Returns: + bool: True if matched, else False. + + ") IsTypeSpaceMatch; + bool IsTypeSpaceMatch(const TensorInfo& other) const; + + %feature("docstring", + " + Get the number of bytes needed for this tensor. + + Returns: + int: Number of bytes consumed by this tensor. + + ") GetNumBytes; + unsigned int GetNumBytes() const; + +}; + +%extend TensorInfo { + + std::string __str__() { + const std::string tmp = "TensorInfo{DataType: " + std::to_string(static_cast($self->GetDataType())) + + ", IsQuantized: " + std::to_string($self->IsQuantized()) + + ", QuantizationScale: " + std::to_string( $self->GetQuantizationScale()) + + ", QuantizationOffset: " + std::to_string($self->GetQuantizationOffset()) + + ", NumDimensions: " + std::to_string($self->GetNumDimensions()) + + ", NumElements: " + std::to_string($self->GetNumElements()) + "}"; + return tmp; + } + +} + +class Tensor +{ +public: + ~Tensor(); + Tensor(); + Tensor(const Tensor& other); + + %mutable_memory(void* memory); + Tensor(const TensorInfo& info, void* memory); + %clear_mutable_memory(void* memory); + + const TensorInfo& GetInfo() const; + const TensorShape& GetShape() const; + + DataType GetDataType() const; + unsigned int GetNumDimensions() const; + unsigned int GetNumBytes() const; + unsigned int GetNumElements() const; + + /* we want to disable getting the memory area from here - forcing use of get_memory_area() in public api. + void* GetMemoryArea() const;*/ +}; + +%extend Tensor { + + std::string __str__() { + const std::string tmp = "Tensor{DataType: " + std::to_string(static_cast($self->GetDataType())) + + ", NumBytes: " + std::to_string($self->GetNumBytes()) + + ", NumDimensions: " + std::to_string( $self->GetNumDimensions()) + + ", NumElements: " + std::to_string($self->GetNumElements()) + "}"; + return tmp; + } +} + +class ConstTensor +{ +public: + ~ConstTensor(); + ConstTensor(); + ConstTensor(const Tensor& other); + ConstTensor(const ConstTensor& other); + + %const_memory(const void* memory); + ConstTensor(const TensorInfo& info, const void* memory); + %clear_const_memory(const void* memory); + + const TensorInfo& GetInfo() const; + const TensorShape& GetShape() const; + + DataType GetDataType() const; + unsigned int GetNumDimensions() const; + unsigned int GetNumBytes() const; + unsigned int GetNumElements() const; + + /* we want to disable getting the memory area from here - forcing use of get_memory_area() in public api. + void* GetMemoryArea() const;*/ +}; + +%extend ConstTensor { + + std::string __str__() { + const std::string tmp = "ConstTensor{DataType: " + std::to_string(static_cast($self->GetDataType())) + + ", NumBytes: " + std::to_string($self->GetNumBytes()) + + ", NumDimensions: " + std::to_string( $self->GetNumDimensions()) + + ", NumElements: " + std::to_string($self->GetNumElements()) + "}"; + return tmp; + } +} + +} diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_types.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_types.i new file mode 100644 index 0000000..e0ad174 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_types.i @@ -0,0 +1,140 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/Types.hpp" +%} + +%include + + +namespace armnn +{ + +%feature("docstring", +" +Vector used to permute a tensor. + +For a 4-d tensor laid out in a memory with the format (Batch Element, Height, Width, Channels), +which is to be passed as an input to Arm NN, each source dimension is mapped to the corresponding +Arm NN dimension. The Batch dimension remains the same (0 -> 0). The source Height dimension is mapped +to the location of the Arm NN Height dimension (1 -> 2). Similar arguments are made for the Width and +Channels (2 -> 3 and 3 -> 1). This will lead to m_DimMappings pointing to the following array: +[ 0, 2, 3, 1 ]. + +Note that the mapping should be reversed if considering the case of Arm NN 4-d outputs (Batch Element, +Channels, Height, Width) being written to a destination with the format mentioned above. We now have +0 -> 0, 2 -> 1, 3 -> 2, 1 -> 3, which, when reordered, lead to the following m_DimMappings contents: +[ 0, 3, 1, 2 ]. + +Args: + dimMappings (list): Indicates how to translate tensor elements from a given source into the target destination, + when source and target potentially have different memory layouts. +") PermutationVector; + +class PermutationVector +{ +public: + using ValueType = unsigned int; + using SizeType = unsigned int; + + %permutation_vector_typemap(const ValueType *dimMappings, SizeType numDimMappings); + PermutationVector(const ValueType *dimMappings, SizeType numDimMappings); + %clear_permutation_vector_typemap(const ValueType *dimMappings, SizeType numDimMappings); + + + %feature("docstring", + " + Get the PermutationVector size. + + Return: + SizeType: Current size of the PermutationVector. + + ") GetSize; + SizeType GetSize(); + + %feature("docstring", + " + Checks if a specified permutation vector is its inverse + + Return: + bool: returns true if the specified Permutation vector is its inverse. + + ") IsInverse; + bool IsInverse(const PermutationVector& other); +}; + +%extend PermutationVector { + + unsigned int __getitem__(unsigned int i) const { + return $self->operator[](i); + } + + bool __eq__(PermutationVector other) { + int size = $self->GetSize(); + int otherSize = other.GetSize(); + if(size != otherSize) + { + return false; + } + for(int i = 0; i < size; ++i){ + if($self->operator[](i) != other[i]) + { + return false; + } + return true; + } + return true; + } +} + +} +%feature("docstring", +" +Interface for device specifications. Main use is to get information relating to what compute capability the device being used has. +") IDeviceSpec; + + +%feature("docstring", +" +Returns the backends supported by this compute device. + +Returns: + set: This devices supported backends. + +") GetSupportedBackends; + +%ignore ProfilingGuid; +%ignore PermutationVector; +#define ARMNN_DEPRECATED_ENUM // SWIG does not support C++ attributes, need this to help generate from Deprecated.hpp. +#define ARMNN_DEPRECATED_ENUM_MSG(message) // SWIG does not support C++ attributes, need this to help generate from Deprecated.hpp. +%include "armnn/Types.hpp" + + + +%extend armnn::IDeviceSpec { + + + std::string __str__() { + + std::string deviceStr = "IDeviceSpec { supportedBackends: ["; + + auto bends = $self->GetSupportedBackends(); + auto sizeBends = $self->GetSupportedBackends().size(); + for (std::unordered_set::const_iterator p = bends.begin(); p != bends.end(); ++p) { + + deviceStr += p->Get(); + + if (sizeBends - 1 > 0) { + deviceStr += ", "; + } + sizeBends--; + + } + deviceStr = deviceStr + "]}"; + + return deviceStr; + } + +} diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_types_utils.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_types_utils.i new file mode 100644 index 0000000..7a628b8 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_types_utils.i @@ -0,0 +1,28 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%{ +#include "armnn/TypesUtils.hpp" +%} + +namespace armnn +{ + constexpr unsigned int GetDataTypeSize(DataType dataType); + + constexpr const char* GetDataTypeName(DataType dataType); + + template + QuantizedType Quantize(float value, float scale, int32_t offset); + %template(Quantize_uint8_t) Quantize; + %template(Quantize_int8_t) Quantize; + %template(Quantize_int16_t) Quantize; + %template(Quantize_int32_t) Quantize; + + template + float Dequantize(QuantizedType value, float scale, int32_t offset); + %template(Dequantize_uint8_t) Dequantize; + %template(Dequantize_int8_t) Dequantize; + %template(Dequantize_int16_t) Dequantize; + %template(Dequantize_int32_t) Dequantize; +} \ No newline at end of file diff --git a/python/pyarmnn/src/pyarmnn/swig/standard_header.i b/python/pyarmnn/src/pyarmnn/swig/standard_header.i new file mode 100644 index 0000000..b26cb0d --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/standard_header.i @@ -0,0 +1,53 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%include "stl.i" +%include "cstring.i" +%include "std_string.i" +%include "std_vector.i" +%include "std_unordered_set.i" +%include "std_pair.i" +%include "stdint.i" +%include "carrays.i" +%include "exception.i" +%include "typemaps.i" +%include "std_iostream.i" + +%ignore *::operator=; +%ignore *::operator[]; + + +// Define exception typemap to wrap armnn exception into python exception. + +%exception{ + try { + $action + } catch (armnn::Exception &e) { + SWIG_exception(SWIG_RuntimeError, const_cast(e.what())); + } +}; + +%exception __getitem__ { + try { + $action + } catch (armnn::InvalidArgumentException &e) { + SWIG_exception(SWIG_ValueError, const_cast(e.what())); + } catch (const std::out_of_range &e) { + SWIG_exception(SWIG_IndexError, const_cast(e.what())); + } catch (const std::exception &e) { + SWIG_exception(SWIG_RuntimeError, const_cast(e.what())); + } +}; + +%exception __setitem__ { + try { + $action + } catch (armnn::InvalidArgumentException &e) { + SWIG_exception(SWIG_ValueError, const_cast(e.what())); + } catch (const std::out_of_range &e) { + SWIG_exception(SWIG_IndexError, const_cast(e.what())); + } catch (const std::exception &e) { + SWIG_exception(SWIG_RuntimeError, const_cast(e.what())); + } +}; diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/network_optimize.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/network_optimize.i new file mode 100644 index 0000000..accbf9e --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/network_optimize.i @@ -0,0 +1,41 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%define %optimize_typemap_out + %typemap(out) (std::pair>) { + PyObject * network = SWIG_NewPointerObj(SWIG_as_voidptr($1.first), SWIGTYPE_p_armnn__IOptimizedNetwork, SWIG_POINTER_OWN); + $result = PyTuple_New(2); + + // Convert vector to fixed-size tuple + std::vector strings = $1.second; + Py_ssize_t size = strings.size(); + + // New reference. Need to Py_DECREF + PyObject* errMsgTuple = PyTuple_New(size); + + if (!errMsgTuple) { + Py_XDECREF(errMsgTuple); + return PyErr_NoMemory(); + } + + for (Py_ssize_t i = 0; i < size; i++) { + // New reference. Need to Py_DECREF + PyObject *string = PyString_FromString(strings[i].c_str()); + + if (!string) { + Py_XDECREF(string); + return PyErr_NoMemory(); + } + PyTuple_SetItem(errMsgTuple, i, string); + } + + // Create result tuple + PyTuple_SetItem($result, 0, network); + PyTuple_SetItem($result, 1, errMsgTuple); + } +%enddef + +%define %clear_optimize_typemap_out + %typemap(out) (std::pair>) +%enddef diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/permutation_vector.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/permutation_vector.i new file mode 100644 index 0000000..48419d1 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/permutation_vector.i @@ -0,0 +1,52 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%define %permutation_vector_typemap(TYPE1, TYPE2) + %typemap(in) (TYPE1, TYPE2) { + if (PyTuple_Check($input)) { + PyObject* seq = $input; + + $2 = PySequence_Fast_GET_SIZE(seq); + $1 = (unsigned int*)PyMem_RawMalloc($2*sizeof(unsigned int)); + + + if(!$1) { + PyErr_NoMemory(); + SWIG_fail; + } + int size = (int)$2; + for(int i=0; i < size; i++) { + PyObject *longItem; + // Borrowed reference. No need to Py_DECREF + PyObject *item = PySequence_Fast_GET_ITEM(seq, i); + if(!item) { + PyErr_SetString(PyExc_TypeError, "Failed to read data from tuple"); + SWIG_fail; + } + // New reference. Need to Py_DECREF + longItem = PyNumber_Long(item); + if(!longItem) { + Py_XDECREF(longItem); + PyErr_SetString(PyExc_TypeError, "All elements must be numbers"); + SWIG_fail; + } + $1[i] = (unsigned int)PyLong_AsUnsignedLong(longItem); + Py_XDECREF(longItem); + } + + } else { + PyErr_SetString(PyExc_TypeError, "Argument is not a tuple"); + SWIG_fail; + } + } + + %typemap(freearg) (TYPE1, TYPE2) { + PyMem_RawFree($1); + } +%enddef + +%define %clear_permutation_vector_typemap(TYPE1, TYPE2) + %typemap(in) (TYPE1, TYPE2); + %typemap(freearg) (TYPE1, TYPE2); +%enddef diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_memory.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_memory.i new file mode 100644 index 0000000..d199bc1 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_memory.i @@ -0,0 +1,52 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%define %mutable_memory(TYPEMAP) + %typemap(in) (TYPEMAP) { + int res; void *buf = 0; + Py_buffer view; + res = PyObject_GetBuffer($input, &view, PyBUF_WRITABLE); + buf = view.buf; + PyBuffer_Release(&view); + if (res < 0) { + PyErr_Clear(); + %argument_fail(res, "(TYPEMAP)", $symname, $argnum); + } + $1 = buf; + } + + %typemap(typecheck) (TYPEMAP) { + $1 = PyObject_CheckBuffer($input) || PyTuple_Check($input) ? 1 : 0; + } +%enddef + +%define %clear_mutable_memory(TYPEMAP) + %typemap(in) (TYPEMAP); + %typemap(typecheck) (TYPEMAP); +%enddef + +%define %const_memory(TYPEMAP) + %typemap(in) (TYPEMAP) { + int res; void *buf = 0; + Py_buffer view; + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + buf = view.buf; + PyBuffer_Release(&view); + if (res < 0) { + PyErr_Clear(); + %argument_fail(res, "(TYPEMAP)", $symname, $argnum); + } + $1 = buf; + } + + %typemap(typecheck) (TYPEMAP) { + $1 = PyObject_CheckBuffer($input) || PyTuple_Check($input) ? 1 : 0; + } +%enddef + +%define %clear_const_memory(TYPEMAP) + %typemap(in) (TYPEMAP); + %typemap(typecheck) (TYPEMAP); +%enddef + diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_shape.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_shape.i new file mode 100644 index 0000000..6565bd6 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/tensor_shape.i @@ -0,0 +1,51 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%define %tensor_shape_typemap(TYPE1, TYPE2) + %typemap(in) (TYPE1, TYPE2) { + if (PyTuple_Check($input)) { + PyObject* seq = $input; + + $1 = PySequence_Fast_GET_SIZE(seq); + $2 = (unsigned int*)PyMem_RawMalloc($1*sizeof(unsigned int)); + + if(!$2) { + PyErr_NoMemory(); + SWIG_fail; + } + int size = (int)$1; + for(int i=0; i < size; i++) { + PyObject *longItem; + // Borrowed reference. No need to Py_DECREF + PyObject *item = PySequence_Fast_GET_ITEM(seq, i); + if(!item) { + PyErr_SetString(PyExc_TypeError, "Failed to read data from tuple"); + SWIG_fail; + } + // New reference. Need to Py_DECREF + longItem = PyNumber_Long(item); + if(!longItem) { + Py_XDECREF(longItem); + PyErr_SetString(PyExc_TypeError, "All elements must be numbers"); + SWIG_fail; + } + $2[i] = (unsigned int)PyLong_AsUnsignedLong(longItem); + Py_XDECREF(longItem); + } + + } else { + PyErr_SetString(PyExc_TypeError, "Argument is not a tuple"); + SWIG_fail; + } + } + + %typemap(freearg) (TYPE1, TYPE2) { + PyMem_RawFree($2); + } +%enddef + +%define %clear_tensor_shape_typemap(TYPE1, TYPE2) + %typemap(in) (TYPE1, TYPE2); + %typemap(freearg) (TYPE1, TYPE2); +%enddef diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/vectors.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/vectors.i new file mode 100644 index 0000000..37fd0c9 --- /dev/null +++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/vectors.i @@ -0,0 +1,235 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +%inline %{ +//-------------------------from_python_to_cpp----------------------------- + int from_python_to_cpp(PyObject *obj, long* val) { + return SWIG_AsVal_long(obj, val); + } + + int from_python_to_cpp(PyObject *obj, int* val) { + return SWIG_AsVal_int(obj, val); + } + + int from_python_to_cpp(PyObject *obj, unsigned int* val) { + return SWIG_AsVal_unsigned_SS_int(obj, val); + } + + int from_python_to_cpp(PyObject *obj, unsigned short* val) { + return SWIG_AsVal_unsigned_SS_short(obj, val); + } + + int from_python_to_cpp(PyObject *obj, float* val) { + return SWIG_AsVal_float(obj, val); + } + + int from_python_to_cpp(PyObject *obj, double* val) { + return SWIG_AsVal_double(obj, val); + } +#ifdef SWIG_LONG_LONG_AVAILABLE + int from_python_to_cpp(PyObject *obj, unsigned long long* val) { + return SWIG_AsVal_unsigned_SS_long_SS_long(obj, val); + } + + int from_python_to_cpp(PyObject *obj, long long* val) { + return SWIG_AsVal_long_SS_long(obj, val); + } +#endif + + int from_python_to_cpp(PyObject *obj, unsigned long* val) { + return SWIG_AsVal_unsigned_SS_long(obj, val); + } + + int from_python_to_cpp(PyObject *obj, short* val) { + return SWIG_AsVal_short(obj, val); + } +//-------------------------from_cpp_to_python----------------------------- + PyObject* from_cpp_to_python(long& val){ + return PyLong_FromLong(val); + } + + PyObject* from_cpp_to_python(unsigned long& val){ + return PyLong_FromUnsignedLong(val); + } +#ifdef SWIG_LONG_LONG_AVAILABLE + PyObject* from_cpp_to_python(long long& val){ + return PyLong_FromLongLong(val); + } + + PyObject* from_cpp_to_python(unsigned long long& val){ + return PyLong_FromUnsignedLongLong(val); + } +#endif + + PyObject* from_cpp_to_python(int& val){ + return PyLong_FromLong(static_cast(val)); + } + + PyObject* from_cpp_to_python(unsigned int& val){ + return PyLong_FromUnsignedLong(static_cast(val)); + } + + PyObject* from_cpp_to_python(unsigned short& val){ + return PyLong_FromUnsignedLong(static_cast(val)); + } + + PyObject* from_cpp_to_python(float& val){ + return PyFloat_FromDouble(static_cast(val)); + } + + PyObject* from_cpp_to_python(double& val){ + return PyFloat_FromDouble(val); + } + + template + PyObject* from_cpp_to_python(std::pair& pair){ + + PyObject* first = from_cpp_to_python(pair.first); + PyObject* second = from_cpp_to_python(pair.second); + + PyObject* localTuple = PyTuple_New(2); + + if (!localTuple) { + Py_XDECREF(localTuple); + return PyErr_NoMemory(); + } + + PyTuple_SetItem(localTuple, 0, first); + PyTuple_SetItem(localTuple, 1, second); + + return localTuple; + } + + template + static int from_python_to_cpp(PyObject* tuple, std::pair* out) { + + if (PyTuple_Check(tuple)) { + + auto size = PyTuple_Size(tuple); + + if (size != 2) { + return SWIG_ValueError; + } + + PyObject* firstPy = PyTuple_GetItem(tuple, 0); + PyObject* secondPy = PyTuple_GetItem(tuple, 1); + + if (!SWIG_IsOK(from_python_to_cpp(firstPy, &out->first))) { + return SWIG_TypeError; + } + + if (!SWIG_IsOK(from_python_to_cpp(secondPy, &out->second))) { + return SWIG_TypeError; + } + + } else { + return SWIG_TypeError; + } + + return SWIG_OK; + } +//---------------std::vector <-> python list --------------------- + template + static PyObject* from_vector_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 = from_cpp_to_python(input->at(i)); + + PyList_SET_ITEM(localList, i, obj); + } + return localList; + } + + template + int from_python_to_vector(PyObject* seq, std::vector& out) { + Py_ssize_t size = PySequence_Fast_GET_SIZE(seq); + + for(Py_ssize_t i=0; i < size; i++) { + PyObject *item = PySequence_Fast_GET_ITEM(seq, i); + if(!item) { + PyErr_SetString(PyExc_TypeError, "Failed to read data from given sequence"); + + return SWIG_NullReferenceError; + } + + T element; + int res = from_python_to_cpp(item, &element); + if (!SWIG_IsOK(res)) { + PyObject* itemRepr = PyObject_Repr(item); + PyObject* itemStrObj = PyUnicode_AsEncodedString(itemRepr, "utf-8", "replace"); + const char* itemStr = PyBytes_AS_STRING(itemStrObj); + + auto pythonType = Py_TYPE(item)->tp_name; + + PyErr_Format(PyExc_TypeError, "Failed to convert python input value %s of type '%s' to C type '%s'", itemStr, pythonType, typeid(T).name()); + Py_XDECREF(itemStrObj); + Py_XDECREF(itemRepr); + Py_DECREF(seq); + return SWIG_TypeError; + } + out.push_back(element); + } + return SWIG_OK; + } + +%} + +%define %list_to_vector(TYPEMAP...) + +// this typemap works for struct argument set + %typemap(in) TYPEMAP* (TYPEMAP tmp) { + if (PySequence_Check($input)) { + + if (from_python_to_vector($input, tmp) < 0) { + SWIG_fail; + } + + $1 = &tmp; + + } else { + PyErr_SetString(PyExc_TypeError, "Argument value object does not provide sequence protocol, implement __getitem__() method."); + SWIG_fail; + } + } + +// this typemap works for constructor + %typemap(in) TYPEMAP { + if (PySequence_Check($input)) { + if (from_python_to_vector($input, $1) < 0){ + SWIG_fail; + } + } else { + PyErr_SetString(PyExc_TypeError, "Argument value object does not provide sequence protocol, implement __getitem__() method."); + SWIG_fail; + } + } + +// this typemap works for struct argument get + + %typemap(out) TYPEMAP* { + $result = from_vector_to_python($1); + } + +// this typemap works for overloaded methods and ctors + %typemap(typecheck) (TYPEMAP) { + $1 = PySequence_Check($input) ? 1 : 0; + } + +%enddef + +%define %list_to_vector_clear(TYPEMAP...) + %typemap(in) (TYPEMAP); + %typemap(in) TYPEMAP* (TYPEMAP tmp); + %typemap(typecheck) (TYPEMAP); + %typemap(out) TYPEMAP*; +%enddef + diff --git a/python/pyarmnn/swig_generate.py b/python/pyarmnn/swig_generate.py new file mode 100755 index 0000000..b63afc5 --- /dev/null +++ b/python/pyarmnn/swig_generate.py @@ -0,0 +1,64 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +""" +This script executes SWIG commands to generate armnn and armnn version wrappers. +This script cannot be moved to ./script dir because it uses find_armnn function from setup.py script. +Both scripts must be in the same folder. +""" +import os +import re +import subprocess +from pathlib import Path + +from setup import find_includes + +__current_dir = Path(__file__).parent.absolute() + + +def check_swig_versoin(version: str): + proc = subprocess.Popen(["swig -version"], + stdout=subprocess.PIPE, shell=True) + result = proc.communicate()[0].decode("utf-8") + + pattern = re.compile(r"(?<=Version ).+(?=$)", re.MULTILINE) + match = pattern.search(result) + + if match: + version_string = match.group(0).strip() + print(f"Swig version = {version_string}") + return version_string.startswith(version) + else: + print(f"Failed to find version string in 'swig -version':\n {result}") + return False + + +def generate_wrap(name, extr_includes): + print(f'\nGenerating wrappers for {name}\n') + + code = os.system(f"swig -v -c++ -python" + f" -Wall" + f" -o {__current_dir}/src/pyarmnn/_generated/{name}_wrap.cpp " + f"-outdir {__current_dir}/src/pyarmnn/_generated " + f"{extr_includes} " + f"-I{__current_dir}/src/pyarmnn/swig " + f"{__current_dir}/src/pyarmnn/swig/{name}.i") + + if code != 0: + raise RuntimeError(f"Failed to generate {name} ext.") + + +if __name__ == "__main__": + if not check_swig_versoin('4.'): + raise RuntimeError("Wrong swig version was found. Expected SWIG version is 4.x.x") + + armnn_includes = find_includes() + + generate_wrap('armnn_version', f"-I{'-I'.join(armnn_includes)} ") + generate_wrap('armnn', f"-I{'-I'.join(armnn_includes)} ") + + generate_wrap('armnn_caffeparser', f"-I{'-I'.join(armnn_includes)} ") + generate_wrap('armnn_onnxparser', f"-I{'-I'.join(armnn_includes)} ") + generate_wrap('armnn_tfparser', f"-I{'-I'.join(armnn_includes)} ") + generate_wrap('armnn_tfliteparser', f"-I{'-I'.join(armnn_includes)} ") + + diff --git a/python/pyarmnn/test/test_caffe_parser.py b/python/pyarmnn/test/test_caffe_parser.py new file mode 100644 index 0000000..d744b90 --- /dev/null +++ b/python/pyarmnn/test/test_caffe_parser.py @@ -0,0 +1,131 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest +import pyarmnn as ann +import numpy as np + + +@pytest.fixture() +def parser(shared_data_folder): + """ + Parse and setup the test network to be used for the tests below + """ + + # Create caffe parser + parser = ann.ICaffeParser() + + # Specify path to model + path_to_model = os.path.join(shared_data_folder, 'mock_model.caffemodel') + + # Specify the tensor shape relative to the input [1, 1, 28, 28] + tensor_shape = {'Placeholder': ann.TensorShape((1, 1, 28, 28))} + + # Specify the requested_outputs + requested_outputs = ["output"] + + # Parse caffe binary & create network + parser.CreateNetworkFromBinaryFile(path_to_model, tensor_shape, requested_outputs) + + yield parser + + +def test_caffe_parser_swig_destroy(): + assert ann.ICaffeParser.__swig_destroy__, "There is a swig python destructor defined" + assert ann.ICaffeParser.__swig_destroy__.__name__ == "delete_ICaffeParser" + + +def test_check_caffe_parser_swig_ownership(parser): + # Check to see that SWIG has ownership for parser. This instructs SWIG to take + # ownership of the return value. This allows the value to be automatically + # garbage-collected when it is no longer in use + assert parser.thisown + + +def test_get_network_input_binding_info(parser): + input_binding_info = parser.GetNetworkInputBindingInfo("Placeholder") + + tensor = input_binding_info[1] + assert tensor.GetDataType() == 1 + assert tensor.GetNumDimensions() == 4 + assert tensor.GetNumElements() == 784 + + +def test_get_network_output_binding_info(parser): + output_binding_info1 = parser.GetNetworkOutputBindingInfo("output") + + # Check the tensor info retrieved from GetNetworkOutputBindingInfo + tensor1 = output_binding_info1[1] + + assert tensor1.GetDataType() == 1 + assert tensor1.GetNumDimensions() == 2 + assert tensor1.GetNumElements() == 10 + + +def test_filenotfound_exception(shared_data_folder): + parser = ann.ICaffeParser() + + # path to model + path_to_model = os.path.join(shared_data_folder, 'some_unknown_network.caffemodel') + + # generic tensor shape [1, 1, 1, 1] + tensor_shape = {'data': ann.TensorShape((1, 1, 1, 1))} + + # requested_outputs + requested_outputs = [""] + + with pytest.raises(RuntimeError) as err: + parser.CreateNetworkFromBinaryFile(path_to_model, tensor_shape, requested_outputs) + + # Only check for part of the exception since the exception returns + # absolute path which will change on different machines. + assert 'Failed to open graph file' in str(err.value) + + +def test_caffe_parser_end_to_end(shared_data_folder): + parser = ann.ICaffeParser = ann.ICaffeParser() + + # Load the network specifying the inputs and outputs + input_name = "Placeholder" + tensor_shape = {input_name: ann.TensorShape((1, 1, 28, 28))} + requested_outputs = ["output"] + + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.caffemodel'), + tensor_shape, requested_outputs) + + # Specify preferred backend + preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] + + input_binding_info = parser.GetNetworkInputBindingInfo(input_name) + + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + assert 0 == len(messages) + + net_id, messages = runtime.LoadNetwork(opt_network) + + assert "" == messages + + # Load test image data stored in input_caffe.npy + input_tensor_data = np.load(os.path.join(shared_data_folder, 'caffe_parser/input_caffe.npy')).astype(np.float32) + input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data]) + + # Load output binding info and + outputs_binding_info = [] + for output_name in requested_outputs: + outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(output_name)) + output_tensors = ann.make_output_tensors(outputs_binding_info) + + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + output_vectors = ann.workload_tensors_to_ndarray(output_tensors) + + # Load golden output file for result comparison. + expected_output = np.load(os.path.join(shared_data_folder, 'caffe_parser/golden_output_caffe.npy')) + + # Check that output matches golden output to 4 decimal places (there are slight rounding differences after this) + np.testing.assert_almost_equal(output_vectors[0], expected_output, 4) diff --git a/python/pyarmnn/test/test_const_tensor.py b/python/pyarmnn/test/test_const_tensor.py new file mode 100644 index 0000000..fa6327f --- /dev/null +++ b/python/pyarmnn/test/test_const_tensor.py @@ -0,0 +1,251 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import pytest +import numpy as np + +import pyarmnn as ann + + +def _get_tensor_info(dt): + tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), dt) + + return tensor_info + + +@pytest.mark.parametrize("dt, data", + [ + (ann.DataType_Float32, np.random.randint(1, size=(2, 4)).astype(np.float32)), + (ann.DataType_Float16, np.random.randint(1, size=(2, 4)).astype(np.float16)), + (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 4)).astype(np.uint8)), + (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 4)).astype(np.int8)), + (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 4)).astype(np.int8)), + (ann.DataType_Signed32, np.random.randint(1, size=(2, 4)).astype(np.int32)), + (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 4)).astype(np.int16)) + ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16']) +def test_const_tensor_too_many_elements(dt, data): + tensor_info = _get_tensor_info(dt) + num_bytes = tensor_info.GetNumBytes() + + with pytest.raises(ValueError) as err: + ann.ConstTensor(tensor_info, data) + + assert 'ConstTensor requires {} bytes, {} provided.'.format(num_bytes, data.nbytes) in str(err.value) + + +@pytest.mark.parametrize("dt, data", + [ + (ann.DataType_Float32, np.random.randint(1, size=(2, 2)).astype(np.float32)), + (ann.DataType_Float16, np.random.randint(1, size=(2, 2)).astype(np.float16)), + (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 2)).astype(np.uint8)), + (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 2)).astype(np.int8)), + (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 2)).astype(np.int8)), + (ann.DataType_Signed32, np.random.randint(1, size=(2, 2)).astype(np.int32)), + (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 2)).astype(np.int16)) + ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16']) +def test_const_tensor_too_little_elements(dt, data): + tensor_info = _get_tensor_info(dt) + num_bytes = tensor_info.GetNumBytes() + + with pytest.raises(ValueError) as err: + ann.ConstTensor(tensor_info, data) + + assert 'ConstTensor requires {} bytes, {} provided.'.format(num_bytes, data.nbytes) in str(err.value) + + +@pytest.mark.parametrize("dt, data", + [ + (ann.DataType_Float32, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.float32)), + (ann.DataType_Float16, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.float16)), + (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.uint8)), + (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int8)), + (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int8)), + (ann.DataType_Signed32, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int32)), + (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 2, 3, 3)).astype(np.int16)) + ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16']) +def test_const_tensor_multi_dimensional_input(dt, data): + tensor = ann.ConstTensor(ann.TensorInfo(ann.TensorShape((2, 2, 3, 3)), dt), data) + + assert data.size == tensor.GetNumElements() + assert data.nbytes == tensor.GetNumBytes() + assert dt == tensor.GetDataType() + assert tensor.get_memory_area().data + + +def test_create_const_tensor_from_tensor(): + tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_Float32) + tensor = ann.Tensor(tensor_info) + copied_tensor = ann.ConstTensor(tensor) + + assert copied_tensor != tensor, "Different objects" + assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects" + assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data, "Same memory area" + assert copied_tensor.GetNumElements() == tensor.GetNumElements() + assert copied_tensor.GetNumBytes() == tensor.GetNumBytes() + assert copied_tensor.GetDataType() == tensor.GetDataType() + + +def test_const_tensor_from_tensor_has_memory_area_access_after_deletion_of_original_tensor(): + tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_Float32) + tensor = ann.Tensor(tensor_info) + + tensor.get_memory_area()[0] = 100 + + copied_mem = tensor.get_memory_area().copy() + + assert 100 == copied_mem[0], "Memory was copied correctly" + + copied_tensor = ann.ConstTensor(tensor) + + tensor.get_memory_area()[0] = 200 + + assert 200 == tensor.get_memory_area()[0], "Tensor and copied Tensor point to the same memory" + assert 200 == copied_tensor.get_memory_area()[0], "Tensor and copied Tensor point to the same memory" + + assert 100 == copied_mem[0], "Copied test memory not affected" + + copied_mem[0] = 200 # modify test memory to equal copied Tensor + + del tensor + np.testing.assert_array_equal(copied_tensor.get_memory_area(), copied_mem), "After initial tensor was deleted, " \ + "copied Tensor still has " \ + "its memory as expected" + + +def test_create_const_tensor_incorrect_args(): + with pytest.raises(ValueError) as err: + ann.ConstTensor('something', 'something') + + expected_error_message = "Incorrect number of arguments or type of arguments provided to create Const Tensor." + assert expected_error_message in str(err.value) + + +@pytest.mark.parametrize("dt, data", + [ + # -1 not in data type enum + (-1, np.random.randint(1, size=(2, 3)).astype(np.float32)), + ], ids=['unknown']) +def test_const_tensor_unsupported_datatype(dt, data): + tensor_info = _get_tensor_info(dt) + + with pytest.raises(ValueError) as err: + ann.ConstTensor(tensor_info, data) + + assert 'The data type provided for this Tensor is not supported: -1' in str(err.value) + + +@pytest.mark.parametrize("dt, data", + [ + (ann.DataType_Float32, [[1, 1, 1], [1, 1, 1]]), + (ann.DataType_Float16, [[1, 1, 1], [1, 1, 1]]), + (ann.DataType_QAsymmU8, [[1, 1, 1], [1, 1, 1]]), + (ann.DataType_QAsymmS8, [[1, 1, 1], [1, 1, 1]]), + (ann.DataType_QSymmS8, [[1, 1, 1], [1, 1, 1]]) + ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8']) +def test_const_tensor_incorrect_input_datatype(dt, data): + tensor_info = _get_tensor_info(dt) + + with pytest.raises(TypeError) as err: + ann.ConstTensor(tensor_info, data) + + assert 'Data must be provided as a numpy array.' in str(err.value) + + +@pytest.mark.parametrize("dt, data", + [ + (ann.DataType_Float32, np.random.randint(1, size=(2, 3)).astype(np.float32)), + (ann.DataType_Float16, np.random.randint(1, size=(2, 3)).astype(np.float16)), + (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 3)).astype(np.uint8)), + (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 3)).astype(np.int8)), + (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 3)).astype(np.int8)), + (ann.DataType_Signed32, np.random.randint(1, size=(2, 3)).astype(np.int32)), + (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 3)).astype(np.int16)) + ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16']) +class TestNumpyDataTypes: + + def test_copy_const_tensor(self, dt, data): + tensor_info = _get_tensor_info(dt) + tensor = ann.ConstTensor(tensor_info, data) + copied_tensor = ann.ConstTensor(tensor) + + assert copied_tensor != tensor, "Different objects" + assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects" + assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data, "Same memory area" + assert copied_tensor.GetNumElements() == tensor.GetNumElements() + assert copied_tensor.GetNumBytes() == tensor.GetNumBytes() + assert copied_tensor.GetDataType() == tensor.GetDataType() + + def test_const_tensor__str__(self, dt, data): + tensor_info = _get_tensor_info(dt) + d_type = tensor_info.GetDataType() + num_dimensions = tensor_info.GetNumDimensions() + num_bytes = tensor_info.GetNumBytes() + num_elements = tensor_info.GetNumElements() + tensor = ann.ConstTensor(tensor_info, data) + + assert str(tensor) == "ConstTensor{{DataType: {}, NumBytes: {}, NumDimensions: " \ + "{}, NumElements: {}}}".format(d_type, num_bytes, num_dimensions, num_elements) + + def test_const_tensor_with_info(self, dt, data): + tensor_info = _get_tensor_info(dt) + elements = tensor_info.GetNumElements() + num_bytes = tensor_info.GetNumBytes() + d_type = dt + + tensor = ann.ConstTensor(tensor_info, data) + + assert tensor_info != tensor.GetInfo(), "Different objects" + assert elements == tensor.GetNumElements() + assert num_bytes == tensor.GetNumBytes() + assert d_type == tensor.GetDataType() + + def test_immutable_memory(self, dt, data): + tensor_info = _get_tensor_info(dt) + + tensor = ann.ConstTensor(tensor_info, data) + + with pytest.raises(ValueError) as err: + tensor.get_memory_area()[0] = 0 + + assert 'is read-only' in str(err.value) + + def test_numpy_dtype_matches_ann_dtype(self, dt, data): + np_data_type_mapping = {ann.DataType_QAsymmU8: np.uint8, + ann.DataType_QAsymmS8: np.int8, + ann.DataType_QSymmS8: np.int8, + ann.DataType_Float32: np.float32, + ann.DataType_QSymmS16: np.int16, + ann.DataType_Signed32: np.int32, + ann.DataType_Float16: np.float16} + + tensor_info = _get_tensor_info(dt) + tensor = ann.ConstTensor(tensor_info, data) + assert np_data_type_mapping[tensor.GetDataType()] == data.dtype + + +# This test checks that mismatched numpy and PyArmNN datatypes with same number of bits raises correct error. +@pytest.mark.parametrize("dt, data", + [ + (ann.DataType_Float32, np.random.randint(1, size=(2, 3)).astype(np.int32)), + (ann.DataType_Float16, np.random.randint(1, size=(2, 3)).astype(np.int16)), + (ann.DataType_QAsymmU8, np.random.randint(1, size=(2, 3)).astype(np.int8)), + (ann.DataType_QAsymmS8, np.random.randint(1, size=(2, 3)).astype(np.uint8)), + (ann.DataType_QSymmS8, np.random.randint(1, size=(2, 3)).astype(np.uint8)), + (ann.DataType_Signed32, np.random.randint(1, size=(2, 3)).astype(np.float32)), + (ann.DataType_QSymmS16, np.random.randint(1, size=(2, 3)).astype(np.float16)) + ], ids=['float32', 'float16', 'unsigned int8', 'signed int8', 'signed int8', 'int32', 'int16']) +def test_numpy_dtype_mismatch_ann_dtype(dt, data): + np_data_type_mapping = {ann.DataType_QAsymmU8: np.uint8, + ann.DataType_QAsymmS8: np.int8, + ann.DataType_QSymmS8: np.int8, + ann.DataType_Float32: np.float32, + ann.DataType_QSymmS16: np.int16, + ann.DataType_Signed32: np.int32, + ann.DataType_Float16: np.float16} + + tensor_info = _get_tensor_info(dt) + with pytest.raises(TypeError) as err: + ann.ConstTensor(tensor_info, data) + + assert str(err.value) == "Expected data to have type {} for type {} but instead got numpy.{}".format( + np_data_type_mapping[dt], dt, data.dtype) + diff --git a/python/pyarmnn/test/test_descriptors.py b/python/pyarmnn/test/test_descriptors.py new file mode 100644 index 0000000..1a41105 --- /dev/null +++ b/python/pyarmnn/test/test_descriptors.py @@ -0,0 +1,535 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import inspect + +import pytest + +import pyarmnn as ann +import numpy as np +import pyarmnn._generated.pyarmnn as generated + + +def test_activation_descriptor_default_values(): + desc = ann.ActivationDescriptor() + assert desc.m_Function == ann.ActivationFunction_Sigmoid + assert desc.m_A == 0 + assert desc.m_B == 0 + + +def test_argminmax_descriptor_default_values(): + desc = ann.ArgMinMaxDescriptor() + assert desc.m_Function == ann.ArgMinMaxFunction_Min + assert desc.m_Axis == -1 + + +def test_batchnormalization_descriptor_default_values(): + desc = ann.BatchNormalizationDescriptor() + assert desc.m_DataLayout == ann.DataLayout_NCHW + np.allclose(0.0001, desc.m_Eps) + + +def test_batchtospacend_descriptor_default_values(): + desc = ann.BatchToSpaceNdDescriptor() + assert desc.m_DataLayout == ann.DataLayout_NCHW + assert [1, 1] == desc.m_BlockShape + assert [(0, 0), (0, 0)] == desc.m_Crops + + +def test_batchtospacend_descriptor_assignment(): + desc = ann.BatchToSpaceNdDescriptor() + desc.m_BlockShape = (1, 2, 3) + + ololo = [(1, 2), (3, 4)] + size_1 = len(ololo) + desc.m_Crops = ololo + + assert size_1 == len(ololo) + desc.m_DataLayout = ann.DataLayout_NHWC + assert ann.DataLayout_NHWC == desc.m_DataLayout + assert [1, 2, 3] == desc.m_BlockShape + assert [(1, 2), (3, 4)] == desc.m_Crops + + +@pytest.mark.parametrize("input_shape, value, vtype", [([-1], -1, 'int'), (("one", "two"), "'one'", 'str'), + ([1.33, 4.55], 1.33, 'float'), + ([{1: "one"}], "{1: 'one'}", 'dict')], ids=lambda x: str(x)) +def test_batchtospacend_descriptor_rubbish_assignment_shape(input_shape, value, vtype): + desc = ann.BatchToSpaceNdDescriptor() + with pytest.raises(TypeError) as err: + desc.m_BlockShape = input_shape + + assert "Failed to convert python input value {} of type '{}' to C type 'j'".format(value, vtype) in str(err.value) + + +@pytest.mark.parametrize("input_crops, value, vtype", [([(1, 2), (3, 4, 5)], '(3, 4, 5)', 'tuple'), + ([(1, 'one')], "(1, 'one')", 'tuple'), + ([-1], -1, 'int'), + ([(1, (1, 2))], '(1, (1, 2))', 'tuple'), + ([[1, [1, 2]]], '[1, [1, 2]]', 'list') + ], ids=lambda x: str(x)) +def test_batchtospacend_descriptor_rubbish_assignment_crops(input_crops, value, vtype): + desc = ann.BatchToSpaceNdDescriptor() + with pytest.raises(TypeError) as err: + desc.m_Crops = input_crops + + assert "Failed to convert python input value {} of type '{}' to C type".format(value, vtype) in str(err.value) + + +def test_batchtospacend_descriptor_empty_assignment(): + desc = ann.BatchToSpaceNdDescriptor() + desc.m_BlockShape = [] + assert [] == desc.m_BlockShape + + +def test_batchtospacend_descriptor_ctor(): + desc = ann.BatchToSpaceNdDescriptor([1, 2, 3], [(4, 5), (6, 7)]) + assert desc.m_DataLayout == ann.DataLayout_NCHW + assert [1, 2, 3] == desc.m_BlockShape + assert [(4, 5), (6, 7)] == desc.m_Crops + + +def test_convolution2d_descriptor_default_values(): + desc = ann.Convolution2dDescriptor() + assert desc.m_PadLeft == 0 + assert desc.m_PadTop == 0 + assert desc.m_PadRight == 0 + assert desc.m_PadBottom == 0 + assert desc.m_StrideX == 0 + assert desc.m_StrideY == 0 + assert desc.m_DilationX == 1 + assert desc.m_DilationY == 1 + assert desc.m_BiasEnabled == False + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_depthtospace_descriptor_default_values(): + desc = ann.DepthToSpaceDescriptor() + assert desc.m_BlockSize == 1 + assert desc.m_DataLayout == ann.DataLayout_NHWC + + +def test_depthwise_convolution2d_descriptor_default_values(): + desc = ann.DepthwiseConvolution2dDescriptor() + assert desc.m_PadLeft == 0 + assert desc.m_PadTop == 0 + assert desc.m_PadRight == 0 + assert desc.m_PadBottom == 0 + assert desc.m_StrideX == 0 + assert desc.m_StrideY == 0 + assert desc.m_DilationX == 1 + assert desc.m_DilationY == 1 + assert desc.m_BiasEnabled == False + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_detectionpostprocess_descriptor_default_values(): + desc = ann.DetectionPostProcessDescriptor() + assert desc.m_MaxDetections == 0 + assert desc.m_MaxClassesPerDetection == 1 + assert desc.m_DetectionsPerClass == 1 + assert desc.m_NmsScoreThreshold == 0 + assert desc.m_NmsIouThreshold == 0 + assert desc.m_NumClasses == 0 + assert desc.m_UseRegularNms == False + assert desc.m_ScaleH == 0 + assert desc.m_ScaleW == 0 + assert desc.m_ScaleX == 0 + assert desc.m_ScaleY == 0 + + +def test_fakequantization_descriptor_default_values(): + desc = ann.FakeQuantizationDescriptor() + np.allclose(6, desc.m_Max) + np.allclose(-6, desc.m_Min) + + +def test_fully_connected_descriptor_default_values(): + desc = ann.FullyConnectedDescriptor() + assert desc.m_BiasEnabled == False + assert desc.m_TransposeWeightMatrix == False + + +def test_instancenormalization_descriptor_default_values(): + desc = ann.InstanceNormalizationDescriptor() + assert desc.m_Gamma == 1 + assert desc.m_Beta == 0 + assert desc.m_DataLayout == ann.DataLayout_NCHW + np.allclose(1e-12, desc.m_Eps) + + +def test_lstm_descriptor_default_values(): + desc = ann.LstmDescriptor() + assert desc.m_ActivationFunc == 1 + assert desc.m_ClippingThresCell == 0 + assert desc.m_ClippingThresProj == 0 + assert desc.m_CifgEnabled == True + assert desc.m_PeepholeEnabled == False + assert desc.m_ProjectionEnabled == False + assert desc.m_LayerNormEnabled == False + + +def test_l2normalization_descriptor_default_values(): + desc = ann.L2NormalizationDescriptor() + assert desc.m_DataLayout == ann.DataLayout_NCHW + np.allclose(1e-12, desc.m_Eps) + + +def test_mean_descriptor_default_values(): + desc = ann.MeanDescriptor() + assert desc.m_KeepDims == False + + +def test_normalization_descriptor_default_values(): + desc = ann.NormalizationDescriptor() + assert desc.m_NormChannelType == ann.NormalizationAlgorithmChannel_Across + assert desc.m_NormMethodType == ann.NormalizationAlgorithmMethod_LocalBrightness + assert desc.m_NormSize == 0 + assert desc.m_Alpha == 0 + assert desc.m_Beta == 0 + assert desc.m_K == 0 + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_origin_descriptor_default_values(): + desc = ann.ConcatDescriptor() + assert 0 == desc.GetNumViews() + assert 0 == desc.GetNumDimensions() + assert 1 == desc.GetConcatAxis() + + +def test_origin_descriptor_incorrect_views(): + desc = ann.ConcatDescriptor(2, 2) + with pytest.raises(RuntimeError) as err: + desc.SetViewOriginCoord(1000, 100, 1000) + assert "Failed to set view origin coordinates." in str(err.value) + + +def test_origin_descriptor_ctor(): + desc = ann.ConcatDescriptor(2, 2) + value = 5 + for i in range(desc.GetNumViews()): + for j in range(desc.GetNumDimensions()): + desc.SetViewOriginCoord(i, j, value+i) + desc.SetConcatAxis(1) + + assert 2 == desc.GetNumViews() + assert 2 == desc.GetNumDimensions() + assert [5, 5] == desc.GetViewOrigin(0) + assert [6, 6] == desc.GetViewOrigin(1) + assert 1 == desc.GetConcatAxis() + + +def test_pad_descriptor_default_values(): + desc = ann.PadDescriptor() + assert desc.m_PadValue == 0 + + +def test_permute_descriptor_default_values(): + pv = ann.PermutationVector((0, 2, 3, 1)) + desc = ann.PermuteDescriptor(pv) + assert desc.m_DimMappings.GetSize() == 4 + assert desc.m_DimMappings[0] == 0 + assert desc.m_DimMappings[1] == 2 + assert desc.m_DimMappings[2] == 3 + assert desc.m_DimMappings[3] == 1 + + +def test_pooling_descriptor_default_values(): + desc = ann.Pooling2dDescriptor() + assert desc.m_PoolType == ann.PoolingAlgorithm_Max + assert desc.m_PadLeft == 0 + assert desc.m_PadTop == 0 + assert desc.m_PadRight == 0 + assert desc.m_PadBottom == 0 + assert desc.m_PoolHeight == 0 + assert desc.m_PoolWidth == 0 + assert desc.m_StrideX == 0 + assert desc.m_StrideY == 0 + assert desc.m_OutputShapeRounding == ann.OutputShapeRounding_Floor + assert desc.m_PaddingMethod == ann.PaddingMethod_Exclude + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_reshape_descriptor_default_values(): + desc = ann.ReshapeDescriptor() + # check the empty Targetshape + assert desc.m_TargetShape.GetNumDimensions() == 0 + + +def test_slice_descriptor_default_values(): + desc = ann.SliceDescriptor() + assert desc.m_TargetWidth == 0 + assert desc.m_TargetHeight == 0 + assert desc.m_Method == ann.ResizeMethod_NearestNeighbor + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_resize_descriptor_default_values(): + desc = ann.ResizeDescriptor() + assert desc.m_TargetWidth == 0 + assert desc.m_TargetHeight == 0 + assert desc.m_Method == ann.ResizeMethod_NearestNeighbor + assert desc.m_DataLayout == ann.DataLayout_NCHW + assert desc.m_BilinearAlignCorners == False + + +def test_spacetobatchnd_descriptor_default_values(): + desc = ann.SpaceToBatchNdDescriptor() + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_spacetodepth_descriptor_default_values(): + desc = ann.SpaceToDepthDescriptor() + assert desc.m_BlockSize == 1 + assert desc.m_DataLayout == ann.DataLayout_NHWC + + +def test_stack_descriptor_default_values(): + desc = ann.StackDescriptor() + assert desc.m_Axis == 0 + assert desc.m_NumInputs == 0 + # check the empty Inputshape + assert desc.m_InputShape.GetNumDimensions() == 0 + + +def test_slice_descriptor_default_values(): + desc = ann.SliceDescriptor() + desc.m_Begin = [1, 2, 3, 4, 5] + desc.m_Size = (1, 2, 3, 4) + + assert [1, 2, 3, 4, 5] == desc.m_Begin + assert [1, 2, 3, 4] == desc.m_Size + + +def test_slice_descriptor_ctor(): + desc = ann.SliceDescriptor([1, 2, 3, 4, 5], (1, 2, 3, 4)) + + assert [1, 2, 3, 4, 5] == desc.m_Begin + assert [1, 2, 3, 4] == desc.m_Size + + +def test_strided_slice_descriptor_default_values(): + desc = ann.StridedSliceDescriptor() + desc.m_Begin = [1, 2, 3, 4, 5] + desc.m_End = [6, 7, 8, 9, 10] + desc.m_Stride = (10, 10) + desc.m_BeginMask = 1 + desc.m_EndMask = 2 + desc.m_ShrinkAxisMask = 3 + desc.m_EllipsisMask = 4 + desc.m_NewAxisMask = 5 + + assert [1, 2, 3, 4, 5] == desc.m_Begin + assert [6, 7, 8, 9, 10] == desc.m_End + assert [10, 10] == desc.m_Stride + assert 1 == desc.m_BeginMask + assert 2 == desc.m_EndMask + assert 3 == desc.m_ShrinkAxisMask + assert 4 == desc.m_EllipsisMask + assert 5 == desc.m_NewAxisMask + + +def test_strided_slice_descriptor_ctor(): + desc = ann.StridedSliceDescriptor([1, 2, 3, 4, 5], [6, 7, 8, 9, 10], (10, 10)) + desc.m_Begin = [1, 2, 3, 4, 5] + desc.m_End = [6, 7, 8, 9, 10] + desc.m_Stride = (10, 10) + + assert [1, 2, 3, 4, 5] == desc.m_Begin + assert [6, 7, 8, 9, 10] == desc.m_End + assert [10, 10] == desc.m_Stride + + +def test_softmax_descriptor_default_values(): + desc = ann.SoftmaxDescriptor() + assert desc.m_Axis == -1 + np.allclose(1.0, desc.m_Beta) + + +def test_space_to_batch_nd_descriptor_default_values(): + desc = ann.SpaceToBatchNdDescriptor() + assert [1, 1] == desc.m_BlockShape + assert [(0, 0), (0, 0)] == desc.m_PadList + assert ann.DataLayout_NCHW == desc.m_DataLayout + + +def test_space_to_batch_nd_descriptor_assigned_values(): + desc = ann.SpaceToBatchNdDescriptor() + desc.m_BlockShape = (90, 100) + desc.m_PadList = [(1, 2), (3, 4)] + assert [90, 100] == desc.m_BlockShape + assert [(1, 2), (3, 4)] == desc.m_PadList + assert ann.DataLayout_NCHW == desc.m_DataLayout + + +def test_space_to_batch_nd_descriptor_ctor(): + desc = ann.SpaceToBatchNdDescriptor((1, 2, 3), [(1, 2), (3, 4)]) + assert [1, 2, 3] == desc.m_BlockShape + assert [(1, 2), (3, 4)] == desc.m_PadList + assert ann.DataLayout_NCHW == desc.m_DataLayout + + +def test_transpose_convolution2d_descriptor_default_values(): + desc = ann.DepthwiseConvolution2dDescriptor() + assert desc.m_PadLeft == 0 + assert desc.m_PadTop == 0 + assert desc.m_PadRight == 0 + assert desc.m_PadBottom == 0 + assert desc.m_StrideX == 0 + assert desc.m_StrideY == 0 + assert desc.m_BiasEnabled == False + assert desc.m_DataLayout == ann.DataLayout_NCHW + + +def test_view_descriptor_default_values(): + desc = ann.SplitterDescriptor() + assert 0 == desc.GetNumViews() + assert 0 == desc.GetNumDimensions() + + +def test_elementwise_unary_descriptor_default_values(): + desc = ann.ElementwiseUnaryDescriptor() + assert desc.m_Operation == ann.UnaryOperation_Abs + + +def test_view_descriptor_incorrect_input(): + desc = ann.SplitterDescriptor(2, 3) + with pytest.raises(RuntimeError) as err: + desc.SetViewOriginCoord(1000, 100, 1000) + assert "Failed to set view origin coordinates." in str(err.value) + + with pytest.raises(RuntimeError) as err: + desc.SetViewSize(1000, 100, 1000) + assert "Failed to set view size." in str(err.value) + + +def test_view_descriptor_ctor(): + desc = ann.SplitterDescriptor(2, 3) + value_size = 1 + value_orig_coord = 5 + for i in range(desc.GetNumViews()): + for j in range(desc.GetNumDimensions()): + desc.SetViewOriginCoord(i, j, value_orig_coord+i) + desc.SetViewSize(i, j, value_size+i) + + assert 2 == desc.GetNumViews() + assert 3 == desc.GetNumDimensions() + assert [5, 5] == desc.GetViewOrigin(0) + assert [6, 6] == desc.GetViewOrigin(1) + assert [1, 1] == desc.GetViewSizes(0) + assert [2, 2] == desc.GetViewSizes(1) + + +def test_createdescriptorforconcatenation_ctor(): + input_shape_vector = [ann.TensorShape((2, 1)), ann.TensorShape((3, 1)), ann.TensorShape((4, 1))] + desc = ann.CreateDescriptorForConcatenation(input_shape_vector, 0) + assert 3 == desc.GetNumViews() + assert 0 == desc.GetConcatAxis() + assert 2 == desc.GetNumDimensions() + c = desc.GetViewOrigin(1) + d = desc.GetViewOrigin(0) + + +def test_createdescriptorforconcatenation_wrong_shape_for_axis(): + input_shape_vector = [ann.TensorShape((1, 2)), ann.TensorShape((3, 4)), ann.TensorShape((5, 6))] + with pytest.raises(RuntimeError) as err: + desc = ann.CreateDescriptorForConcatenation(input_shape_vector, 0) + + assert "All inputs to concatenation must be the same size along all dimensions except the concatenation dimension" in str( + err.value) + + +@pytest.mark.parametrize("input_shape_vector", [([-1, "one"]), + ([1.33, 4.55]), + ([{1: "one"}])], ids=lambda x: str(x)) +def test_createdescriptorforconcatenation_rubbish_assignment_shape_vector(input_shape_vector): + with pytest.raises(TypeError) as err: + desc = ann.CreateDescriptorForConcatenation(input_shape_vector, 0) + + assert "in method 'CreateDescriptorForConcatenation', argument 1 of type 'std::vector< armnn::TensorShape,std::allocator< armnn::TensorShape > >'" in str( + err.value) + + +generated_classes = inspect.getmembers(generated, inspect.isclass) +generated_classes_names = list(map(lambda x: x[0], generated_classes)) +@pytest.mark.parametrize("desc_name", ['ActivationDescriptor', + 'ArgMinMaxDescriptor', + 'PermuteDescriptor', + 'SoftmaxDescriptor', + 'ConcatDescriptor', + 'SplitterDescriptor', + 'Pooling2dDescriptor', + 'FullyConnectedDescriptor', + 'Convolution2dDescriptor', + 'DepthwiseConvolution2dDescriptor', + 'DetectionPostProcessDescriptor', + 'NormalizationDescriptor', + 'L2NormalizationDescriptor', + 'BatchNormalizationDescriptor', + 'InstanceNormalizationDescriptor', + 'BatchToSpaceNdDescriptor', + 'FakeQuantizationDescriptor', + 'ResizeDescriptor', + 'ReshapeDescriptor', + 'SpaceToBatchNdDescriptor', + 'SpaceToDepthDescriptor', + 'LstmDescriptor', + 'MeanDescriptor', + 'PadDescriptor', + 'SliceDescriptor', + 'StackDescriptor', + 'StridedSliceDescriptor', + 'TransposeConvolution2dDescriptor', + 'ElementwiseUnaryDescriptor']) +class TestDescriptorMassChecks: + + def test_desc_implemented(self, desc_name): + assert desc_name in generated_classes_names + + def test_desc_equal(self, desc_name): + desc_class = next(filter(lambda x: x[0] == desc_name, generated_classes))[1] + + assert desc_class() == desc_class() + + +generated_classes = inspect.getmembers(generated, inspect.isclass) +generated_classes_names = list(map(lambda x: x[0], generated_classes)) +@pytest.mark.parametrize("desc_name", ['ActivationDescriptor', + 'ArgMinMaxDescriptor', + 'PermuteDescriptor', + 'SoftmaxDescriptor', + 'ConcatDescriptor', + 'SplitterDescriptor', + 'Pooling2dDescriptor', + 'FullyConnectedDescriptor', + 'Convolution2dDescriptor', + 'DepthwiseConvolution2dDescriptor', + 'DetectionPostProcessDescriptor', + 'NormalizationDescriptor', + 'L2NormalizationDescriptor', + 'BatchNormalizationDescriptor', + 'InstanceNormalizationDescriptor', + 'BatchToSpaceNdDescriptor', + 'FakeQuantizationDescriptor', + 'ResizeDescriptor', + 'ReshapeDescriptor', + 'SpaceToBatchNdDescriptor', + 'SpaceToDepthDescriptor', + 'LstmDescriptor', + 'MeanDescriptor', + 'PadDescriptor', + 'SliceDescriptor', + 'StackDescriptor', + 'StridedSliceDescriptor', + 'TransposeConvolution2dDescriptor', + 'ElementwiseUnaryDescriptor']) +class TestDescriptorMassChecks: + + def test_desc_implemented(self, desc_name): + assert desc_name in generated_classes_names + + def test_desc_equal(self, desc_name): + desc_class = next(filter(lambda x: x[0] == desc_name, generated_classes))[1] + + assert desc_class() == desc_class() + diff --git a/python/pyarmnn/test/test_generated.py b/python/pyarmnn/test/test_generated.py new file mode 100644 index 0000000..24765c7 --- /dev/null +++ b/python/pyarmnn/test/test_generated.py @@ -0,0 +1,52 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import inspect +from typing import Tuple + +import pytest + +import pyarmnn._generated.pyarmnn as generated_armnn +import pyarmnn._generated.pyarmnn_caffeparser as generated_caffe +import pyarmnn._generated.pyarmnn_onnxparser as generated_onnx +import pyarmnn._generated.pyarmnn_tfliteparser as generated_tflite +import pyarmnn._generated.pyarmnn_tfparser as generated_tf + +swig_independent_classes = ('IBackend', + 'IDeviceSpec', + 'IConnectableLayer', + 'IInputSlot', + 'IOutputSlot', + 'IProfiler') + + +def get_classes(swig_independent_classes: Tuple): + # We need to ignore some swig generated_armnn classes. This is because some are abstract classes + # They cannot be created with the swig generated_armnn wrapper, therefore they don't need a destructor. + # Swig also generates its own meta class - this needs to be ignored. + ignored_class_names = (*swig_independent_classes, '_SwigNonDynamicMeta') + return list(filter(lambda x: x[0] not in ignored_class_names, + inspect.getmembers(generated_armnn, inspect.isclass) + + inspect.getmembers(generated_caffe, inspect.isclass) + + inspect.getmembers(generated_tflite, inspect.isclass) + + inspect.getmembers(generated_onnx, inspect.isclass) + + inspect.getmembers(generated_tf, inspect.isclass))) + + +@pytest.mark.parametrize("class_instance", get_classes(swig_independent_classes), ids=lambda x: 'class={}'.format(x[0])) +class TestPyOwnedClasses: + + def test_destructors_exist_per_class(self, class_instance): + assert getattr(class_instance[1], '__swig_destroy__', None) + + def test_owned(self, class_instance): + assert getattr(class_instance[1], 'thisown', None) + + +@pytest.mark.parametrize("class_instance", swig_independent_classes) +class TestPyIndependentClasses: + + def test_destructors_does_not_exist_per_class(self, class_instance): + assert not getattr(class_instance[1], '__swig_destroy__', None) + + def test_not_owned(self, class_instance): + assert not getattr(class_instance[1], 'thisown', None) diff --git a/python/pyarmnn/test/test_iconnectable.py b/python/pyarmnn/test/test_iconnectable.py new file mode 100644 index 0000000..0d15be5 --- /dev/null +++ b/python/pyarmnn/test/test_iconnectable.py @@ -0,0 +1,142 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import pytest + +import pyarmnn as ann + + +@pytest.fixture(scope="function") +def network(): + return ann.INetwork() + + +class TestIInputIOutputIConnectable: + + def test_input_slot(self, network): + # Create input, addition & output layer + input1 = network.AddInputLayer(0, "input1") + input2 = network.AddInputLayer(1, "input2") + add = network.AddAdditionLayer("addition") + output = network.AddOutputLayer(0, "output") + + # Connect the input/output slots for each layer + input1.GetOutputSlot(0).Connect(add.GetInputSlot(0)) + input2.GetOutputSlot(0).Connect(add.GetInputSlot(1)) + add.GetOutputSlot(0).Connect(output.GetInputSlot(0)) + + # Check IInputSlot GetConnection() + input_slot = add.GetInputSlot(0) + input_slot_connection = input_slot.GetConnection() + + assert isinstance(input_slot_connection, ann.IOutputSlot) + + del input_slot_connection + + assert input_slot.GetConnection() + assert isinstance(input_slot.GetConnection(), ann.IOutputSlot) + + del input_slot + + assert add.GetInputSlot(0) + + def test_output_slot(self, network): + + # Create input, addition & output layer + input1 = network.AddInputLayer(0, "input1") + input2 = network.AddInputLayer(1, "input2") + add = network.AddAdditionLayer("addition") + output = network.AddOutputLayer(0, "output") + + # Connect the input/output slots for each layer + input1.GetOutputSlot(0).Connect(add.GetInputSlot(0)) + input2.GetOutputSlot(0).Connect(add.GetInputSlot(1)) + add.GetOutputSlot(0).Connect(output.GetInputSlot(0)) + + # Check IInputSlot GetConnection() + add_get_input_connection = add.GetInputSlot(0).GetConnection() + output_get_input_connection = output.GetInputSlot(0).GetConnection() + + # Check IOutputSlot GetConnection() + add_get_output_connect = add.GetOutputSlot(0).GetConnection(0) + assert isinstance(add_get_output_connect.GetConnection(), ann.IOutputSlot) + + # Test IOutputSlot GetNumConnections() & CalculateIndexOnOwner() + assert add_get_input_connection.GetNumConnections() == 1 + assert len(add_get_input_connection) == 1 + assert add_get_input_connection[0] + assert add_get_input_connection.CalculateIndexOnOwner() == 0 + + # Check GetOwningLayerGuid(). Check that it is different for add and output layer + assert add_get_input_connection.GetOwningLayerGuid() != output_get_input_connection.GetOwningLayerGuid() + + # Set TensorInfo + test_tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_Float32) + + # Check IsTensorInfoSet() + assert not add_get_input_connection.IsTensorInfoSet() + add_get_input_connection.SetTensorInfo(test_tensor_info) + assert add_get_input_connection.IsTensorInfoSet() + + # Check GetTensorInfo() + output_tensor_info = add_get_input_connection.GetTensorInfo() + assert 2 == output_tensor_info.GetNumDimensions() + assert 6 == output_tensor_info.GetNumElements() + + # Check Disconnect() + assert output_get_input_connection.GetNumConnections() == 1 # 1 connection to Outputslot0 from input1 + add.GetOutputSlot(0).Disconnect(output.GetInputSlot(0)) # disconnect add.OutputSlot0 from Output.InputSlot0 + assert output_get_input_connection.GetNumConnections() == 0 + + def test_output_slot__out_of_range(self, network): + # Create input layer to check output slot get item handling + input1 = network.AddInputLayer(0, "input1") + + outputSlot = input1.GetOutputSlot(0) + with pytest.raises(ValueError) as err: + outputSlot[1] + + assert "Invalid index 1 provided" in str(err.value) + + def test_iconnectable_guid(self, network): + + # Check IConnectable GetGuid() + # Note Guid can change based on which tests are run so + # checking here that each layer does not have the same guid + add_id = network.AddAdditionLayer().GetGuid() + output_id = network.AddOutputLayer(0).GetGuid() + assert add_id != output_id + + def test_iconnectable_layer_functions(self, network): + + # Create input, addition & output layer + input1 = network.AddInputLayer(0, "input1") + input2 = network.AddInputLayer(1, "input2") + add = network.AddAdditionLayer("addition") + output = network.AddOutputLayer(0, "output") + + # Check GetNumInputSlots(), GetName() & GetNumOutputSlots() + assert input1.GetNumInputSlots() == 0 + assert input1.GetName() == "input1" + assert input1.GetNumOutputSlots() == 1 + + assert input2.GetNumInputSlots() == 0 + assert input2.GetName() == "input2" + assert input2.GetNumOutputSlots() == 1 + + assert add.GetNumInputSlots() == 2 + assert add.GetName() == "addition" + assert add.GetNumOutputSlots() == 1 + + assert output.GetNumInputSlots() == 1 + assert output.GetName() == "output" + assert output.GetNumOutputSlots() == 0 + + # Check GetOutputSlot() + input1_get_output = input1.GetOutputSlot(0) + assert input1_get_output.GetNumConnections() == 0 + assert len(input1_get_output) == 0 + + # Check GetInputSlot() + add_get_input = add.GetInputSlot(0) + add_get_input.GetConnection() + assert isinstance(add_get_input, ann.IInputSlot) diff --git a/python/pyarmnn/test/test_network.py b/python/pyarmnn/test/test_network.py new file mode 100644 index 0000000..fc2591c --- /dev/null +++ b/python/pyarmnn/test/test_network.py @@ -0,0 +1,288 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os +import stat + +import pytest +import pyarmnn as ann + + +@pytest.fixture(scope="function") +def get_runtime(shared_data_folder, network_file): + parser= ann.ITfLiteParser() + preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, network_file)) + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + yield preferred_backends, network, runtime + + +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite', + ], + ids=['mock_model']) +def test_optimize_executes_successfully(network_file, get_runtime): + preferred_backends = [ann.BackendId('CpuRef')] + network = get_runtime[1] + runtime = get_runtime[2] + + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + assert len(messages) == 0, 'With only CpuRef, there should be no warnings irrelevant of architecture.' + assert opt_network + + +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite', + ], + ids=['mock_model']) +def test_optimize_owned_by_python(network_file, get_runtime): + preferred_backends = get_runtime[0] + network = get_runtime[1] + runtime = get_runtime[2] + + opt_network, _ = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + assert opt_network.thisown + + +@pytest.mark.aarch64 +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite' + ], + ids=['mock_model']) +def test_optimize_executes_successfully_for_neon_backend_only(network_file, get_runtime): + preferred_backends = [ann.BackendId('CpuAcc')] + network = get_runtime[1] + runtime = get_runtime[2] + + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + assert 0 == len(messages) + assert opt_network + + +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite' + ], + ids=['mock_model']) +def test_optimize_fails_for_invalid_backends(network_file, get_runtime): + invalid_backends = [ann.BackendId('Unknown')] + network = get_runtime[1] + runtime = get_runtime[2] + + with pytest.raises(RuntimeError) as err: + ann.Optimize(network, invalid_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + expected_error_message = "None of the preferred backends [Unknown ] are supported." + assert expected_error_message in str(err.value) + + +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite' + ], + ids=['mock_model']) +def test_optimize_fails_for_no_backends_specified(network_file, get_runtime): + empty_backends = [] + network = get_runtime[1] + runtime = get_runtime[2] + + with pytest.raises(RuntimeError) as err: + ann.Optimize(network, empty_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + expected_error_message = "Invoked Optimize with no backends specified" + assert expected_error_message in str(err.value) + + +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite' + ], + ids=['mock_model']) +def test_serialize_to_dot(network_file, get_runtime, tmpdir): + preferred_backends = get_runtime[0] + network = get_runtime[1] + runtime = get_runtime[2] + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + dot_file_path = os.path.join(tmpdir, 'mock_model.dot') + """Check that serialized file does not exist at the start, gets created after SerializeToDot and is not empty""" + assert not os.path.exists(dot_file_path) + opt_network.SerializeToDot(dot_file_path) + + assert os.path.exists(dot_file_path) + + with open(dot_file_path) as res_file: + expected_data = res_file.read() + assert len(expected_data) > 1 + assert '[label=< [1,28,28,1] >]' in expected_data + + +@pytest.mark.x86_64 +@pytest.mark.parametrize("network_file", + [ + 'mock_model.tflite' + ], + ids=['mock_model']) +def test_serialize_to_dot_mode_readonly(network_file, get_runtime, tmpdir): + preferred_backends = get_runtime[0] + network = get_runtime[1] + runtime = get_runtime[2] + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + """Create file, write to it and change mode to read-only""" + dot_file_path = os.path.join(tmpdir, 'mock_model.dot') + f = open(dot_file_path, "w+") + f.write("test") + f.close() + os.chmod(dot_file_path, stat.S_IREAD) + assert os.path.exists(dot_file_path) + + with pytest.raises(RuntimeError) as err: + opt_network.SerializeToDot(dot_file_path) + + expected_error_message = "Failed to open dot file" + assert expected_error_message in str(err.value) + + +@pytest.mark.parametrize("method", [ + 'AddActivationLayer', + 'AddAdditionLayer', + 'AddArgMinMaxLayer', + 'AddBatchNormalizationLayer', + 'AddBatchToSpaceNdLayer', + 'AddComparisonLayer', + 'AddConcatLayer', + 'AddConstantLayer', + 'AddConvolution2dLayer', + 'AddDepthToSpaceLayer', + 'AddDepthwiseConvolution2dLayer', + 'AddDequantizeLayer', + 'AddDetectionPostProcessLayer', + 'AddDivisionLayer', + 'AddElementwiseUnaryLayer', + 'AddFloorLayer', + 'AddFullyConnectedLayer', + 'AddGatherLayer', + 'AddInputLayer', + 'AddInstanceNormalizationLayer', + 'AddLogSoftmaxLayer', + 'AddL2NormalizationLayer', + 'AddLstmLayer', + 'AddMaximumLayer', + 'AddMeanLayer', + 'AddMergeLayer', + 'AddMinimumLayer', + 'AddMultiplicationLayer', + 'AddNormalizationLayer', + 'AddOutputLayer', + 'AddPadLayer', + 'AddPermuteLayer', + 'AddPooling2dLayer', + 'AddPreluLayer', + 'AddQuantizeLayer', + 'AddQuantizedLstmLayer', + 'AddReshapeLayer', + 'AddResizeLayer', + 'AddSliceLayer', + 'AddSoftmaxLayer', + 'AddSpaceToBatchNdLayer', + 'AddSpaceToDepthLayer', + 'AddSplitterLayer', + 'AddStackLayer', + 'AddStandInLayer', + 'AddStridedSliceLayer', + 'AddSubtractionLayer', + 'AddSwitchLayer', + 'AddTransposeConvolution2dLayer' +]) +def test_network_method_exists(method): + assert getattr(ann.INetwork, method, None) + + +def test_fullyconnected_layer_optional_none(): + net = ann.INetwork() + layer = net.AddFullyConnectedLayer(fullyConnectedDescriptor=ann.FullyConnectedDescriptor(), + weights=ann.ConstTensor()) + + assert layer + + +def test_fullyconnected_layer_optional_provided(): + net = ann.INetwork() + layer = net.AddFullyConnectedLayer(fullyConnectedDescriptor=ann.FullyConnectedDescriptor(), + weights=ann.ConstTensor(), + biases=ann.ConstTensor()) + + assert layer + + +def test_fullyconnected_layer_all_args(): + net = ann.INetwork() + layer = net.AddFullyConnectedLayer(fullyConnectedDescriptor=ann.FullyConnectedDescriptor(), + weights=ann.ConstTensor(), + biases=ann.ConstTensor(), + name='NAME1') + + assert layer + assert 'NAME1' == layer.GetName() + + +def test_DepthwiseConvolution2d_layer_optional_none(): + net = ann.INetwork() + layer = net.AddDepthwiseConvolution2dLayer(convolution2dDescriptor=ann.DepthwiseConvolution2dDescriptor(), + weights=ann.ConstTensor()) + + assert layer + + +def test_DepthwiseConvolution2d_layer_optional_provided(): + net = ann.INetwork() + layer = net.AddDepthwiseConvolution2dLayer(convolution2dDescriptor=ann.DepthwiseConvolution2dDescriptor(), + weights=ann.ConstTensor(), + biases=ann.ConstTensor()) + + assert layer + + +def test_DepthwiseConvolution2d_layer_all_args(): + net = ann.INetwork() + layer = net.AddDepthwiseConvolution2dLayer(convolution2dDescriptor=ann.DepthwiseConvolution2dDescriptor(), + weights=ann.ConstTensor(), + biases=ann.ConstTensor(), + name='NAME1') + + assert layer + assert 'NAME1' == layer.GetName() + + +def test_Convolution2d_layer_optional_none(): + net = ann.INetwork() + layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(), + weights=ann.ConstTensor()) + + assert layer + + +def test_Convolution2d_layer_optional_provided(): + net = ann.INetwork() + layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(), + weights=ann.ConstTensor(), + biases=ann.ConstTensor()) + + assert layer + + +def test_Convolution2d_layer_all_args(): + net = ann.INetwork() + layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(), + weights=ann.ConstTensor(), + biases=ann.ConstTensor(), + name='NAME1') + + assert layer + assert 'NAME1' == layer.GetName() diff --git a/python/pyarmnn/test/test_onnx_parser.py b/python/pyarmnn/test/test_onnx_parser.py new file mode 100644 index 0000000..99353a0 --- /dev/null +++ b/python/pyarmnn/test/test_onnx_parser.py @@ -0,0 +1,111 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest +import pyarmnn as ann +import numpy as np +from typing import List + + +@pytest.fixture() +def parser(shared_data_folder): + """ + Parse and setup the test network to be used for the tests below + """ + + # create onnx parser + parser = ann.IOnnxParser() + + # path to model + path_to_model = os.path.join(shared_data_folder, 'mock_model.onnx') + + # parse onnx binary & create network + parser.CreateNetworkFromBinaryFile(path_to_model) + + yield parser + + +def test_onnx_parser_swig_destroy(): + assert ann.IOnnxParser.__swig_destroy__, "There is a swig python destructor defined" + assert ann.IOnnxParser.__swig_destroy__.__name__ == "delete_IOnnxParser" + + +def test_check_onnx_parser_swig_ownership(parser): + # Check to see that SWIG has ownership for parser. This instructs SWIG to take + # ownership of the return value. This allows the value to be automatically + # garbage-collected when it is no longer in use + assert parser.thisown + + +def test_onnx_parser_get_network_input_binding_info(parser): + input_binding_info = parser.GetNetworkInputBindingInfo("input") + + tensor = input_binding_info[1] + assert tensor.GetDataType() == 1 + assert tensor.GetNumDimensions() == 4 + assert tensor.GetNumElements() == 784 + assert tensor.GetQuantizationOffset() == 0 + assert tensor.GetQuantizationScale() == 0 + + +def test_onnx_parser_get_network_output_binding_info(parser): + output_binding_info = parser.GetNetworkOutputBindingInfo("output") + + tensor = output_binding_info[1] + assert tensor.GetDataType() == 1 + assert tensor.GetNumDimensions() == 4 + assert tensor.GetNumElements() == 10 + assert tensor.GetQuantizationOffset() == 0 + assert tensor.GetQuantizationScale() == 0 + + +def test_onnx_filenotfound_exception(shared_data_folder): + parser = ann.IOnnxParser() + + # path to model + path_to_model = os.path.join(shared_data_folder, 'some_unknown_model.onnx') + + # parse onnx binary & create network + + with pytest.raises(RuntimeError) as err: + parser.CreateNetworkFromBinaryFile(path_to_model) + + # Only check for part of the exception since the exception returns + # absolute path which will change on different machines. + assert 'Invalid (null) filename' in str(err.value) + + +def test_onnx_parser_end_to_end(shared_data_folder): + parser = ann.IOnnxParser = ann.IOnnxParser() + + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.onnx')) + + # load test image data stored in input_onnx.npy + input_binding_info = parser.GetNetworkInputBindingInfo("input") + input_tensor_data = np.load(os.path.join(shared_data_folder, 'onnx_parser/input_onnx.npy')).astype(np.float32) + + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + assert 0 == len(messages) + + net_id, messages = runtime.LoadNetwork(opt_network) + + assert "" == messages + + input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data]) + output_tensors = ann.make_output_tensors([parser.GetNetworkOutputBindingInfo("output")]) + + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + output = ann.workload_tensors_to_ndarray(output_tensors) + + # Load golden output file for result comparison. + golden_output = np.load(os.path.join(shared_data_folder, 'onnx_parser/golden_output_onnx.npy')) + + # Check that output matches golden output to 4 decimal places (there are slight rounding differences after this) + np.testing.assert_almost_equal(output[0], golden_output, decimal=4) diff --git a/python/pyarmnn/test/test_profiling_utilities.py b/python/pyarmnn/test/test_profiling_utilities.py new file mode 100644 index 0000000..b7b91b5 --- /dev/null +++ b/python/pyarmnn/test/test_profiling_utilities.py @@ -0,0 +1,68 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest + +import pyarmnn as ann + + +class MockIProfiler: + def __init__(self, json_string): + self._profile_json = json_string + + def as_json(self): + return self._profile_json + + +@pytest.fixture() +def mock_profiler(shared_data_folder): + path_to_file = os.path.join(shared_data_folder, 'mock_profile_out.json') + with open(path_to_file, 'r') as file: + profiler_output = file.read() + return MockIProfiler(profiler_output) + + +def test_inference_exec(mock_profiler): + profiling_data_obj = ann.get_profiling_data(mock_profiler) + + assert (len(profiling_data_obj.inference_data) > 0) + assert (len(profiling_data_obj.per_workload_execution_data) > 0) + + # Check each total execution time + assert (profiling_data_obj.inference_data["execution_time"] == [1.1, 2.2, 3.3, 4.4, 5.5, 6.6]) + assert (profiling_data_obj.inference_data["time_unit"] == "us") + + +@pytest.mark.parametrize("exec_times, unit, backend, workload", [([2, 2, + 2, 2, + 2, 2], + 'us', + 'CpuRef', + 'RefSomeMock1dWorkload_Execute_#5'), + ([2, 2, + 2, 2, + 2, 2], + 'us', + 'CpuAcc', + 'NeonSomeMock2Workload_Execute_#6'), + ([2, 2, + 2, 2, + 2, 2], + 'us', + 'GpuAcc', + 'ClSomeMock3dWorkload_Execute_#7'), + ([2, 2, + 2, 2, + 2, 2], + 'us', + 'EthosNAcc', + 'EthosNSomeMock4dWorkload_Execute_#8') + ]) +def test_profiler_workloads(mock_profiler, exec_times, unit, backend, workload): + profiling_data_obj = ann.get_profiling_data(mock_profiler) + + work_load_exec = profiling_data_obj.per_workload_execution_data[workload] + assert work_load_exec["execution_time"] == exec_times + assert work_load_exec["time_unit"] == unit + assert work_load_exec["backend"] == backend diff --git a/python/pyarmnn/test/test_quantize_and_dequantize.py b/python/pyarmnn/test/test_quantize_and_dequantize.py new file mode 100644 index 0000000..08fea39 --- /dev/null +++ b/python/pyarmnn/test/test_quantize_and_dequantize.py @@ -0,0 +1,91 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import pytest +import numpy as np + +import pyarmnn as ann + +# import generated so we can test for Dequantize_* and Quantize_* +# functions not available in the public API. +import pyarmnn._generated.pyarmnn as gen_ann + + +@pytest.mark.parametrize('method', ['Quantize_int8_t', + 'Quantize_uint8_t', + 'Quantize_int16_t', + 'Quantize_int32_t', + 'Dequantize_int8_t', + 'Dequantize_uint8_t', + 'Dequantize_int16_t', + 'Dequantize_int32_t']) +def test_quantize_exists(method): + assert method in dir(gen_ann) and callable(getattr(gen_ann, method)) + + +@pytest.mark.parametrize('dt, min, max', [('uint8', 0, 255), + ('int8', -128, 127), + ('int16', -32768, 32767), + ('int32', -2147483648, 2147483647)]) +def test_quantize_uint8_output(dt, min, max): + result = ann.quantize(3.3274056911468506, 0.02620004490017891, 128, dt) + assert type(result) is int and min <= result <= max + + +@pytest.mark.parametrize('dt', ['uint8', + 'int8', + 'int16', + 'int32']) +def test_dequantize_uint8_output(dt): + result = ann.dequantize(3, 0.02620004490017891, 128, dt) + assert type(result) is float + + +def test_quantize_unsupported_dtype(): + with pytest.raises(ValueError) as err: + ann.quantize(3.3274056911468506, 0.02620004490017891, 128, 'uint16') + + assert 'Unexpected target datatype uint16 given.' in str(err.value) + + +def test_dequantize_unsupported_dtype(): + with pytest.raises(ValueError) as err: + ann.dequantize(3, 0.02620004490017891, 128, 'uint16') + + assert 'Unexpected value datatype uint16 given.' in str(err.value) + + +def test_dequantize_value_range(): + with pytest.raises(ValueError) as err: + ann.dequantize(-1, 0.02620004490017891, 128, 'uint8') + + assert 'Value is not within range of the given datatype uint8' in str(err.value) + + +@pytest.mark.parametrize('dt, data', [('uint8', np.uint8(255)), + ('int8', np.int8(127)), + ('int16', np.int16(32767)), + ('int32', np.int32(2147483647)), + + ('uint8', np.int8(127)), + ('uint8', np.int16(255)), + ('uint8', np.int32(255)), + + ('int8', np.uint8(127)), + ('int8', np.int16(127)), + ('int8', np.int32(127)), + + ('int16', np.int8(127)), + ('int16', np.uint8(255)), + ('int16', np.int32(32767)), + + ('int32', np.uint8(255)), + ('int16', np.int8(127)), + ('int32', np.int16(32767)) + + ]) +def test_dequantize_numpy_dt(dt, data): + result = ann.dequantize(data, 1, 0, dt) + + assert type(result) is float + + assert np.float32(data) == result diff --git a/python/pyarmnn/test/test_runtime.py b/python/pyarmnn/test/test_runtime.py new file mode 100644 index 0000000..2943be8 --- /dev/null +++ b/python/pyarmnn/test/test_runtime.py @@ -0,0 +1,257 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest +import numpy as np + +import pyarmnn as ann + + +@pytest.fixture(scope="function") +def random_runtime(shared_data_folder): + parser = ann.ITfLiteParser() + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite')) + preferred_backends = [ann.BackendId('CpuRef')] + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + graphs_count = parser.GetSubgraphCount() + + graph_id = graphs_count - 1 + input_names = parser.GetSubgraphInputTensorNames(graph_id) + + input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, input_names[0]) + input_tensor_id = input_binding_info[0] + + input_tensor_info = input_binding_info[1] + + output_names = parser.GetSubgraphOutputTensorNames(graph_id) + + input_data = np.random.randint(255, size=input_tensor_info.GetNumElements(), dtype=np.uint8) + + const_tensor_pair = (input_tensor_id, ann.ConstTensor(input_tensor_info, input_data)) + + input_tensors = [const_tensor_pair] + + output_tensors = [] + + for index, output_name in enumerate(output_names): + out_bind_info = parser.GetNetworkOutputBindingInfo(graph_id, output_name) + + out_tensor_info = out_bind_info[1] + out_tensor_id = out_bind_info[0] + + output_tensors.append((out_tensor_id, + ann.Tensor(out_tensor_info))) + + yield preferred_backends, network, runtime, input_tensors, output_tensors + + +@pytest.fixture(scope='function') +def mock_model_runtime(shared_data_folder): + parser = ann.ITfLiteParser() + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite')) + graph_id = 0 + + input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, "input_1") + + input_tensor_data = np.load(os.path.join(shared_data_folder, 'tflite_parser/input_lite.npy')) + + preferred_backends = [ann.BackendId('CpuRef')] + + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + print(messages) + + net_id, messages = runtime.LoadNetwork(opt_network) + + print(messages) + + input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data]) + + output_names = parser.GetSubgraphOutputTensorNames(graph_id) + outputs_binding_info = [] + + for output_name in output_names: + outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(graph_id, output_name)) + + output_tensors = ann.make_output_tensors(outputs_binding_info) + + yield runtime, net_id, input_tensors, output_tensors + + +def test_python_disowns_network(random_runtime): + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + runtime.LoadNetwork(opt_network) + + assert not opt_network.thisown + + +def test_load_network(random_runtime): + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + net_id, messages = runtime.LoadNetwork(opt_network) + assert "" == messages + assert net_id == 0 + + +def test_load_network_properties_provided(random_runtime): + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + properties = ann.INetworkProperties(True, True) + net_id, messages = runtime.LoadNetwork(opt_network, properties) + assert "" == messages + assert net_id == 0 + + +def test_unload_network_fails_for_invalid_net_id(random_runtime): + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + + ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + with pytest.raises(RuntimeError) as err: + runtime.UnloadNetwork(9) + + expected_error_message = "Failed to unload network." + assert expected_error_message in str(err.value) + + +def test_enqueue_workload(random_runtime): + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + input_tensors = random_runtime[3] + output_tensors = random_runtime[4] + + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + net_id, _ = runtime.LoadNetwork(opt_network) + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + +def test_enqueue_workload_fails_with_empty_input_tensors(random_runtime): + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + input_tensors = [] + output_tensors = random_runtime[4] + + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + net_id, _ = runtime.LoadNetwork(opt_network) + with pytest.raises(RuntimeError) as err: + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + expected_error_message = "Number of inputs provided does not match network." + assert expected_error_message in str(err.value) + + +@pytest.mark.x86_64 +@pytest.mark.parametrize('count', [5]) +def test_multiple_inference_runs_yield_same_result(count, mock_model_runtime): + """ + Test that results remain consistent among multiple runs of the same inference. + """ + runtime = mock_model_runtime[0] + net_id = mock_model_runtime[1] + input_tensors = mock_model_runtime[2] + output_tensors = mock_model_runtime[3] + + expected_results = np.array([[4, 85, 108, 29, 8, 16, 0, 2, 5, 0]]) + + for _ in range(count): + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + output_vectors = ann.workload_tensors_to_ndarray(output_tensors) + + for i in range(len(expected_results)): + assert output_vectors[i].all() == expected_results[i].all() + + +@pytest.mark.aarch64 +def test_aarch64_inference_results(mock_model_runtime): + + runtime = mock_model_runtime[0] + net_id = mock_model_runtime[1] + input_tensors = mock_model_runtime[2] + output_tensors = mock_model_runtime[3] + + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + output_vectors = ann.workload_tensors_to_ndarray(output_tensors) + + expected_outputs = expected_results = np.array([[4, 85, 108, 29, 8, 16, 0, 2, 5, 0]]) + + for i in range(len(expected_outputs)): + assert output_vectors[i].all() == expected_results[i].all() + + +def test_enqueue_workload_with_profiler(random_runtime): + """ + Tests ArmNN's profiling extension + """ + preferred_backends = random_runtime[0] + network = random_runtime[1] + runtime = random_runtime[2] + input_tensors = random_runtime[3] + output_tensors = random_runtime[4] + + opt_network, _ = ann.Optimize(network, preferred_backends, + runtime.GetDeviceSpec(), ann.OptimizerOptions()) + net_id, _ = runtime.LoadNetwork(opt_network) + + profiler = runtime.GetProfiler(net_id) + # By default profiling should be turned off: + assert profiler.IsProfilingEnabled() is False + + # Enable profiling: + profiler.EnableProfiling(True) + assert profiler.IsProfilingEnabled() is True + + # Run the inference: + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + # Get profile output as a string: + str_profile = profiler.as_json() + + # Verify that certain markers are present: + assert len(str_profile) != 0 + assert str_profile.find('\"ArmNN\": {') > 0 + + # Get events analysis output as a string: + str_events_analysis = profiler.event_log() + + assert "Event Sequence - Name | Duration (ms) | Start (ms) | Stop (ms) | Device" in str_events_analysis + + assert profiler.thisown == 0 + + +def test_check_runtime_swig_ownership(random_runtime): + # Check to see that SWIG has ownership for runtime. This instructs SWIG to take + # ownership of the return value. This allows the value to be automatically + # garbage-collected when it is no longer in use + runtime = random_runtime[2] + assert runtime.thisown diff --git a/python/pyarmnn/test/test_setup.py b/python/pyarmnn/test/test_setup.py new file mode 100644 index 0000000..8396ca0 --- /dev/null +++ b/python/pyarmnn/test/test_setup.py @@ -0,0 +1,100 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os +import sys +import shutil + +import pytest + +sys.path.append(os.path.abspath('..')) +from setup import find_armnn, find_includes, linux_gcc_lib_search, check_armnn_version + + +@pytest.fixture(autouse=True) +def _setup_armnn(tmpdir): + includes = str(os.path.join(tmpdir, 'include')) + libs = str(os.path.join(tmpdir, 'lib')) + os.environ["TEST_ARMNN_INCLUDE"] = includes + os.environ["TEST_ARMNN_LIB"] = libs + os.environ["EMPTY_ARMNN_INCLUDE"] = '' + + os.mkdir(includes) + os.mkdir(libs) + + with open(os.path.join(libs, "libarmnn.so"), "w"): + pass + + with open(os.path.join(libs, "libarmnnSomeThing1.so"), "w"): + pass + with open(os.path.join(libs, "libarmnnSomeThing1.so.1"), "w"): + pass + with open(os.path.join(libs, "libarmnnSomeThing1.so.1.2"), "w"): + pass + + with open(os.path.join(libs, "libarmnnSomeThing2.so"), "w"): + pass + + with open(os.path.join(libs, "libSomeThing3.so"), "w"): + pass + + yield + + del os.environ["TEST_ARMNN_INCLUDE"] + del os.environ["TEST_ARMNN_LIB"] + del os.environ["EMPTY_ARMNN_INCLUDE"] + shutil.rmtree(includes) + shutil.rmtree(libs) + + +def test_find_armnn(tmpdir): + lib_names, lib_paths = find_armnn(lib_name='libarmnn*.so', + armnn_libs_env="TEST_ARMNN_LIB", + default_lib_search=("/lib",)) + armnn_includes = find_includes(armnn_include_env="TEST_ARMNN_INCLUDE") + + assert [':libarmnn.so', ':libarmnnSomeThing1.so', ':libarmnnSomeThing2.so'] == sorted(lib_names) + assert [os.path.join(tmpdir, 'lib')] == lib_paths + assert [os.path.join(tmpdir, 'include')] == armnn_includes + + +def test_find_armnn_default_path(tmpdir): + lib_names, lib_paths = find_armnn(lib_name='libarmnn*.so', + armnn_libs_env="RUBBISH_LIB", + default_lib_search=(os.environ["TEST_ARMNN_LIB"],)) + armnn_includes = find_includes('TEST_ARMNN_INCLUDE') + assert [':libarmnn.so', ':libarmnnSomeThing1.so', ':libarmnnSomeThing2.so'] == sorted(lib_names) + assert [os.path.join(tmpdir, 'lib')] == lib_paths + assert [os.path.join(tmpdir, 'include')] == armnn_includes + + +def test_not_find_armnn(tmpdir): + with pytest.raises(RuntimeError) as err: + find_armnn(lib_name='libarmnn*.so', armnn_libs_env="RUBBISH_LIB", + default_lib_search=("/lib",)) + + assert 'ArmNN library libarmnn*.so was not found in (\'/lib\',)' in str(err.value) + + +@pytest.mark.parametrize("env", ["RUBBISH_INCLUDE", "EMPTY_ARMNN_INCLUDE"]) +def test_rubbish_armnn_include(tmpdir, env): + includes = find_includes(armnn_include_env=env) + assert includes == ['/usr/local/include', '/usr/include'] + + +def test_gcc_serch_path(): + assert linux_gcc_lib_search() + + +def test_armnn_version(): + check_armnn_version('20190800', '20190800') + + +def test_incorrect_armnn_version(): + with pytest.raises(AssertionError) as err: + check_armnn_version('20190800', '20190500') + + assert 'Expected ArmNN version is 201905 but installed ArmNN version is 201908' in str(err.value) + + +def test_armnn_version_patch_does_not_matter(): + check_armnn_version('20190800', '20190801') diff --git a/python/pyarmnn/test/test_supported_backends.py b/python/pyarmnn/test/test_supported_backends.py new file mode 100644 index 0000000..e1ca5ee --- /dev/null +++ b/python/pyarmnn/test/test_supported_backends.py @@ -0,0 +1,50 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os +import platform +import pytest +import pyarmnn as ann + + +@pytest.fixture() +def get_supported_backends_setup(shared_data_folder): + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + get_device_spec = runtime.GetDeviceSpec() + supported_backends = get_device_spec.GetSupportedBackends() + + yield supported_backends + + +def test_ownership(): + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + device_spec = runtime.GetDeviceSpec() + + assert not device_spec.thisown + + +def test_to_string(): + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + device_spec = runtime.GetDeviceSpec() + expected_str = "IDeviceSpec {{ supportedBackends: [" \ + "{}" \ + "]}}".format(', '.join(map(lambda b: str(b), device_spec.GetSupportedBackends()))) + + assert expected_str == str(device_spec) + + +def test_get_supported_backends_cpu_ref(get_supported_backends_setup): + assert "CpuRef" in map(lambda b: str(b), get_supported_backends_setup) + + +@pytest.mark.aarch64 +class TestNoneCpuRefBackends: + + @pytest.mark.parametrize("backend", ["CpuAcc"]) + def test_get_supported_backends_cpu_acc(self, get_supported_backends_setup, backend): + assert backend in map(lambda b: str(b), get_supported_backends_setup) diff --git a/python/pyarmnn/test/test_tensor.py b/python/pyarmnn/test/test_tensor.py new file mode 100644 index 0000000..8b57169 --- /dev/null +++ b/python/pyarmnn/test/test_tensor.py @@ -0,0 +1,144 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +from copy import copy + +import pytest +import numpy as np +import pyarmnn as ann + + +def __get_tensor_info(dt): + tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), dt) + + return tensor_info + + +@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16, + ann.DataType_QAsymmU8, ann.DataType_QSymmS8, + ann.DataType_QAsymmS8]) +def test_create_tensor_with_info(dt): + tensor_info = __get_tensor_info(dt) + elements = tensor_info.GetNumElements() + num_bytes = tensor_info.GetNumBytes() + d_type = dt + + tensor = ann.Tensor(tensor_info) + + assert tensor_info != tensor.GetInfo(), "Different objects" + assert elements == tensor.GetNumElements() + assert num_bytes == tensor.GetNumBytes() + assert d_type == tensor.GetDataType() + + +def test_create_tensor_undefined_datatype(): + tensor_info = ann.TensorInfo() + tensor_info.SetDataType(99) + + with pytest.raises(ValueError) as err: + ann.Tensor(tensor_info) + + assert 'The data type provided for this Tensor is not supported.' in str(err.value) + + +@pytest.mark.parametrize("dt", [ann.DataType_Float32]) +def test_tensor_memory_output(dt): + tensor_info = __get_tensor_info(dt) + tensor = ann.Tensor(tensor_info) + + # empty memory area because inference has not yet been run. + assert tensor.get_memory_area().tolist() # has random stuff + assert 4 == tensor.get_memory_area().itemsize, "it is float32" + + +@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16, + ann.DataType_QAsymmU8, ann.DataType_QSymmS8, + ann.DataType_QAsymmS8]) +def test_tensor__str__(dt): + tensor_info = __get_tensor_info(dt) + elements = tensor_info.GetNumElements() + num_bytes = tensor_info.GetNumBytes() + d_type = dt + dimensions = tensor_info.GetNumDimensions() + + tensor = ann.Tensor(tensor_info) + + assert str(tensor) == "Tensor{{DataType: {}, NumBytes: {}, NumDimensions: " \ + "{}, NumElements: {}}}".format(d_type, num_bytes, dimensions, elements) + + +def test_create_empty_tensor(): + tensor = ann.Tensor() + + assert 0 == tensor.GetNumElements() + assert 0 == tensor.GetNumBytes() + assert tensor.get_memory_area() is None + + +@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16, + ann.DataType_QAsymmU8, ann.DataType_QSymmS8, + ann.DataType_QAsymmS8]) +def test_create_tensor_from_tensor(dt): + tensor_info = __get_tensor_info(dt) + tensor = ann.Tensor(tensor_info) + copied_tensor = ann.Tensor(tensor) + + assert copied_tensor != tensor, "Different objects" + assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects" + assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data, "Same memory area" + assert copied_tensor.GetNumElements() == tensor.GetNumElements() + assert copied_tensor.GetNumBytes() == tensor.GetNumBytes() + assert copied_tensor.GetDataType() == tensor.GetDataType() + + +@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16, + ann.DataType_QAsymmU8, ann.DataType_QSymmS8, + ann.DataType_QAsymmS8]) +def test_copy_tensor(dt): + tensor = ann.Tensor(__get_tensor_info(dt)) + copied_tensor = copy(tensor) + + assert copied_tensor != tensor, "Different objects" + assert copied_tensor.GetInfo() != tensor.GetInfo(), "Different objects" + assert copied_tensor.get_memory_area().ctypes.data == tensor.get_memory_area().ctypes.data, "Same memory area" + assert copied_tensor.GetNumElements() == tensor.GetNumElements() + assert copied_tensor.GetNumBytes() == tensor.GetNumBytes() + assert copied_tensor.GetDataType() == tensor.GetDataType() + + +@pytest.mark.parametrize("dt", [ann.DataType_Float32, ann.DataType_Float16, + ann.DataType_QAsymmU8, ann.DataType_QSymmS8, + ann.DataType_QAsymmS8]) +def test_copied_tensor_has_memory_area_access_after_deletion_of_original_tensor(dt): + + tensor = ann.Tensor(__get_tensor_info(dt)) + + tensor.get_memory_area()[0] = 100 + + initial_mem_copy = np.array(tensor.get_memory_area()) + + assert 100 == initial_mem_copy[0] + + copied_tensor = ann.Tensor(tensor) + + del tensor + np.testing.assert_array_equal(copied_tensor.get_memory_area(), initial_mem_copy) + assert 100 == copied_tensor.get_memory_area()[0] + + +def test_create_const_tensor_incorrect_args(): + with pytest.raises(ValueError) as err: + ann.Tensor('something', 'something') + + expected_error_message = "Incorrect number of arguments or type of arguments provided to create Tensor." + assert expected_error_message in str(err.value) + + +@pytest.mark.parametrize("dt", [ann.DataType_Float16]) +def test_tensor_memory_output_fp16(dt): + # Check Tensor with float16 + tensor_info = __get_tensor_info(dt) + tensor = ann.Tensor(tensor_info) + + assert tensor.GetNumElements() == 6 + assert tensor.GetNumBytes() == 12 + assert tensor.GetDataType() == ann.DataType_Float16 diff --git a/python/pyarmnn/test/test_tensor_conversion.py b/python/pyarmnn/test/test_tensor_conversion.py new file mode 100644 index 0000000..a48b00f --- /dev/null +++ b/python/pyarmnn/test/test_tensor_conversion.py @@ -0,0 +1,99 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest +import pyarmnn as ann +import numpy as np + + +@pytest.fixture(scope="function") +def get_tensor_info_input(shared_data_folder): + """ + Sample input tensor information. + """ + parser = ann.ITfLiteParser() + parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite')) + graph_id = 0 + + input_binding_info = [parser.GetNetworkInputBindingInfo(graph_id, 'input_1')] + + yield input_binding_info + + +@pytest.fixture(scope="function") +def get_tensor_info_output(shared_data_folder): + """ + Sample output tensor information. + """ + parser = ann.ITfLiteParser() + parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite')) + graph_id = 0 + + output_names = parser.GetSubgraphOutputTensorNames(graph_id) + outputs_binding_info = [] + + for output_name in output_names: + outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(graph_id, output_name)) + + yield outputs_binding_info + + +def test_make_input_tensors(get_tensor_info_input): + input_tensor_info = get_tensor_info_input + input_data = [] + + for tensor_id, tensor_info in input_tensor_info: + input_data.append(np.random.randint(0, 255, size=(1, tensor_info.GetNumElements())).astype(np.uint8)) + + input_tensors = ann.make_input_tensors(input_tensor_info, input_data) + assert len(input_tensors) == 1 + + for tensor, tensor_info in zip(input_tensors, input_tensor_info): + # Because we created ConstTensor function, we cannot check type directly. + assert type(tensor[1]).__name__ == 'ConstTensor' + assert str(tensor[1].GetInfo()) == str(tensor_info[1]) + + +def test_make_output_tensors(get_tensor_info_output): + output_binding_info = get_tensor_info_output + + output_tensors = ann.make_output_tensors(output_binding_info) + assert len(output_tensors) == 1 + + for tensor, tensor_info in zip(output_tensors, output_binding_info): + assert type(tensor[1]) == ann.Tensor + assert str(tensor[1].GetInfo()) == str(tensor_info[1]) + + +def test_workload_tensors_to_ndarray(get_tensor_info_output): + # Check shape and size of output from workload_tensors_to_ndarray matches expected. + output_binding_info = get_tensor_info_output + output_tensors = ann.make_output_tensors(output_binding_info) + + data = ann.workload_tensors_to_ndarray(output_tensors) + + for i in range(0, len(output_tensors)): + assert data[i].shape == tuple(output_tensors[i][1].GetShape()) + assert data[i].size == output_tensors[i][1].GetNumElements() + + +def test_make_input_tensors_fp16(get_tensor_info_input): + # Check ConstTensor with float16 + input_tensor_info = get_tensor_info_input + input_data = [] + + for tensor_id, tensor_info in input_tensor_info: + input_data.append(np.random.randint(0, 255, size=(1, tensor_info.GetNumElements())).astype(np.float16)) + tensor_info.SetDataType(ann.DataType_Float16) # set datatype to float16 + + input_tensors = ann.make_input_tensors(input_tensor_info, input_data) + assert len(input_tensors) == 1 + + for tensor, tensor_info in zip(input_tensors, input_tensor_info): + # Because we created ConstTensor function, we cannot check type directly. + assert type(tensor[1]).__name__ == 'ConstTensor' + assert str(tensor[1].GetInfo()) == str(tensor_info[1]) + assert tensor[1].GetDataType() == ann.DataType_Float16 + assert tensor[1].GetNumElements() == 28*28*1 + assert tensor[1].GetNumBytes() == (28*28*1)*2 # check each element is two byte diff --git a/python/pyarmnn/test/test_tensor_info.py b/python/pyarmnn/test/test_tensor_info.py new file mode 100644 index 0000000..dc73533 --- /dev/null +++ b/python/pyarmnn/test/test_tensor_info.py @@ -0,0 +1,27 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import pyarmnn as ann + + +def test_tensor_info_ctor_shape(): + tensor_shape = ann.TensorShape((1, 1, 2)) + + tensor_info = ann.TensorInfo(tensor_shape, ann.DataType_QAsymmU8, 0.5, 1) + + assert 2 == tensor_info.GetNumElements() + assert 3 == tensor_info.GetNumDimensions() + assert ann.DataType_QAsymmU8 == tensor_info.GetDataType() + assert 0.5 == tensor_info.GetQuantizationScale() + assert 1 == tensor_info.GetQuantizationOffset() + + shape = tensor_info.GetShape() + + assert 2 == shape.GetNumElements() + assert 3 == shape.GetNumDimensions() + + +def test_tensor_info__str__(): + tensor_info = ann.TensorInfo(ann.TensorShape((2, 3)), ann.DataType_QAsymmU8, 0.5, 1) + + assert tensor_info.__str__() == "TensorInfo{DataType: 2, IsQuantized: 1, QuantizationScale: 0.500000, " \ + "QuantizationOffset: 1, NumDimensions: 2, NumElements: 6}" diff --git a/python/pyarmnn/test/test_tensor_shape.py b/python/pyarmnn/test/test_tensor_shape.py new file mode 100644 index 0000000..c6f731f --- /dev/null +++ b/python/pyarmnn/test/test_tensor_shape.py @@ -0,0 +1,78 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import pytest +import pyarmnn as ann + + +def test_tensor_shape_tuple(): + tensor_shape = ann.TensorShape((1, 2, 3)) + + assert 3 == tensor_shape.GetNumDimensions() + assert 6 == tensor_shape.GetNumElements() + + +def test_tensor_shape_one(): + tensor_shape = ann.TensorShape((10,)) + assert 1 == tensor_shape.GetNumDimensions() + assert 10 == tensor_shape.GetNumElements() + + +def test_tensor_shape_empty(): + with pytest.raises(RuntimeError) as err: + ann.TensorShape(()) + + assert "Tensor numDimensions must be greater than 0" in str(err.value) + + +def test_tensor_shape_tuple_mess(): + tensor_shape = ann.TensorShape((1, "2", 3.0)) + + assert 3 == tensor_shape.GetNumDimensions() + assert 6 == tensor_shape.GetNumElements() + + +def test_tensor_shape_list(): + + with pytest.raises(TypeError) as err: + ann.TensorShape([1, 2, 3]) + + assert "Argument is not a tuple" in str(err.value) + + +def test_tensor_shape_tuple_mess_fail(): + + with pytest.raises(TypeError) as err: + ann.TensorShape((1, "two", 3.0)) + + assert "All elements must be numbers" in str(err.value) + + +def test_tensor_shape_varags(): + with pytest.raises(TypeError) as err: + ann.TensorShape(1, 2, 3) + + assert "__init__() takes 2 positional arguments but 4 were given" in str(err.value) + + +def test_tensor_shape__get_item_out_of_bounds(): + tensor_shape = ann.TensorShape((1, 2, 3)) + with pytest.raises(ValueError) as err: + for i in range(4): + tensor_shape[i] + + assert "Invalid dimension index: 3 (number of dimensions is 3)" in str(err.value) + + +def test_tensor_shape__set_item_out_of_bounds(): + tensor_shape = ann.TensorShape((1, 2, 3)) + with pytest.raises(ValueError) as err: + for i in range(4): + tensor_shape[i] = 1 + + assert "Invalid dimension index: 3 (number of dimensions is 3)" in str(err.value) + + +def test_tensor_shape___str__(): + tensor_shape = ann.TensorShape((1, 2, 3)) + + assert str(tensor_shape) == "TensorShape{Shape(1, 2, 3), NumDimensions: 3, NumElements: 6}" diff --git a/python/pyarmnn/test/test_tf_parser.py b/python/pyarmnn/test/test_tf_parser.py new file mode 100644 index 0000000..796dd71 --- /dev/null +++ b/python/pyarmnn/test/test_tf_parser.py @@ -0,0 +1,133 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest +import pyarmnn as ann +import numpy as np + + +@pytest.fixture() +def parser(shared_data_folder): + """ + Parse and setup the test network to be used for the tests below + """ + + # create tf parser + parser = ann.ITfParser() + + # path to model + path_to_model = os.path.join(shared_data_folder, 'mock_model.pb') + + # tensor shape [1, 28, 28, 1] + tensorshape = {'input': ann.TensorShape((1, 28, 28, 1))} + + # requested_outputs + requested_outputs = ["output"] + + # parse tf binary & create network + parser.CreateNetworkFromBinaryFile(path_to_model, tensorshape, requested_outputs) + + yield parser + + +def test_tf_parser_swig_destroy(): + assert ann.ITfParser.__swig_destroy__, "There is a swig python destructor defined" + assert ann.ITfParser.__swig_destroy__.__name__ == "delete_ITfParser" + + +def test_check_tf_parser_swig_ownership(parser): + # Check to see that SWIG has ownership for parser. This instructs SWIG to take + # ownership of the return value. This allows the value to be automatically + # garbage-collected when it is no longer in use + assert parser.thisown + + +def test_tf_parser_get_network_input_binding_info(parser): + input_binding_info = parser.GetNetworkInputBindingInfo("input") + + tensor = input_binding_info[1] + assert tensor.GetDataType() == 1 + assert tensor.GetNumDimensions() == 4 + assert tensor.GetNumElements() == 28*28*1 + assert tensor.GetQuantizationOffset() == 0 + assert tensor.GetQuantizationScale() == 0 + + +def test_tf_parser_get_network_output_binding_info(parser): + output_binding_info = parser.GetNetworkOutputBindingInfo("output") + + tensor = output_binding_info[1] + assert tensor.GetDataType() == 1 + assert tensor.GetNumDimensions() == 2 + assert tensor.GetNumElements() == 10 + assert tensor.GetQuantizationOffset() == 0 + assert tensor.GetQuantizationScale() == 0 + + +def test_tf_filenotfound_exception(shared_data_folder): + parser = ann.ITfParser() + + # path to model + path_to_model = os.path.join(shared_data_folder, 'some_unknown_model.pb') + + # tensor shape [1, 1, 1, 1] + tensorshape = {'input': ann.TensorShape((1, 1, 1, 1))} + + # requested_outputs + requested_outputs = [""] + + # parse tf binary & create network + + with pytest.raises(RuntimeError) as err: + parser.CreateNetworkFromBinaryFile(path_to_model, tensorshape, requested_outputs) + + # Only check for part of the exception since the exception returns + # absolute path which will change on different machines. + assert 'failed to open' in str(err.value) + + +def test_tf_parser_end_to_end(shared_data_folder): + parser = ann.ITfParser = ann.ITfParser() + + tensorshape = {'input': ann.TensorShape((1, 28, 28, 1))} + requested_outputs = ["output"] + + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.pb'), + tensorshape, requested_outputs) + + input_binding_info = parser.GetNetworkInputBindingInfo("input") + + # load test image data stored in input_tf.npy + input_tensor_data = np.load(os.path.join(shared_data_folder, 'tf_parser/input_tf.npy')).astype(np.float32) + + preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] + + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + + assert 0 == len(messages) + + net_id, messages = runtime.LoadNetwork(opt_network) + + assert "" == messages + + input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data]) + + outputs_binding_info = [] + + for output_name in requested_outputs: + outputs_binding_info.append(parser.GetNetworkOutputBindingInfo(output_name)) + + output_tensors = ann.make_output_tensors(outputs_binding_info) + + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + output_vectors = ann.workload_tensors_to_ndarray(output_tensors) + + # Load golden output file for result comparison. + golden_output = np.load(os.path.join(shared_data_folder, 'tf_parser/golden_output_tf.npy')) + + # Check that output matches golden output to 4 decimal places (there are slight rounding differences after this) + np.testing.assert_almost_equal(output_vectors[0], golden_output, decimal=4) diff --git a/python/pyarmnn/test/test_tflite_parser.py b/python/pyarmnn/test/test_tflite_parser.py new file mode 100644 index 0000000..344ec7c --- /dev/null +++ b/python/pyarmnn/test/test_tflite_parser.py @@ -0,0 +1,147 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os + +import pytest +import pyarmnn as ann +import numpy as np + + +@pytest.fixture() +def parser(shared_data_folder): + """ + Parse and setup the test network to be used for the tests below + """ + parser = ann.ITfLiteParser() + parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'mock_model.tflite')) + + yield parser + + +def test_tflite_parser_swig_destroy(): + assert ann.ITfLiteParser.__swig_destroy__, "There is a swig python destructor defined" + assert ann.ITfLiteParser.__swig_destroy__.__name__ == "delete_ITfLiteParser" + + +def test_check_tflite_parser_swig_ownership(parser): + # Check to see that SWIG has ownership for parser. This instructs SWIG to take + # ownership of the return value. This allows the value to be automatically + # garbage-collected when it is no longer in use + assert parser.thisown + + +def test_tflite_get_sub_graph_count(parser): + graphs_count = parser.GetSubgraphCount() + assert graphs_count == 1 + + +def test_tflite_get_network_input_binding_info(parser): + graphs_count = parser.GetSubgraphCount() + graph_id = graphs_count - 1 + + input_names = parser.GetSubgraphInputTensorNames(graph_id) + + input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, input_names[0]) + + tensor = input_binding_info[1] + assert tensor.GetDataType() == 2 + assert tensor.GetNumDimensions() == 4 + assert tensor.GetNumElements() == 784 + assert tensor.GetQuantizationOffset() == 128 + assert tensor.GetQuantizationScale() == 0.007843137718737125 + + +def test_tflite_get_network_output_binding_info(parser): + graphs_count = parser.GetSubgraphCount() + graph_id = graphs_count - 1 + + output_names = parser.GetSubgraphOutputTensorNames(graph_id) + + output_binding_info1 = parser.GetNetworkOutputBindingInfo(graph_id, output_names[0]) + + # Check the tensor info retrieved from GetNetworkOutputBindingInfo + tensor1 = output_binding_info1[1] + + assert tensor1.GetDataType() == 2 + assert tensor1.GetNumDimensions() == 2 + assert tensor1.GetNumElements() == 10 + assert tensor1.GetQuantizationOffset() == 0 + assert tensor1.GetQuantizationScale() == 0.00390625 + + +def test_tflite_get_subgraph_input_tensor_names(parser): + graphs_count = parser.GetSubgraphCount() + graph_id = graphs_count - 1 + + input_names = parser.GetSubgraphInputTensorNames(graph_id) + + assert input_names == ('input_1',) + + +def test_tflite_get_subgraph_output_tensor_names(parser): + graphs_count = parser.GetSubgraphCount() + graph_id = graphs_count - 1 + + output_names = parser.GetSubgraphOutputTensorNames(graph_id) + + assert output_names[0] == 'dense/Softmax' + + +def test_tflite_filenotfound_exception(shared_data_folder): + parser = ann.ITfLiteParser() + + with pytest.raises(RuntimeError) as err: + parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, 'some_unknown_network.tflite')) + + # Only check for part of the exception since the exception returns + # absolute path which will change on different machines. + assert 'Cannot find the file' in str(err.value) + + +def test_tflite_parser_end_to_end(shared_data_folder): + parser = ann.ITfLiteParser() + + network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, "mock_model.tflite")) + + graphs_count = parser.GetSubgraphCount() + graph_id = graphs_count - 1 + + input_names = parser.GetSubgraphInputTensorNames(graph_id) + input_binding_info = parser.GetNetworkInputBindingInfo(graph_id, input_names[0]) + + output_names = parser.GetSubgraphOutputTensorNames(graph_id) + + preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] + + options = ann.CreationOptions() + runtime = ann.IRuntime(options) + + opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) + assert 0 == len(messages) + + net_id, messages = runtime.LoadNetwork(opt_network) + assert "" == messages + + # Load test image data stored in input_lite.npy + input_tensor_data = np.load(os.path.join(shared_data_folder, 'tflite_parser/input_lite.npy')) + input_tensors = ann.make_input_tensors([input_binding_info], [input_tensor_data]) + + output_tensors = [] + for index, output_name in enumerate(output_names): + out_bind_info = parser.GetNetworkOutputBindingInfo(graph_id, output_name) + out_tensor_info = out_bind_info[1] + out_tensor_id = out_bind_info[0] + output_tensors.append((out_tensor_id, + ann.Tensor(out_tensor_info))) + + runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) + + output_vectors = [] + for index, out_tensor in enumerate(output_tensors): + output_vectors.append(out_tensor[1].get_memory_area()) + + # Load golden output file for result comparison. + expected_outputs = np.load(os.path.join(shared_data_folder, 'tflite_parser/golden_output_lite.npy')) + + # Check that output matches golden output + assert (expected_outputs == output_vectors[0]).all() diff --git a/python/pyarmnn/test/test_types.py b/python/pyarmnn/test/test_types.py new file mode 100644 index 0000000..dfe1429 --- /dev/null +++ b/python/pyarmnn/test/test_types.py @@ -0,0 +1,29 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import pytest +import pyarmnn as ann + + +def test_activation_function(): + assert 0 == ann.ActivationFunction_Sigmoid + assert 1 == ann.ActivationFunction_TanH + assert 2 == ann.ActivationFunction_Linear + assert 3 == ann.ActivationFunction_ReLu + assert 4 == ann.ActivationFunction_BoundedReLu + assert 5 == ann.ActivationFunction_SoftReLu + assert 6 == ann.ActivationFunction_LeakyReLu + assert 7 == ann.ActivationFunction_Abs + assert 8 == ann.ActivationFunction_Sqrt + assert 9 == ann.ActivationFunction_Square + + +def test_permutation_vector(): + pv = ann.PermutationVector((0, 2, 3, 1)) + assert pv[0] == 0 + assert pv[2] == 3 + + pv2 = ann.PermutationVector((0, 2, 3, 1)) + assert pv == pv2 + + pv4 = ann.PermutationVector((0, 3, 1, 2)) + assert pv.IsInverse(pv4) diff --git a/python/pyarmnn/test/test_version.py b/python/pyarmnn/test/test_version.py new file mode 100644 index 0000000..2ea0fd8 --- /dev/null +++ b/python/pyarmnn/test/test_version.py @@ -0,0 +1,35 @@ +# Copyright © 2020 Arm Ltd. All rights reserved. +# SPDX-License-Identifier: MIT +import os +import importlib + + +def test_rel_version(): + import pyarmnn._version as v + importlib.reload(v) + assert "dev" not in v.__version__ + del v + + +def test_dev_version(): + import pyarmnn._version as v + os.environ["PYARMNN_DEV_VER"] = "1" + + importlib.reload(v) + + assert "20.2.0.dev1" == v.__version__ + + del os.environ["PYARMNN_DEV_VER"] + del v + + +def test_arm_version_not_affected(): + import pyarmnn._version as v + os.environ["PYARMNN_DEV_VER"] = "1" + + importlib.reload(v) + + assert "20200200" == v.__arm_ml_version__ + + del os.environ["PYARMNN_DEV_VER"] + del v diff --git a/python/pyarmnn/test/testdata/shared/caffe_parser/golden_output_caffe.npy b/python/pyarmnn/test/testdata/shared/caffe_parser/golden_output_caffe.npy new file mode 100644 index 0000000000000000000000000000000000000000..007141cb9f5f2453a56653f2a61eda716fabb236 GIT binary patch literal 168 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqpcnmP)#3giMVSKlW#=bSHE2fnqia}$WL`+m>Ww)Tvc-O}_}>m}#E O+T6^VY4zYWz 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 Xz`$Vf(5qCu95a)qgB5wP%9sBKo&7nd literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/license.txt b/python/pyarmnn/test/testdata/shared/license.txt new file mode 100644 index 0000000..1e95a68 --- /dev/null +++ b/python/pyarmnn/test/testdata/shared/license.txt @@ -0,0 +1,10 @@ +This folder contains models and data needed for the testing of PyArmNN. + +All models and files found in this folder were created by ARM for the purpose +of testing PyArmNN. + +All the contents of this folder are distributed with the following license. + +Copyright © 2020 Arm Ltd. All rights reserved. +SPDX-License-Identifier: MIT + diff --git a/python/pyarmnn/test/testdata/shared/mock_model.caffemodel b/python/pyarmnn/test/testdata/shared/mock_model.caffemodel new file mode 100644 index 0000000000000000000000000000000000000000..df4079b7292264bf431e4df3103bd3928267252a GIT binary patch literal 138926 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 literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/mock_model.onnx b/python/pyarmnn/test/testdata/shared/mock_model.onnx new file mode 100644 index 0000000000000000000000000000000000000000..c1b506cc16ce1d1714f249c1da674594eb207ff6 GIT binary patch literal 139104 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

SKY*8(8!CIpmE$ubkd6k++@{ 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 literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/mock_model.pb b/python/pyarmnn/test/testdata/shared/mock_model.pb new file mode 100644 index 0000000000000000000000000000000000000000..cff9dc7addbc35c8316daf275e3edcac7853492c GIT binary patch literal 141105 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 Cf~wR2 literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/mock_model.tflite b/python/pyarmnn/test/testdata/shared/mock_model.tflite new file mode 100644 index 0000000000000000000000000000000000000000..0b8944d3ed284ef889abe598226fbc2b51068517 GIT binary patch literal 37944 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 S)+w_EZ24#9+eSa>-3tJRA2AmI literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/onnx_parser/input_onnx.npy b/python/pyarmnn/test/testdata/shared/onnx_parser/input_onnx.npy new file mode 100644 index 0000000000000000000000000000000000000000..15df758b58f988471b8929d5da05a9750fd29c54 GIT binary patch literal 3264 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 Xz`$Vf(5qCu95a)qgB5wP%9sBKo&7nd literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/tf_parser/golden_output_tf.npy b/python/pyarmnn/test/testdata/shared/tf_parser/golden_output_tf.npy new file mode 100644 index 0000000000000000000000000000000000000000..007141cb9f5f2453a56653f2a61eda716fabb236 GIT binary patch literal 168 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 YMNm+Pc<5a!URuwf>0m`(tn%ak0G?SnrvLx| literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/tflite_parser/golden_output_lite.npy b/python/pyarmnn/test/testdata/shared/tflite_parser/golden_output_lite.npy new file mode 100644 index 0000000000000000000000000000000000000000..099f7fed22d71cb0031c48a99b9ba8ef1edcdaa4 GIT binary patch literal 138 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1JQ);NLqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= iXCxM+0{I$-ItqpcnmP)#3giMVme3qo4gm%xRt5l;h#bZM literal 0 HcmV?d00001 diff --git a/python/pyarmnn/test/testdata/shared/tflite_parser/input_lite.npy b/python/pyarmnn/test/testdata/shared/tflite_parser/input_lite.npy new file mode 100644 index 0000000000000000000000000000000000000000..53174683ff227b5f90f3355d597cacb1b3305c37 GIT binary patch literal 912 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1JQ);NLqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItoS>5NxQaqfo0rGT?e)zvbYD#Q|^1I9P8STpGT6PJ;S@=ZBB4W;~Pn zSDtIfVur`d4sYIk($PCo#^3U1wosRw%-!z_#;szDl$3sSnmmc!`E~cAr0GD>jKQ?^}pqX@c0+*-Jhv@E5sw zA22`r<79Z9BLBva4ZBNtS2oMsWbyQyUpS$&YV$<#Z7Fw){a(~A4%~BYW#K`_X<_ru zpYmMWS1oP0_1N5n`djb*`{{d5D3Vp>jm+*%Y)2i}F6tL!=)C=EKI_u+72hRIBHSje zP+=E)7hS~?u>GXMLAUjHmVMtAW&F1LRfJsrVqK=}4x{}#(VaUh_&*(cx@395CjMs^ zl49>I*|mP<$HNWRgQp8#Rd~RyQT4La^WqHedpj3v-3YvQQbqTf##LY6W7FLG=Gc6V zSz3{@{X$vV1KFNMwG2Ne-H4iPmQ~7Y(4qMAwe4(Sm#wc$O@f8izbf(-H%YHK<#KIe z(>>97Rm!ub6}P!g-W^dBu=Q_GlyZH>wity9o#L7I_g5?0Jy1y4m7lt?>hgs|x8Bs% zX0ENdlM2jCS&oRWW0&b`ocq1+Z{55LUy~ecijHMY*L7ArbS{|dnu5{u8%B3U?%G8w zMfBOue(s;7ZE|nZt`_m#xAeCKq@JFbop9xj)dO?JBeTM;a3#L@?yJ4mTJA|=lF^k7 zui`eRMyFaZ-52}5PeJ|L`X?42(ymxJE`EG<&SN3}pnvc8$X%_pf3bO!XxF;d4PW-V zX2?}UT-=fnmLbR&R{ufiW8yWBx&Q9E{LU_pU2!BdENyN|rpJ>>Q<_B`bq{N-XE3d4 zzw?qsH=_QQ?5&bo*E7YTCeAZA*6euL@O1LT2lskc+53N(n^Xs?IGLCc3cFfE1*`ZZ1(MwWVMB4f{ Xr#jp0W0%9_jN7Cgc8AKf#Owk9!GWjy literal 0 HcmV?d00001 diff --git a/python/pyarmnn/tox.ini b/python/pyarmnn/tox.ini new file mode 100644 index 0000000..ca1d12b --- /dev/null +++ b/python/pyarmnn/tox.ini @@ -0,0 +1,60 @@ +[tox] +skip_missing_interpreters=true +envlist = + py35 + py36 + py37 + +[testenv] +deps = pytest==5.2.0 + pytest-cov==2.8.1 + attrs==19.3.0 + setuptools==41.6.0 + numpy==1.17.2 + pillow==6.1.0 + +recreate = True +whitelist_externals = /bin/sh +commands = + python -m pytest test/ -v {posargs} --junit-xml=test_report_junit-{envname}.xml --cov=pyarmnn --cov-report xml:coverage-{envname}.xml + +[testenv:devenv] +envdir = env +basepython = python3.6 +usedevelop = True +deps = {[testenv]deps} + tox +skip_install = True +commands = python -c "import sys; print('Dev environment created: ' + sys.executable)" + +[testenv:gen] +basepython = python3.6 +skip_install = True +usedevelop = True +passenv = + ARMNN_LIB + ARMNN_INCLUDE +commands = + python setup.py clean --all + python ./swig_generate.py + python setup.py build_ext --inplace + +[testenv:doc] +basepython = python3.6 +deps = pdoc3==0.6.3 +passenv = + PYARMNN_DEV_VER +commands = + python ./scripts/generate_docs.py --html --output-dir docs pyarmnn --force --template-dir=./docs_conf + +[testenv:pylint] +basepython = python3.6 +deps = pylint==2.3.1 + numpy==1.17.2 +recreate = False +skip_install = True +usedevelop = True +setenv = + PYTHONPATH = src +commands = + sh -c "pylint --rcfile=pylintconfig src --output-format=parseable --reports=no > pylint_results.txt || true" -- 2.7.4