blob: b01b07c357fc313a3c6f38e0872f38614c841eca [file] [log] [blame]
Diqing Zhong94457b12020-12-09 15:22:40 +01001# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved.
2#
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:
18# Unit tests for graph_optimiser
19import numpy as np
20
Louis Verhaardae2d5532020-12-11 17:19:54 +010021from ethosu.vela.data_type import DataType
Diqing Zhong94457b12020-12-09 15:22:40 +010022from ethosu.vela.graph_optimiser import convert_batched_fc_shape
Patrik Gustavsson3a269202021-01-21 08:28:55 +010023from ethosu.vela.graph_optimiser import optimise_graph_a
Louis Verhaardae2d5532020-12-11 17:19:54 +010024from ethosu.vela.graph_optimiser import optimise_pad
25from ethosu.vela.nn_graph import Graph
Diqing Zhong94457b12020-12-09 15:22:40 +010026from ethosu.vela.operation import Op
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
33
34
35def test_convert_batched_fc():
36 """Tests shape conversion of batched fully connected"""
Patrik Gustavsson3a269202021-01-21 08:28:55 +010037 ifm_shape = [4, 8]
38 ifm = create_const_tensor("test_in", ifm_shape, np.uint8, np.zeros(ifm_shape))
39 w_shape = [8, 4]
40 weights = create_const_tensor("weight_in", w_shape, np.uint8, np.zeros(w_shape))
Diqing Zhong94457b12020-12-09 15:22:40 +010041 ofm = Tensor(ifm.shape, np.uint8, "test_out")
42 op = testutil.create_op(Op.FullyConnected, [ifm, weights], ofm)
Patrik Gustavsson2349d422020-12-01 16:02:29 +010043
Diqing Zhong94457b12020-12-09 15:22:40 +010044 ifm.consumer_list.append(op)
45
46 prev_op = op.clone()
Patrik Gustavsson3a269202021-01-21 08:28:55 +010047 prev_op.ifm_shapes = op.ifm_shapes.copy()
48 prev_op.ofm_shapes = op.ofm_shapes.copy()
Patrik Gustavsson2349d422020-12-01 16:02:29 +010049
Diqing Zhong94457b12020-12-09 15:22:40 +010050 conv_op = convert_batched_fc_shape(op, None, None)
51
52 assert conv_op.ifm == prev_op.ifm
53 assert conv_op.ofm == prev_op.ofm
Patrik Gustavsson3a269202021-01-21 08:28:55 +010054 assert op.ifm_shapes[0] == Shape4D([1, 2, 2, 8])
55 assert op.ofm_shapes[0] == Shape4D([1, 2, 2, 8])
Diqing Zhong94457b12020-12-09 15:22:40 +010056 assert conv_op.type == Op.FullyConnected
57 assert len(conv_op.ifm.shape) == 2
Patrik Gustavsson3a269202021-01-21 08:28:55 +010058 assert len(conv_op.ofm.shape) == 2
59 assert conv_op.ifm.shape == conv_op.ofm.shape
60
61 ifm.shape = [1, 8]
62 weights.shape = [8, 1]
63 ofm.shape = [1, 8]
64 op = testutil.create_op(Op.FullyConnected, [ifm, weights], ofm)
65 ifm.consumer_list.append(op)
66
67 prev_op = op.clone()
68 prev_op.ifm_shapes = op.ifm_shapes.copy()
69 prev_op.ofm_shapes = op.ofm_shapes.copy()
70
71 conv_op = convert_batched_fc_shape(op, None, None)
72
73 assert conv_op.ifm == prev_op.ifm
74 assert conv_op.ofm == prev_op.ofm
75 assert op.ifm_shapes[0] == prev_op.ifm_shapes[0]
76 assert op.ofm_shapes[0] == prev_op.ofm_shapes[0]
77 assert conv_op.type == Op.FullyConnected
78 assert len(conv_op.ifm.shape) == 2
79 assert len(conv_op.ofm.shape) == 2
Diqing Zhong94457b12020-12-09 15:22:40 +010080 assert conv_op.ifm.shape == conv_op.ofm.shape
Louis Verhaardae2d5532020-12-11 17:19:54 +010081
82
83def test_optimise_pad():
84 """
85 Tests that the PAD operator is bypassed when followed by a convolution operator,
86 and that the padding of the convolution operation is correctly updated
87 """
88 # Create Pad operation followed by Conv2D
89 quant = testutil.default_quant_params()
90 in_tens = Tensor([1, 76, 75, 64], DataType.uint8, "input")
91 in_tens.quantization = quant
92 pad_input = create_const_tensor("pad_input", [4, 2], DataType.int32, [[0, 0], [2, 1], [1, 1], [0, 0]])
93 temp_tens = Tensor([1, 79, 77, 64], DataType.uint8, "pad_out")
94 temp_tens.quantization = quant.clone()
95 out_tens = Tensor([1, 76, 75, 64], DataType.uint8, "output")
96 out_tens.quantization = quant.clone()
97 weight_tens = Tensor([5, 3, 64, 64], DataType.uint8, "weights")
98 weight_tens.values = np.zeros(weight_tens.shape)
99 weight_tens.quant_values = np.zeros(weight_tens.shape, np.uint8)
100 weight_tens.quantization = quant.clone()
101
102 bias_tens = Tensor([64], DataType.int32, "biases")
103 pad_op = testutil.create_op(Op.Pad, [in_tens, pad_input], temp_tens)
104 attrs = {"padding": Padding.VALID, "stride_w": 2, "stride_h": 2, "dilation_w_factor": 1, "dilation_h_factor": 1}
105 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
106 pad_op.run_on_npu = True
107 conv2d_op = testutil.create_op(Op.Conv2D, [temp_tens, weight_tens, bias_tens], out_tens, attrs)
108 conv2d_op.run_on_npu = True
109 nng = Graph()
110 sg = testutil.create_subgraph([pad_op, conv2d_op])
111 nng.subgraphs.append(sg)
112 arch = testutil.create_arch()
113
114 optimise_pad(conv2d_op, nng, arch)
115
116 op = sg.output_tensors[0].ops[0]
117 assert op.type == Op.Conv2D
118 assert op.attrs["padding"] == Padding.EXPLICIT
119 assert op.attrs["explicit_padding"] == (2, 1, 1, 1)
120 assert op.ifm.shape == [1, 76, 75, 64]
121 assert pad_op not in op.ifm.ops
Patrik Gustavsson3a269202021-01-21 08:28:55 +0100122
123
124def test_remove_reshape():
125 """
126 Tests that the expected reshape are removed in graph_optimisation
127 """
128
129 def setup_network():
130 quant = testutil.default_quant_params()
131 # create reshape1 op
132 ifm_shape = [64, 16]
133 reshape1_ofm_shape = [1, 4, 16, 16]
134 reshape1_ifm = create_const_tensor("reshape1_in", ifm_shape, DataType.uint8, np.zeros(ifm_shape))
135 reshape1_ifm.quantization = quant
136 reshape1_ofm = create_const_tensor(
137 "reshape1_out", reshape1_ofm_shape, DataType.uint8, np.zeros(reshape1_ofm_shape)
138 )
139 reshape1_ofm.quantization = quant
140 shape_tens = create_const_tensor("reshape1_shape", [1], DataType.int32, reshape1_ofm_shape)
141 reshape1_op = testutil.create_op(Op.Reshape, [reshape1_ifm, shape_tens], reshape1_ofm, set_ifm_ofm_shapes=False)
142 reshape1_op.attrs["new_shape"] = reshape1_ofm_shape
143 reshape1_op.run_on_npu = True
144
145 # create reshape2 op
146 reshape2_ofm_shape = [1, 8, 8, 16]
147 reshape2_ofm = create_const_tensor(
148 "reshape2_out", reshape2_ofm_shape, DataType.uint8, np.zeros(reshape2_ofm_shape)
149 )
150 reshape2_ofm.quantization = quant
151 shape_tens = create_const_tensor("reshape2_shape", [1], DataType.int32, reshape2_ofm_shape)
152 reshape2_op = testutil.create_op(Op.Reshape, [reshape1_ofm, shape_tens], reshape2_ofm, set_ifm_ofm_shapes=False)
153 reshape2_op.attrs["new_shape"] = reshape2_ofm_shape
154 reshape2_op.run_on_npu = True
155
156 # create conv op
157 conv_ofm = Tensor([1, 8, 8, 16], DataType.uint8, "output")
158 conv_ofm.quantization = quant.clone()
159 weight_tens = Tensor([1, 1, 16, 16], DataType.uint8, "weights")
160 weight_tens.values = np.zeros(weight_tens.shape)
161 weight_tens.quant_values = np.zeros(weight_tens.shape, np.uint8)
162 weight_tens.quantization = quant.clone()
163 bias_tens = Tensor([16], DataType.int32, "biases")
164
165 attrs = {"padding": Padding.SAME, "stride_w": 1, "stride_h": 1, "dilation_w_factor": 1, "dilation_h_factor": 1}
166 attrs["strides"] = (1, attrs["stride_h"], attrs["stride_w"], 1)
167
168 conv2d_op = testutil.create_op(
169 Op.Conv2D, [reshape1_ofm, weight_tens, bias_tens], conv_ofm, attrs=attrs, set_ifm_ofm_shapes=False
170 )
171 conv2d_op.run_on_npu = True
172
173 # create reshape3 op
174 ofm_shape = [8, 8, 16]
175 reshape3_ofm = create_const_tensor("reshape3_out", ofm_shape, DataType.uint8, np.zeros(ofm_shape))
176 reshape3_ofm.quantization = quant
177 shape_tens = create_const_tensor("reshape3_shape", [1], DataType.int32, ofm_shape)
178 reshape3_op = testutil.create_op(Op.Reshape, [conv_ofm, shape_tens], reshape3_ofm, set_ifm_ofm_shapes=False)
179 reshape3_op.attrs["new_shape"] = ofm_shape
180 reshape3_op.run_on_npu = True
181 nng = Graph()
182 sg = testutil.create_subgraph([reshape1_op, reshape2_op, conv2d_op, reshape3_op])
183 nng.subgraphs.append(sg)
184
185 return nng, reshape1_op, reshape2_op, conv2d_op, reshape3_op
186
187 # Test1 no Reshape op is expected to remain in the NPU subgrapgh
188 # but first one will be put on CPU
189 # Network is Reshape-Reshape-Conv-Reshape
190 # Result is cpu_Reshape-Conv
191 nng, reshape1_op, reshape2_op, conv2d_op, reshape3_op = setup_network()
192 arch = testutil.create_arch()
193 assert verify_graph_health(nng)
194 nng = optimise_graph_a(nng, arch)
195 assert verify_graph_health(nng)
196 assert conv2d_op.ifm == reshape1_op.ofm
197 assert conv2d_op.ofm == reshape3_op.ofm
198
199 # Test2 reshape2 with different quantisation, this Reshape op is expected to remain
200 # Network is Reshape-Reshape-Conv-Reshape
201 # expected is cpu_Reshape-Reshape-Conv
202 nng, reshape1_op, reshape2_op, conv2d_op, reshape3_op = setup_network()
203 quant_zp32 = testutil.default_quant_params()
204 quant_zp32.zero_point = 32
205 reshape2_op.ofm.quantization = quant_zp32
206 assert verify_graph_health(nng)
207 nng = optimise_graph_a(nng, arch)
208 assert verify_graph_health(nng)
209 assert conv2d_op.ofm == reshape3_op.ofm