blob: 54dd70f69a3ddabbeab14387a90e0ef2229ac07e [file] [log] [blame]
Tim Hall3b1578e2023-01-13 17:57:25 +00001# SPDX-FileCopyrightText: Copyright 2020-2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
Diqing Zhong94457b12020-12-09 15:22:40 +01002#
3# SPDX-License-Identifier: Apache-2.0
4#
5# Licensed under the Apache License, Version 2.0 (the License); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an AS IS BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17# Description:
Patrik Gustavsson8f1f9aa2021-06-28 07:41:58 +020018# Unit tests for tflite_graph_optimiser
Diqing Zhong94457b12020-12-09 15:22:40 +010019import numpy as np
Louis Verhaardebf4af62021-01-27 15:57:57 +010020import pytest
Diqing Zhong94457b12020-12-09 15:22:40 +010021
Louis Verhaardae2d5532020-12-11 17:19:54 +010022from ethosu.vela.data_type import DataType
Patrik Gustavsson8f1f9aa2021-06-28 07:41:58 +020023from ethosu.vela.graph_optimiser import optimise_graph
Patrik Gustavsson8f1f9aa2021-06-28 07:41:58 +020024from ethosu.vela.nn_graph import NetworkType
Diqing Zhong94457b12020-12-09 15:22:40 +010025from ethosu.vela.operation import Op
Ayaan Masood25f48dd2022-06-29 18:16:04 +010026from ethosu.vela.operation import Operation
Louis Verhaardae2d5532020-12-11 17:19:54 +010027from ethosu.vela.operation import Padding
Patrik Gustavsson3a269202021-01-21 08:28:55 +010028from ethosu.vela.rewrite_graph import verify_graph_health
Diqing Zhong94457b12020-12-09 15:22:40 +010029from ethosu.vela.tensor import create_const_tensor
patrik.gustavssoneeb85152020-12-21 17:10:40 +000030from ethosu.vela.tensor import Shape4D
Diqing Zhong94457b12020-12-09 15:22:40 +010031from ethosu.vela.tensor import Tensor
32from ethosu.vela.test import testutil
Patrik Gustavsson8f1f9aa2021-06-28 07:41:58 +020033from ethosu.vela.tflite_graph_optimiser import calc_explicit_padding
34from ethosu.vela.tflite_graph_optimiser import convert_batched_fc_shape
Ayaan Masood25f48dd2022-06-29 18:16:04 +010035from ethosu.vela.tflite_graph_optimiser import optimise_quantize
Patrik Gustavsson8f1f9aa2021-06-28 07:41:58 +020036from ethosu.vela.tflite_graph_optimiser import replace_pad_by_hw_pad
37from ethosu.vela.tflite_graph_optimiser import rewrite_fully_connected_input
Diqing Zhong94457b12020-12-09 15:22:40 +010038
39
40def test_convert_batched_fc():
41 """Tests shape conversion of batched fully connected"""
Patrik Gustavsson3a269202021-01-21 08:28:55 +010042 ifm_shape = [4, 8]
Tim Hall3b1578e2023-01-13 17:57:25 +000043 ifm = create_const_tensor("test_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
Patrik Gustavsson3a269202021-01-21 08:28:55 +010044 w_shape = [8, 4]
Tim Hall3b1578e2023-01-13 17:57:25 +000045 weights = create_const_tensor("weight_in", w_shape, DataType.uint8, np.zeros(w_shape))
Diqing Zhong94457b12020-12-09 15:22:40 +010046 ofm = Tensor(ifm.shape, np.uint8, "test_out")
47 op = testutil.create_op(Op.FullyConnected, [ifm, weights], ofm)
Patrik Gustavsson2349d422020-12-01 16:02:29 +010048
Diqing Zhong94457b12020-12-09 15:22:40 +010049 ifm.consumer_list.append(op)
50
51 prev_op = op.clone()
Patrik Gustavsson3a269202021-01-21 08:28:55 +010052 prev_op.ifm_shapes = op.ifm_shapes.copy()
53 prev_op.ofm_shapes = op.ofm_shapes.copy()
Patrik Gustavsson2349d422020-12-01 16:02:29 +010054
Patrik Gustavsson2c2522d2021-01-29 11:51:31 +010055 rewrite_fully_connected_input(op, None, None)
Diqing Zhong94457b12020-12-09 15:22:40 +010056 conv_op = convert_batched_fc_shape(op, None, None)
Diqing Zhong94457b12020-12-09 15:22:40 +010057 assert conv_op.ifm == prev_op.ifm
58 assert conv_op.ofm == prev_op.ofm
Patrik Gustavsson3a269202021-01-21 08:28:55 +010059 assert op.ifm_shapes[0] == Shape4D([1, 2, 2, 8])
60 assert op.ofm_shapes[0] == Shape4D([1, 2, 2, 8])
Diqing Zhong94457b12020-12-09 15:22:40 +010061 assert conv_op.type == Op.FullyConnected
62 assert len(conv_op.ifm.shape) == 2
Patrik Gustavsson3a269202021-01-21 08:28:55 +010063 assert len(conv_op.ofm.shape) == 2
64 assert conv_op.ifm.shape == conv_op.ofm.shape
65
66 ifm.shape = [1, 8]
67 weights.shape = [8, 1]
68 ofm.shape = [1, 8]
69 op = testutil.create_op(Op.FullyConnected, [ifm, weights], ofm)
70 ifm.consumer_list.append(op)
71
72 prev_op = op.clone()
73 prev_op.ifm_shapes = op.ifm_shapes.copy()
74 prev_op.ofm_shapes = op.ofm_shapes.copy()
75
Patrik Gustavsson2c2522d2021-01-29 11:51:31 +010076 rewrite_fully_connected_input(op, None, None)
Patrik Gustavsson3a269202021-01-21 08:28:55 +010077 conv_op = convert_batched_fc_shape(op, None, None)
78
79 assert conv_op.ifm == prev_op.ifm
80 assert conv_op.ofm == prev_op.ofm
81 assert op.ifm_shapes[0] == prev_op.ifm_shapes[0]
82 assert op.ofm_shapes[0] == prev_op.ofm_shapes[0]
83 assert conv_op.type == Op.FullyConnected
84 assert len(conv_op.ifm.shape) == 2
85 assert len(conv_op.ofm.shape) == 2
Diqing Zhong94457b12020-12-09 15:22:40 +010086 assert conv_op.ifm.shape == conv_op.ofm.shape
Louis Verhaardae2d5532020-12-11 17:19:54 +010087
88
Louis Verhaardebf4af62021-01-27 15:57:57 +010089explicit_padding_test_data = [
90 # Kernel size 2
91 [(17, 1, 2, 1, 1), (1, 1)],
92 [(18, 1, 2, 0, 1), (0, 1)],
93 [(18, 1, 2, 1, 0), (1, 0)],
94 # Kernel size 3
95 [(18, 2, 3, 1, 1), (1, 0)],
96 [(25, 2, 3, 1, 1), (1, 1)],
97 # Kernel size 4
98 [(18, 1, 4, 1, 2), (1, 2)],
99 [(18, 1, 4, 2, 1), (2, 1)],
100 [(19, 1, 4, 2, 2), (2, 2)],
101 # Kernel size 5
102 [(19, 1, 5, 1, 2), (1, 2)],
103 [(19, 1, 5, 0, 2), (0, 2)],
104 [(19, 1, 5, 1, 0), (1, 0)],
105 # Kernel size 21
106 [(41, 2, 21, 8, 10), (8, 10)],
107 [(41, 3, 21, 10, 10), (10, 9)],
108 [(42, 3, 21, 10, 10), (10, 8)],
109 [(42, 3, 21, 9, 10), (9, 9)],
110 [(41, 3, 21, 10, 6), (10, 6)],
111]
112
113
114@pytest.mark.parametrize("test_input, expected_result", explicit_padding_test_data)
115def test_calc_explicit_padding(test_input, expected_result):
116 input_size, stride, filter_size, explicit_pad_before, explicit_pad_after = test_input
117 before, after = calc_explicit_padding(input_size, stride, filter_size, explicit_pad_before, explicit_pad_after)
118 assert (before, after) == expected_result
119
120
Louis Verhaardc822d622021-03-11 14:59:06 +0100121def create_pad_and_conv2d(
122 in_shape,
123 out_shape,
124 padding,
125 in_dtype=DataType.int8,
126 out_dtype=DataType.int8,
127 pad_dtype=DataType.int32,
128 pad_setting=Padding.VALID,
129 kernel_size=3,
130):
131 """Creates Pad operator followed by a conv2d operator"""
132 qp = testutil.default_quant_params()
133 in0 = Tensor(in_shape, in_dtype, "in")
134 in0.quantization = qp
Tim Hall3b1578e2023-01-13 17:57:25 +0000135 shape = [] if padding == [] else list(np.shape(padding))
136 pad_tensor = create_const_tensor(name="pad", shape=shape, values=padding, dtype=pad_dtype)
Louis Verhaardc822d622021-03-11 14:59:06 +0100137 out = Tensor(out_shape, out_dtype, "out")
138 out.quantization = qp.clone()
139 op = testutil.create_op(Op.Pad, [in0, pad_tensor], out)
140 op.run_on_npu = True
141 conv_out_tens = Tensor(in_shape, in_dtype, "output")
142 conv_out_tens.quantization = qp.clone()
143 weight_tens = Tensor([kernel_size, kernel_size, in_shape[-1], out_shape[-1]], in_dtype, "weights")
James Peet7519d502021-07-19 16:47:58 +0100144 weight_tens.values = np.zeros(weight_tens.shape, in_dtype.as_numpy_type())
Louis Verhaardc822d622021-03-11 14:59:06 +0100145 weight_tens.quantization = qp.clone()
146 bias_tens = Tensor(out_shape, pad_dtype, "biases")
147 attrs = {"padding": pad_setting, "stride_w": 2, "stride_h": 2, "dilation_w_factor": 1, "dilation_h_factor": 1}
148 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
149 conv2d_op = testutil.create_op(Op.Conv2DBias, [out, weight_tens, bias_tens], conv_out_tens, attrs)
150 conv2d_op.add_input_tensor(out)
151 conv2d_op.run_on_npu = True
152 return op, conv2d_op
153
154
155def test_pad_followed_by_conv_is_removed():
Louis Verhaardae2d5532020-12-11 17:19:54 +0100156 """
157 Tests that the PAD operator is bypassed when followed by a convolution operator,
158 and that the padding of the convolution operation is correctly updated
159 """
Louis Verhaardc822d622021-03-11 14:59:06 +0100160 pad_op, conv2d_op = create_pad_and_conv2d(
161 in_shape=[1, 76, 75, 64], out_shape=[1, 76, 75, 64], padding=[[0, 0], [2, 1], [1, 1], [0, 0]], kernel_size=4
162 )
163 nng = testutil.create_graph([pad_op, conv2d_op])
Louis Verhaardae2d5532020-12-11 17:19:54 +0100164 arch = testutil.create_arch()
165
Louis Verhaardc822d622021-03-11 14:59:06 +0100166 replace_pad_by_hw_pad(conv2d_op, nng, arch)
Louis Verhaardae2d5532020-12-11 17:19:54 +0100167
Louis Verhaardc822d622021-03-11 14:59:06 +0100168 op = nng.subgraphs[0].output_tensors[0].ops[0]
169 assert op.type == Op.Conv2DBias
Louis Verhaardae2d5532020-12-11 17:19:54 +0100170 assert op.attrs["padding"] == Padding.EXPLICIT
171 assert op.attrs["explicit_padding"] == (2, 1, 1, 1)
172 assert op.ifm.shape == [1, 76, 75, 64]
173 assert pad_op not in op.ifm.ops
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100174
175
Louis Verhaardc822d622021-03-11 14:59:06 +0100176leading_pad_test_data = [
177 (2, 2, 11, True),
178 (1, 2, 11, False),
179 (2, 1, 11, False),
180 (5, 2, 11, True),
181]
182
183
184@pytest.mark.parametrize("top, left, kernel_size, expect_pad_removed", leading_pad_test_data)
185def test_leading_pad_size(top, left, kernel_size, expect_pad_removed):
186 # Tests PAD operator with big kernel size; top and left pad must be multiple of stride
187 out_shape = [1, 11 + left, 11 + top, 1]
188 padding = [[0, 0], [top, 0], [left, 0], [0, 0]]
189 pad_op, conv2d_op = create_pad_and_conv2d(
190 in_shape=[1, 11, 11, 1], out_shape=out_shape, padding=padding, kernel_size=kernel_size
191 )
192 nng = testutil.create_graph([pad_op, conv2d_op])
193 arch = testutil.create_arch()
194 replace_pad_by_hw_pad(conv2d_op, nng, arch)
195 op = nng.subgraphs[0].output_tensors[0].ops[0]
196 if expect_pad_removed:
197 assert op.attrs["padding"] == Padding.EXPLICIT
198 assert "explicit_padding" in op.attrs
199 assert op.ifm.shape == op.ofm.shape
200 assert pad_op not in op.ifm.ops
201 else:
202 assert pad_op in op.ifm.ops
203 assert op.attrs["padding"] == Padding.VALID
204 assert "explicit_padding" not in op.attrs
205
206
Louis Verhaard1a92f782021-02-09 16:08:26 +0100207def test_optimise_pad_followed_by_avg_pool():
208 """
209 Tests that the PAD operator is bypassed when followed by a average pool operator,
210 and that the average pool is converted to a depthwise
211 """
212 # Create Pad operation followed by AvgPool
213 quant = testutil.default_quant_params()
214 in_tens = Tensor([1, 76, 75, 64], DataType.uint8, "input")
215 in_tens.quantization = quant
Louis Verhaardc822d622021-03-11 14:59:06 +0100216 # Test with 3x2 input tensor
217 pad_input = create_const_tensor("pad_input", [3, 2], DataType.int32, [[2, 2], [1, 1], [0, 0]])
Louis Verhaard1a92f782021-02-09 16:08:26 +0100218 temp_tens = Tensor([1, 79, 77, 64], DataType.uint8, "pad_out")
219 temp_tens.quantization = quant.clone()
220 out_tens = Tensor([1, 76, 75, 64], DataType.uint8, "output")
221 out_tens.quantization = quant.clone()
222
223 pad_op = testutil.create_op(Op.Pad, [in_tens, pad_input], temp_tens)
224 attrs = {
225 "padding": Padding.VALID,
226 "ksize": [1, 5, 3, 1],
227 "stride_w": 2,
228 "stride_h": 2,
229 "dilation_w_factor": 1,
230 "dilation_h_factor": 1,
231 }
232 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
233 pad_op.run_on_npu = True
234 conv2d_op = testutil.create_op(Op.AvgPool, [temp_tens], out_tens, attrs)
235 conv2d_op.run_on_npu = True
Louis Verhaardc822d622021-03-11 14:59:06 +0100236 nng = testutil.create_graph([pad_op, conv2d_op])
Louis Verhaard1a92f782021-02-09 16:08:26 +0100237 arch = testutil.create_arch()
238
Louis Verhaardc822d622021-03-11 14:59:06 +0100239 replace_pad_by_hw_pad(conv2d_op, nng, arch)
Louis Verhaard1a92f782021-02-09 16:08:26 +0100240
Louis Verhaardc822d622021-03-11 14:59:06 +0100241 op = nng.subgraphs[0].output_tensors[0].ops[0]
Louis Verhaard1a92f782021-02-09 16:08:26 +0100242 assert op.type == Op.DepthwiseConv2DBias
243 assert op.attrs["padding"] == Padding.EXPLICIT
Louis Verhaardc822d622021-03-11 14:59:06 +0100244 assert op.attrs["explicit_padding"] == (2, 1, 2, 1)
Louis Verhaard1a92f782021-02-09 16:08:26 +0100245 assert op.ifm.shape == [1, 76, 75, 64]
246 assert pad_op not in op.ifm.ops
247 # Check that bias and weight tensors have been added
248 assert op.bias.shape == [64]
Louis Verhaard1a92f782021-02-09 16:08:26 +0100249 assert op.weights.shape == [5, 3, 1, 64]
250
251
Louis Verhaardc822d622021-03-11 14:59:06 +0100252pad_avg_pool_test_data = [
253 ((3, 3), (1, 1, 1, 1), True),
254 ((3, 3), (2, 1, 1, 1), False),
255 ((3, 3), (1, 2, 1, 1), False),
256 ((3, 3), (1, 1, 2, 1), False),
257 ((3, 3), (1, 1, 1, 2), False),
258 ((2, 4), (1, 2, 1, 2), True),
259 ((5, 3), (2, 1, 2, 1), True),
260 ((5, 3), (0, 1, 2, 1), True),
261 ((5, 3), (2, 0, 2, 1), True),
262 ((5, 3), (2, 1, 0, 1), True),
263 ((5, 3), (2, 1, 0, 1), True),
264 ((4, 4), (2, 2, 2, 2), True),
265 ((4, 4), (1, 2, 2, 2), False),
266 ((4, 4), (2, 1, 2, 2), False),
267 ((4, 4), (2, 2, 1, 2), False),
268 ((4, 4), (2, 2, 2, 1), False),
269]
270
271
272@pytest.mark.parametrize("k_size, padding, expect_pad_removed", pad_avg_pool_test_data)
273def test_pad_followed_by_avg_pool(k_size, padding, expect_pad_removed):
274 # Tests PAD followed by AvgPool
275 k_w, k_h = k_size
276 top, left, bottom, right = padding
277 pad_values = [[0, 0], [top, bottom], [left, right], [0, 0]]
278 dtype = DataType.int8
279 qp = testutil.default_quant_params()
280 in_shape = [1, 15, 17, 8]
281 out_shape = [1, in_shape[1] + top + bottom, in_shape[2] + left + right, in_shape[3]]
282 in0 = Tensor(in_shape, dtype, "in")
283 in0.quantization = qp
284 pad_tensor = create_const_tensor(
285 name="pad", shape=list(np.shape(pad_values)), values=pad_values, dtype=DataType.int32
286 )
287 out = Tensor(out_shape, dtype, "out")
288 out.quantization = qp.clone()
289 pad_op = testutil.create_op(Op.Pad, [in0, pad_tensor], out)
290 pool_out_tens = Tensor(in_shape, dtype, "output")
291 pool_out_tens.quantization = qp.clone()
292 attrs = {
293 "padding": Padding.VALID,
294 "ksize": [1, k_w, k_h, 1],
295 "stride_w": 1,
296 "stride_h": 1,
297 "dilation_w_factor": 1,
298 "dilation_h_factor": 1,
299 }
300 pool_op = testutil.create_op(Op.AvgPool, [out], pool_out_tens, attrs)
Louis Verhaardc822d622021-03-11 14:59:06 +0100301 pad_op.run_on_npu = True
302 pool_op.run_on_npu = True
303 nng = testutil.create_graph([pad_op, pool_op])
304 arch = testutil.create_arch()
Patrik Gustavsson8f1f9aa2021-06-28 07:41:58 +0200305 nng = optimise_graph(nng, arch, NetworkType.TFLite)
Louis Verhaardc822d622021-03-11 14:59:06 +0100306 sg = nng.subgraphs[0]
307 all_ops = sg.get_all_ops()
308 print("all_ops: ", all_ops)
309 # Pad should not be in the graph anymore, it should either have been removed or rewritten
310 assert not any(op.type == Op.Pad for op in all_ops)
311 op = nng.subgraphs[0].output_tensors[0].ops[0]
312 if expect_pad_removed:
313 # Expect rewrite to depthwise, PAD is removed
314 assert op.type == Op.DepthwiseConv2DBias
315 assert op.attrs["padding"] == Padding.EXPLICIT
316 assert any(pad > 0 for pad in op.attrs["explicit_padding"])
317 assert op.ifm.shape == op.ofm.shape
318 # Check that bias and weight tensors have been added
319 assert len(op.bias.shape) > 0
320 assert op.weights.shape is not None
321 else:
322 # Pad should have been rewritten to a number of average pool operations
323 assert all(op.type in (Op.AvgPool, Op.Const) for op in all_ops)
324 assert pool_op.type == Op.AvgPool
325 assert pool_op.attrs["padding"] == Padding.VALID
326
327
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200328def test_remove_reshape():
329 """
330 Test that the expected reshape are removed in graph_optimisation
331 """
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200332
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200333 # Create tensors and operators Test1
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200334 quant = testutil.default_quant_params()
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200335
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200336 # create reshape1 op
337 ifm_shape = [64, 16]
338 reshape1_ofm_shape = [1, 4, 16, 16]
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200339 reshape1_ifm = create_const_tensor("reshape1_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200340 reshape1_ifm.quantization = quant
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200341 reshape1_ofm = create_const_tensor("reshape1_out", reshape1_ofm_shape, DataType.uint8, np.zeros(reshape1_ofm_shape))
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200342 reshape1_ofm.quantization = quant
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200343 shape_tens = create_const_tensor("reshape1_shape", [1], DataType.int32, reshape1_ofm_shape)
344 reshape1_op = testutil.create_op(Op.Reshape, [reshape1_ifm, shape_tens], reshape1_ofm, set_ifm_ofm_shapes=False)
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200345 reshape1_op.attrs["new_shape"] = reshape1_ofm_shape
346 reshape1_op.run_on_npu = True
347
348 # create conv op
349 conv_ofm = Tensor([1, 8, 8, 16], DataType.uint8, "output")
350 conv_ofm.quantization = quant.clone()
351 weight_tens = Tensor([1, 1, 16, 16], DataType.uint8, "weights")
352 weight_tens.values = np.zeros(weight_tens.shape, np.uint8)
353 weight_tens.quantization = quant.clone()
354 bias_tens = Tensor([16], DataType.int32, "biases")
355
356 attrs = {"padding": Padding.SAME, "stride_w": 1, "stride_h": 1, "dilation_w_factor": 1, "dilation_h_factor": 1}
357 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
358
359 conv2d_op = testutil.create_op(
360 Op.Conv2D, [reshape1_ofm, weight_tens, bias_tens], conv_ofm, attrs=attrs, set_ifm_ofm_shapes=False
361 )
362 conv2d_op.run_on_npu = True
363
364 # create reshape2 op
365 ofm_shape = [8, 8, 16]
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200366 reshape2_ofm = create_const_tensor("reshape2_out", ofm_shape, DataType.uint8, np.zeros(ofm_shape))
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200367 reshape2_ofm.quantization = quant
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200368 shape_tens = create_const_tensor("reshape2_shape", [1], DataType.int32, ofm_shape)
369 reshape2_op = testutil.create_op(Op.Reshape, [conv_ofm, shape_tens], reshape2_ofm, set_ifm_ofm_shapes=False)
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200370 reshape2_op.attrs["new_shape"] = ofm_shape
371 reshape2_op.run_on_npu = True
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100372
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100373 # Test1 no Reshape op is expected to remain in the NPU subgrapgh
374 # but first one will be put on CPU
Patrik Gustavsson138d47f2021-02-08 10:13:48 +0100375 # Network is Reshape-Conv-Reshape
376 # Result is Conv
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200377 nng = testutil.create_graph([reshape1_op, conv2d_op, reshape2_op])
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100378 arch = testutil.create_arch()
379 assert verify_graph_health(nng)
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200380 nng = optimise_graph(nng, arch, NetworkType.TFLite, True)
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100381 assert verify_graph_health(nng)
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100382
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200383 # Create tensors and operator Test2
384 # create reshape op
385 reshape_ifm = create_const_tensor("reshape_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
386 reshape_ifm.quantization = quant
387 reshape_ofm = create_const_tensor("reshape1_out", reshape1_ofm_shape, DataType.uint8, np.zeros(reshape1_ofm_shape))
388 reshape_ofm.quantization = quant
389 shape_tens = create_const_tensor("reshape1_shape", [1], DataType.int32, reshape1_ofm_shape)
390 reshape_op = testutil.create_op(Op.Reshape, [reshape_ifm, shape_tens], reshape_ofm, set_ifm_ofm_shapes=False)
391 reshape_op.attrs["new_shape"] = reshape1_ofm_shape
392 reshape_op.run_on_npu = True
393
394 # Test2 Reshape ifm/ofm is sg input/output.
395 # Reshape op is expected to be replaced by a AvgPool 'NOP'.
396 #
397 # Network is Reshape
398 # expected is AvgPool
399 nng = testutil.create_graph([reshape_op])
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100400 assert verify_graph_health(nng)
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200401 nng = optimise_graph(nng, arch, NetworkType.TFLite, True)
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100402 assert verify_graph_health(nng)
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200403
404
405def test_remove_squeeze():
406 """
407 Tests that the expected squeeze are removed in graph_optimisation
408 """
409
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200410 # Create tensors and operators Test1
411 quant = testutil.default_quant_params()
412
413 # create conv op
414 ifm_shape = [1, 1, 1, 1024]
415 conv_ifm = create_const_tensor("conv_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
416 conv_ifm.quantization = quant
417 conv_ofm = Tensor([1, 1, 1, 1001], DataType.uint8, "output")
418 conv_ofm.quantization = quant.clone()
419 weight_tens = Tensor([1, 1, 1024, 1001], DataType.uint8, "weights")
420 weight_tens.values = np.zeros(weight_tens.shape, np.uint8)
421 weight_tens.quantization = quant.clone()
422 bias_tens = Tensor([1001], DataType.int32, "biases")
423
424 attrs = {"padding": Padding.SAME, "stride_w": 1, "stride_h": 1, "dilation_w_factor": 1, "dilation_h_factor": 1}
425 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
426
427 conv2d_op = testutil.create_op(
428 Op.Conv2D, [conv_ifm, weight_tens, bias_tens], conv_ofm, attrs=attrs, set_ifm_ofm_shapes=False
429 )
430 conv2d_op.run_on_npu = True
431
432 # create squeeze op
433 ofm_shape = [1, 1001]
434 squeeze_ofm = create_const_tensor("squeeze_out", ofm_shape, DataType.uint8, np.zeros(ofm_shape))
435 squeeze_ofm.quantization = quant.clone()
436 squeeze_op = testutil.create_op(Op.Squeeze, [conv_ofm], squeeze_ofm, set_ifm_ofm_shapes=False)
437 squeeze_op.attrs["squeeze_dims"] = [1, 2]
438 squeeze_op.run_on_npu = True
439
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200440 # Test1 no Squeeze op is expected to remain in the NPU subgrapgh
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200441 #
442 # Network is Conv-Squeeze
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200443 # Result is Conv
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200444 nng = testutil.create_graph([conv2d_op, squeeze_op])
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200445 arch = testutil.create_arch()
446 assert verify_graph_health(nng)
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200447 nng = optimise_graph(nng, arch, NetworkType.TFLite, True)
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200448 assert verify_graph_health(nng)
449
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200450 # Create tensors and operator Test2
451 # create squeeze op
452 ifm_shape = [1, 1, 1, 1001]
453 squeeze_ifm = create_const_tensor("squeeze_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
454 squeeze_ifm.quantization = quant
455 squeeze_ofm = create_const_tensor("squeeze_out", ofm_shape, DataType.uint8, np.zeros(ofm_shape))
456 squeeze_ofm.quantization = quant.clone()
457 squeeze_op = testutil.create_op(Op.Squeeze, [squeeze_ifm], squeeze_ofm, set_ifm_ofm_shapes=False)
458 squeeze_op.attrs["squeeze_dims"] = [1, 2]
459 squeeze_op.run_on_npu = True
460
461 # Test2 Squeeze ifm/ofm is sg input/output.
462 # Squeeze op is expected to be replaced by a AvgPool 'NOP'.
463 #
464 # Network is Squeeze
465 # expected is AvgPool
466 nng = testutil.create_graph([squeeze_op])
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200467 assert verify_graph_health(nng)
Jonas Ohlsson0957e3e2021-09-01 15:57:21 +0200468 nng = optimise_graph(nng, arch, NetworkType.TFLite, True)
469 assert verify_graph_health(nng)
470
471
472def test_remove_expand_dims():
473 """
474 Tests that the expected ExpandDims are removed in graph_optimisation
475 """
476
477 # Create tensors and operators Test1
478 quant = testutil.default_quant_params()
479
480 # create ExpandDims op
481 ifm_shape = [4, 16, 16]
482 ofm_shape = [1, 4, 16, 16]
483 expand_dims_ifm = create_const_tensor("expand_dims_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
484 expand_dims_ifm.quantization = quant
485 expand_dims_ofm = create_const_tensor("expand_dims_out", ofm_shape, DataType.uint8, np.zeros(ofm_shape))
486 expand_dims_ofm.quantization = quant.clone()
487 dim_tens = create_const_tensor("dim_tens", [], DataType.uint8, 1)
488 expand_dims_op = testutil.create_op(
489 Op.ExpandDims, [expand_dims_ifm, dim_tens], expand_dims_ofm, set_ifm_ofm_shapes=False
490 )
491 expand_dims_op.run_on_npu = True
492
493 # create conv op
494 conv_ofm = Tensor([1, 8, 8, 16], DataType.uint8, "output")
495 conv_ofm.quantization = quant.clone()
496 weight_tens = Tensor([1, 1, 16, 16], DataType.uint8, "weights")
497 weight_tens.values = np.zeros(weight_tens.shape, np.uint8)
498 weight_tens.quantization = quant.clone()
499 bias_tens = Tensor([16], DataType.int32, "biases")
500
501 attrs = {"padding": Padding.SAME, "stride_w": 1, "stride_h": 1, "dilation_w_factor": 1, "dilation_h_factor": 1}
502 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
503
504 conv2d_op = testutil.create_op(
505 Op.Conv2D, [expand_dims_ofm, weight_tens, bias_tens], conv_ofm, attrs=attrs, set_ifm_ofm_shapes=False
506 )
507 conv2d_op.run_on_npu = True
508
509 # Test1 no ExpandDims op is expected to remain in the NPU subgrapgh
510 #
511 # Network is ExpandDims-Conv
512 # Result is Conv
513 nng = testutil.create_graph([expand_dims_op, conv2d_op])
514 arch = testutil.create_arch()
515 assert verify_graph_health(nng)
516 nng = optimise_graph(nng, arch, NetworkType.TFLite, True)
517 assert verify_graph_health(nng)
518
519 # create ExpandDims op
520 expand_dims_ifm = create_const_tensor("expand_dims_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
521 expand_dims_ifm.quantization = quant
522 expand_dims_ofm = create_const_tensor("expand_dims_out", ofm_shape, DataType.uint8, np.zeros(ofm_shape))
523 expand_dims_ofm.quantization = quant.clone()
524 dim_tens = create_const_tensor("dim_tens", [], DataType.uint8, 1)
525 expand_dims_op = testutil.create_op(
526 Op.ExpandDims, [expand_dims_ifm, dim_tens], expand_dims_ofm, set_ifm_ofm_shapes=False
527 )
528 expand_dims_op.run_on_npu = True
529
530 # Test2 ExpandDims ifm/ofm is sg input/output.
531 # ExpandDims op is expected to be replaced by a AvgPool 'NOP'.
532 #
533 # Network is ExpandDims
534 # expected is AvgPool
535 nng = testutil.create_graph([expand_dims_op])
536 assert verify_graph_health(nng)
537 nng = optimise_graph(nng, arch, NetworkType.TFLite, True)
Jonas Ohlssonfbfd96e2021-08-25 11:38:03 +0200538 assert verify_graph_health(nng)
Ayaan Masood25f48dd2022-06-29 18:16:04 +0100539
540
541def test_quant_static_optimisations():
542
543 """
544 Tests if the quant value at vela compile time is calculated correctly
545 """
546
Tim Hall3b1578e2023-01-13 17:57:25 +0000547 quant_ifm = create_const_tensor("const_quant_ifm", values=np.array(127), shape=[], dtype=DataType.int8)
Ayaan Masood25f48dd2022-06-29 18:16:04 +0100548 quant_ifm.quantization = testutil.default_quant_params()
549 quant_ifm.quantization.scale_f32 = 0.15748031
550 quant_ifm.quantization.quant_min = -128
551 quant_ifm.quantization.quant_max = 127
552
553 quant_ofm = create_const_tensor("const_quant_ofm", values=np.array([]), shape=[], dtype=DataType.int8)
554 quant_ofm.quantization = testutil.default_quant_params()
555 quant_ofm.quantization.scale_f32 = 0.036092404
556 quant_ofm.quantization.zero_point = -128
557 quant_ofm.quantization.quant_min = -128
558 quant_ofm.quantization.quant_max = 127
559
560 # Create quant op
561
562 quant_op = testutil.create_op(Op.Quantize, [quant_ifm], quant_ofm)
563
564 quant_op.run_on_npu = True
565
566 op: Operation = optimise_quantize(quant_op, None, None)
567
568 assert op.ofm.values == 127
569
Tim Hall3b1578e2023-01-13 17:57:25 +0000570 quant_ifm = create_const_tensor("const_quant_ifm", values=np.array(127), shape=[], dtype=DataType.int8)
Ayaan Masood25f48dd2022-06-29 18:16:04 +0100571 quant_ifm.quantization = testutil.default_quant_params()
572 quant_ifm.quantization.scale_f32 = 0.15748031
573 quant_ifm.quantization.quant_min = -128
574 quant_ifm.quantization.quant_max = 127
575
576 quant_ofm = create_const_tensor("const_quant_ofm", values=np.array([]), shape=[], dtype=DataType.int8)
577 quant_ofm.quantization = testutil.default_quant_params()
578 quant_ofm.quantization.scale_f32 = 0.036092404
579 quant_ofm.quantization.zero_point = -128
580 quant_ofm.quantization.quant_min = -128
581 quant_ofm.quantization.quant_max = 127
582
583 # Create quant op
584
585 quant_op = testutil.create_op(Op.Quantize, [quant_ifm], quant_ofm)
586
587 quant_op.run_on_npu = True
588
589 op: Operation = optimise_quantize(quant_op, None, None)
590
591 assert op.ofm.values == 127
592
593
594def test_optimise_quantize_multiple_values():
595 """
596 Tests if the quant value at vela compile time is calculated correctly
597 when passing multiple values to quantize node
598 """
599
Tim Hall3b1578e2023-01-13 17:57:25 +0000600 quant_ifm = create_const_tensor("const_quant_ifm", values=np.array([127, 127]), shape=[], dtype=DataType.int8)
Ayaan Masood25f48dd2022-06-29 18:16:04 +0100601 quant_ifm.quantization = testutil.default_quant_params()
602 quant_ifm.quantization.scale_f32 = 0.15748031
603 quant_ifm.quantization.quant_min = -128
604 quant_ifm.quantization.quant_max = 127
605
606 quant_ofm = create_const_tensor("const_quant_ofm", values=np.array([]), shape=[], dtype=DataType.int8)
607 quant_ofm.quantization = testutil.default_quant_params()
608 quant_ofm.quantization.scale_f32 = 0.036092404
609 quant_ofm.quantization.zero_point = -128
610 quant_ofm.quantization.quant_min = -128
611 quant_ofm.quantization.quant_max = 127
612
613 # Create quant op
614
615 quant_op = testutil.create_op(Op.Quantize, [quant_ifm], quant_ofm)
616
617 quant_op.run_on_npu = True
618
619 op: Operation = optimise_quantize(quant_op, None, None)
620
621 assert (op.ofm.values == np.array([127, 127])).all()