Teresa Charlin | 1814733 | 2021-11-17 14:34:30 +0000 | [diff] [blame] | 1 | # Copyright © 2020 Arm Ltd and Contributors. All rights reserved. |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 2 | # SPDX-License-Identifier: MIT |
| 3 | import os |
| 4 | import stat |
Cathal Corbett | 9f184c4 | 2021-11-10 12:14:39 +0000 | [diff] [blame] | 5 | import numpy as np |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 6 | |
| 7 | import pytest |
| 8 | import pyarmnn as ann |
| 9 | |
alexander | 7301078 | 2021-10-18 19:17:24 +0100 | [diff] [blame] | 10 | |
Jan Eilers | 841aca1 | 2020-08-12 14:59:06 +0100 | [diff] [blame] | 11 | def test_optimizer_options_default_values(): |
| 12 | opt = ann.OptimizerOptions() |
| 13 | assert opt.m_ReduceFp32ToFp16 == False |
| 14 | assert opt.m_Debug == False |
| 15 | assert opt.m_ReduceFp32ToBf16 == False |
Narumol Prangnawarat | ea063df | 2020-08-21 10:03:49 +0100 | [diff] [blame] | 16 | assert opt.m_ImportEnabled == False |
alexander | 7301078 | 2021-10-18 19:17:24 +0100 | [diff] [blame] | 17 | assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly |
| 18 | |
Jan Eilers | 841aca1 | 2020-08-12 14:59:06 +0100 | [diff] [blame] | 19 | |
| 20 | def test_optimizer_options_set_values1(): |
| 21 | opt = ann.OptimizerOptions(True, True) |
| 22 | assert opt.m_ReduceFp32ToFp16 == True |
| 23 | assert opt.m_Debug == True |
| 24 | assert opt.m_ReduceFp32ToBf16 == False |
Narumol Prangnawarat | ea063df | 2020-08-21 10:03:49 +0100 | [diff] [blame] | 25 | assert opt.m_ImportEnabled == False |
alexander | 7301078 | 2021-10-18 19:17:24 +0100 | [diff] [blame] | 26 | assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly |
| 27 | |
Jan Eilers | 841aca1 | 2020-08-12 14:59:06 +0100 | [diff] [blame] | 28 | |
| 29 | def test_optimizer_options_set_values2(): |
| 30 | opt = ann.OptimizerOptions(False, False, True) |
| 31 | assert opt.m_ReduceFp32ToFp16 == False |
| 32 | assert opt.m_Debug == False |
| 33 | assert opt.m_ReduceFp32ToBf16 == True |
Narumol Prangnawarat | ea063df | 2020-08-21 10:03:49 +0100 | [diff] [blame] | 34 | assert opt.m_ImportEnabled == False |
alexander | 7301078 | 2021-10-18 19:17:24 +0100 | [diff] [blame] | 35 | assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly |
| 36 | |
Narumol Prangnawarat | ea063df | 2020-08-21 10:03:49 +0100 | [diff] [blame] | 37 | |
| 38 | def test_optimizer_options_set_values3(): |
alexander | 7301078 | 2021-10-18 19:17:24 +0100 | [diff] [blame] | 39 | opt = ann.OptimizerOptions(False, False, True, ann.ShapeInferenceMethod_InferAndValidate, True) |
Narumol Prangnawarat | ea063df | 2020-08-21 10:03:49 +0100 | [diff] [blame] | 40 | assert opt.m_ReduceFp32ToFp16 == False |
| 41 | assert opt.m_Debug == False |
| 42 | assert opt.m_ReduceFp32ToBf16 == True |
| 43 | assert opt.m_ImportEnabled == True |
alexander | 7301078 | 2021-10-18 19:17:24 +0100 | [diff] [blame] | 44 | assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_InferAndValidate |
| 45 | |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 46 | |
| 47 | @pytest.fixture(scope="function") |
| 48 | def get_runtime(shared_data_folder, network_file): |
| 49 | parser= ann.ITfLiteParser() |
| 50 | preferred_backends = [ann.BackendId('CpuAcc'), ann.BackendId('CpuRef')] |
| 51 | network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, network_file)) |
| 52 | options = ann.CreationOptions() |
| 53 | runtime = ann.IRuntime(options) |
| 54 | |
| 55 | yield preferred_backends, network, runtime |
| 56 | |
| 57 | |
| 58 | @pytest.mark.parametrize("network_file", |
| 59 | [ |
| 60 | 'mock_model.tflite', |
| 61 | ], |
| 62 | ids=['mock_model']) |
| 63 | def test_optimize_executes_successfully(network_file, get_runtime): |
| 64 | preferred_backends = [ann.BackendId('CpuRef')] |
| 65 | network = get_runtime[1] |
| 66 | runtime = get_runtime[2] |
| 67 | |
| 68 | opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 69 | |
| 70 | assert len(messages) == 0, 'With only CpuRef, there should be no warnings irrelevant of architecture.' |
| 71 | assert opt_network |
| 72 | |
| 73 | |
| 74 | @pytest.mark.parametrize("network_file", |
| 75 | [ |
| 76 | 'mock_model.tflite', |
| 77 | ], |
| 78 | ids=['mock_model']) |
| 79 | def test_optimize_owned_by_python(network_file, get_runtime): |
| 80 | preferred_backends = get_runtime[0] |
| 81 | network = get_runtime[1] |
| 82 | runtime = get_runtime[2] |
| 83 | |
| 84 | opt_network, _ = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 85 | assert opt_network.thisown |
| 86 | |
| 87 | |
| 88 | @pytest.mark.aarch64 |
| 89 | @pytest.mark.parametrize("network_file", |
| 90 | [ |
| 91 | 'mock_model.tflite' |
| 92 | ], |
| 93 | ids=['mock_model']) |
| 94 | def test_optimize_executes_successfully_for_neon_backend_only(network_file, get_runtime): |
| 95 | preferred_backends = [ann.BackendId('CpuAcc')] |
| 96 | network = get_runtime[1] |
| 97 | runtime = get_runtime[2] |
| 98 | |
| 99 | opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 100 | assert 0 == len(messages) |
| 101 | assert opt_network |
| 102 | |
| 103 | |
| 104 | @pytest.mark.parametrize("network_file", |
| 105 | [ |
| 106 | 'mock_model.tflite' |
| 107 | ], |
| 108 | ids=['mock_model']) |
| 109 | def test_optimize_fails_for_invalid_backends(network_file, get_runtime): |
| 110 | invalid_backends = [ann.BackendId('Unknown')] |
| 111 | network = get_runtime[1] |
| 112 | runtime = get_runtime[2] |
| 113 | |
| 114 | with pytest.raises(RuntimeError) as err: |
| 115 | ann.Optimize(network, invalid_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 116 | |
| 117 | expected_error_message = "None of the preferred backends [Unknown ] are supported." |
| 118 | assert expected_error_message in str(err.value) |
| 119 | |
| 120 | |
| 121 | @pytest.mark.parametrize("network_file", |
| 122 | [ |
| 123 | 'mock_model.tflite' |
| 124 | ], |
| 125 | ids=['mock_model']) |
| 126 | def test_optimize_fails_for_no_backends_specified(network_file, get_runtime): |
| 127 | empty_backends = [] |
| 128 | network = get_runtime[1] |
| 129 | runtime = get_runtime[2] |
| 130 | |
| 131 | with pytest.raises(RuntimeError) as err: |
| 132 | ann.Optimize(network, empty_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 133 | |
| 134 | expected_error_message = "Invoked Optimize with no backends specified" |
| 135 | assert expected_error_message in str(err.value) |
| 136 | |
| 137 | |
| 138 | @pytest.mark.parametrize("network_file", |
| 139 | [ |
| 140 | 'mock_model.tflite' |
| 141 | ], |
| 142 | ids=['mock_model']) |
| 143 | def test_serialize_to_dot(network_file, get_runtime, tmpdir): |
| 144 | preferred_backends = get_runtime[0] |
| 145 | network = get_runtime[1] |
| 146 | runtime = get_runtime[2] |
| 147 | opt_network, _ = ann.Optimize(network, preferred_backends, |
| 148 | runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 149 | dot_file_path = os.path.join(tmpdir, 'mock_model.dot') |
| 150 | """Check that serialized file does not exist at the start, gets created after SerializeToDot and is not empty""" |
| 151 | assert not os.path.exists(dot_file_path) |
| 152 | opt_network.SerializeToDot(dot_file_path) |
| 153 | |
| 154 | assert os.path.exists(dot_file_path) |
| 155 | |
| 156 | with open(dot_file_path) as res_file: |
| 157 | expected_data = res_file.read() |
| 158 | assert len(expected_data) > 1 |
| 159 | assert '[label=< [1,28,28,1] >]' in expected_data |
| 160 | |
| 161 | |
| 162 | @pytest.mark.x86_64 |
| 163 | @pytest.mark.parametrize("network_file", |
| 164 | [ |
| 165 | 'mock_model.tflite' |
| 166 | ], |
| 167 | ids=['mock_model']) |
| 168 | def test_serialize_to_dot_mode_readonly(network_file, get_runtime, tmpdir): |
| 169 | preferred_backends = get_runtime[0] |
| 170 | network = get_runtime[1] |
| 171 | runtime = get_runtime[2] |
| 172 | opt_network, _ = ann.Optimize(network, preferred_backends, |
| 173 | runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 174 | """Create file, write to it and change mode to read-only""" |
| 175 | dot_file_path = os.path.join(tmpdir, 'mock_model.dot') |
| 176 | f = open(dot_file_path, "w+") |
| 177 | f.write("test") |
| 178 | f.close() |
| 179 | os.chmod(dot_file_path, stat.S_IREAD) |
| 180 | assert os.path.exists(dot_file_path) |
| 181 | |
| 182 | with pytest.raises(RuntimeError) as err: |
| 183 | opt_network.SerializeToDot(dot_file_path) |
| 184 | |
| 185 | expected_error_message = "Failed to open dot file" |
| 186 | assert expected_error_message in str(err.value) |
| 187 | |
| 188 | |
| 189 | @pytest.mark.parametrize("method", [ |
| 190 | 'AddActivationLayer', |
| 191 | 'AddAdditionLayer', |
| 192 | 'AddArgMinMaxLayer', |
| 193 | 'AddBatchNormalizationLayer', |
| 194 | 'AddBatchToSpaceNdLayer', |
Teresa Charlin | 87dc812 | 2021-11-22 15:34:26 +0000 | [diff] [blame] | 195 | 'AddCastLayer', |
Teresa Charlin | f7b5011 | 2021-11-18 15:24:50 +0000 | [diff] [blame] | 196 | 'AddChannelShuffleLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 197 | 'AddComparisonLayer', |
| 198 | 'AddConcatLayer', |
| 199 | 'AddConstantLayer', |
| 200 | 'AddConvolution2dLayer', |
Teresa Charlin | 1814733 | 2021-11-17 14:34:30 +0000 | [diff] [blame] | 201 | 'AddConvolution3dLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 202 | 'AddDepthToSpaceLayer', |
| 203 | 'AddDepthwiseConvolution2dLayer', |
| 204 | 'AddDequantizeLayer', |
| 205 | 'AddDetectionPostProcessLayer', |
| 206 | 'AddDivisionLayer', |
| 207 | 'AddElementwiseUnaryLayer', |
| 208 | 'AddFloorLayer', |
Jan Eilers | 841aca1 | 2020-08-12 14:59:06 +0100 | [diff] [blame] | 209 | 'AddFillLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 210 | 'AddFullyConnectedLayer', |
| 211 | 'AddGatherLayer', |
Teresa Charlin | 26ee542 | 2022-05-03 21:39:57 +0100 | [diff] [blame] | 212 | 'AddGatherNdLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 213 | 'AddInputLayer', |
| 214 | 'AddInstanceNormalizationLayer', |
Cathal Corbett | f0836e0 | 2021-11-18 18:17:38 +0000 | [diff] [blame] | 215 | 'AddLogicalBinaryLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 216 | 'AddLogSoftmaxLayer', |
| 217 | 'AddL2NormalizationLayer', |
| 218 | 'AddLstmLayer', |
| 219 | 'AddMaximumLayer', |
| 220 | 'AddMeanLayer', |
| 221 | 'AddMergeLayer', |
| 222 | 'AddMinimumLayer', |
| 223 | 'AddMultiplicationLayer', |
| 224 | 'AddNormalizationLayer', |
| 225 | 'AddOutputLayer', |
| 226 | 'AddPadLayer', |
| 227 | 'AddPermuteLayer', |
| 228 | 'AddPooling2dLayer', |
Ryan OShea | 8965500 | 2022-03-09 02:07:24 +0000 | [diff] [blame] | 229 | 'AddPooling3dLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 230 | 'AddPreluLayer', |
| 231 | 'AddQuantizeLayer', |
| 232 | 'AddQuantizedLstmLayer', |
Jan Eilers | 841aca1 | 2020-08-12 14:59:06 +0100 | [diff] [blame] | 233 | 'AddRankLayer', |
Ryan OShea | 09a0522 | 2021-11-18 16:52:41 +0000 | [diff] [blame] | 234 | 'AddReduceLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 235 | 'AddReshapeLayer', |
| 236 | 'AddResizeLayer', |
Ryan OShea | aeee9ad | 2021-11-18 17:43:55 +0000 | [diff] [blame] | 237 | 'AddShapeLayer', |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 238 | 'AddSliceLayer', |
| 239 | 'AddSoftmaxLayer', |
| 240 | 'AddSpaceToBatchNdLayer', |
| 241 | 'AddSpaceToDepthLayer', |
| 242 | 'AddSplitterLayer', |
| 243 | 'AddStackLayer', |
| 244 | 'AddStandInLayer', |
| 245 | 'AddStridedSliceLayer', |
| 246 | 'AddSubtractionLayer', |
| 247 | 'AddSwitchLayer', |
Cathal Corbett | 2b4182f | 2021-11-18 10:28:47 +0000 | [diff] [blame] | 248 | 'AddTransposeConvolution2dLayer', |
| 249 | 'AddTransposeLayer' |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 250 | ]) |
| 251 | def test_network_method_exists(method): |
| 252 | assert getattr(ann.INetwork, method, None) |
| 253 | |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 254 | def test_Convolution2d_layer_optional_none(): |
| 255 | net = ann.INetwork() |
| 256 | layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(), |
Cathal Corbett | 9f184c4 | 2021-11-10 12:14:39 +0000 | [diff] [blame] | 257 | weights=ann.ConstTensor()) |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 258 | |
| 259 | assert layer |
| 260 | |
| 261 | |
| 262 | def test_Convolution2d_layer_optional_provided(): |
| 263 | net = ann.INetwork() |
| 264 | layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(), |
Cathal Corbett | 9f184c4 | 2021-11-10 12:14:39 +0000 | [diff] [blame] | 265 | weights=ann.ConstTensor(), |
| 266 | biases=ann.ConstTensor()) |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 267 | |
| 268 | assert layer |
| 269 | |
| 270 | |
| 271 | def test_Convolution2d_layer_all_args(): |
| 272 | net = ann.INetwork() |
| 273 | layer = net.AddConvolution2dLayer(convolution2dDescriptor=ann.Convolution2dDescriptor(), |
Cathal Corbett | 9f184c4 | 2021-11-10 12:14:39 +0000 | [diff] [blame] | 274 | weights=ann.ConstTensor(), |
| 275 | biases=ann.ConstTensor(), |
| 276 | name='NAME1') |
Richard Burton | dc0c6ed | 2020-04-08 16:39:05 +0100 | [diff] [blame] | 277 | |
| 278 | assert layer |
| 279 | assert 'NAME1' == layer.GetName() |
Cathal Corbett | 9f184c4 | 2021-11-10 12:14:39 +0000 | [diff] [blame] | 280 | |
| 281 | |
| 282 | def test_add_constant_layer_to_fully_connected(): |
| 283 | |
| 284 | inputWidth = 1 |
| 285 | inputHeight = 1 |
| 286 | inputChannels = 5 |
| 287 | inputNum = 2 |
| 288 | |
| 289 | outputChannels = 3 |
| 290 | outputNum = 2 |
| 291 | |
| 292 | inputShape = ( inputNum, inputChannels, inputHeight, inputWidth ) |
| 293 | outputShape = ( outputNum, outputChannels ) |
| 294 | weightsShape = ( inputChannels, outputChannels ) |
| 295 | biasShape = ( outputChannels, ) |
| 296 | |
| 297 | input = np.array([ |
| 298 | [1.0, 2.0, 3.0, 4.0, 5.0], |
| 299 | [5.0, 4.0, 3.0, 2.0, 1.0] |
| 300 | ], dtype=np.float32) |
| 301 | |
| 302 | weights = np.array([ |
| 303 | [.5, 2., .5], |
| 304 | [.5, 2., 1.], |
| 305 | [.5, 2., 2.], |
| 306 | [.5, 2., 3.], |
| 307 | [.5, 2., 4.] |
| 308 | ], dtype=np.float32) |
| 309 | |
| 310 | biasValues = np.array([10, 20, 30], dtype=np.float32) |
| 311 | |
| 312 | expectedOutput = np.array([ |
| 313 | [0.5 + 1.0 + 1.5 + 2.0 + 2.5 + biasValues[0], |
| 314 | 2.0 + 4.0 + 6.0 + 8.0 + 10. + biasValues[1], |
| 315 | 0.5 + 2.0 + 6.0 + 12. + 20. + biasValues[2]], |
| 316 | [2.5 + 2.0 + 1.5 + 1.0 + 0.5 + biasValues[0], |
| 317 | 10.0 + 8.0 + 6.0 + 4.0 + 2. + biasValues[1], |
| 318 | 2.5 + 4.0 + 6.0 + 6. + 4. + biasValues[2]] |
| 319 | ], dtype=np.float32) |
| 320 | |
| 321 | network = ann.INetwork() |
| 322 | |
| 323 | input_info = ann.TensorInfo(ann.TensorShape(inputShape), ann.DataType_Float32, 0, 0, True) |
| 324 | input_tensor = ann.ConstTensor(input_info, input) |
| 325 | input_layer = network.AddInputLayer(0, "input") |
| 326 | |
| 327 | w_info = ann.TensorInfo(ann.TensorShape(weightsShape), ann.DataType_Float32, 0, 0, True) |
| 328 | w_tensor = ann.ConstTensor(w_info, weights) |
| 329 | w_layer = network.AddConstantLayer(w_tensor, "weights") |
| 330 | |
| 331 | b_info = ann.TensorInfo(ann.TensorShape(biasShape), ann.DataType_Float32, 0, 0, True) |
| 332 | b_tensor = ann.ConstTensor(b_info, biasValues) |
| 333 | b_layer = network.AddConstantLayer(b_tensor, "bias") |
| 334 | |
| 335 | fc_descriptor = ann.FullyConnectedDescriptor() |
| 336 | fc_descriptor.m_BiasEnabled = True |
| 337 | fc_descriptor.m_ConstantWeights = True |
| 338 | fully_connected = network.AddFullyConnectedLayer(fc_descriptor, "fc") |
| 339 | |
| 340 | output_info = ann.TensorInfo(ann.TensorShape(outputShape), ann.DataType_Float32) |
| 341 | output_tensor = ann.Tensor(output_info, np.zeros([1, 1], dtype=np.float32)) |
| 342 | output = network.AddOutputLayer(0, "output") |
| 343 | |
| 344 | input_layer.GetOutputSlot(0).Connect(fully_connected.GetInputSlot(0)) |
| 345 | w_layer.GetOutputSlot(0).Connect(fully_connected.GetInputSlot(1)) |
| 346 | b_layer.GetOutputSlot(0).Connect(fully_connected.GetInputSlot(2)) |
| 347 | fully_connected.GetOutputSlot(0).Connect(output.GetInputSlot(0)) |
| 348 | |
| 349 | input_layer.GetOutputSlot(0).SetTensorInfo(input_info) |
| 350 | w_layer.GetOutputSlot(0).SetTensorInfo(w_info) |
| 351 | b_layer.GetOutputSlot(0).SetTensorInfo(b_info) |
| 352 | fully_connected.GetOutputSlot(0).SetTensorInfo(output_info) |
| 353 | |
| 354 | preferred_backends = [ann.BackendId('CpuRef')] |
| 355 | options = ann.CreationOptions() |
| 356 | runtime = ann.IRuntime(options) |
| 357 | opt_network, messages = ann.Optimize(network, preferred_backends, runtime.GetDeviceSpec(), ann.OptimizerOptions()) |
| 358 | net_id, messages = runtime.LoadNetwork(opt_network) |
| 359 | |
| 360 | input_tensors = [(0, input_tensor)] |
| 361 | output_tensors = [(0, output_tensor)] |
| 362 | runtime.EnqueueWorkload(net_id, input_tensors, output_tensors) |
| 363 | |
| 364 | output_vectors = ann.workload_tensors_to_ndarray(output_tensors) |
| 365 | |
| 366 | assert (output_vectors==expectedOutput).all() |