From dc0c6ed9f8b993e63f492f203d7d7080ab4c835c Mon Sep 17 00:00:00 2001 From: Richard Burton Date: Wed, 8 Apr 2020 16:39:05 +0100 Subject: 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/test/test_const_tensor.py | 251 +++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 python/pyarmnn/test/test_const_tensor.py (limited to 'python/pyarmnn/test/test_const_tensor.py') diff --git a/python/pyarmnn/test/test_const_tensor.py b/python/pyarmnn/test/test_const_tensor.py new file mode 100644 index 0000000000..fa6327f19c --- /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) + -- cgit v1.2.1