blob: 957726797bb47d6f1f41d8765332a16694e58372 [file] [log] [blame]
Mike Kelly4cc341c2023-07-07 15:43:06 +01001//
2// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#include <ResolveType.hpp>
7
8#include <armnn/INetwork.hpp>
9#include <armnn/utility/NumericCast.hpp>
10#include <GraphUtils.hpp>
11#include <CommonTestUtils.hpp>
12#include <armnnTestUtils/DataLayoutUtils.hpp>
13
14#include <doctest/doctest.h>
15
16#include <vector>
17#include "backendsCommon/SubgraphUtils.hpp"
18
19namespace armnn
20{
21
22template<DataType ArmnnIType, DataType ArmnnOType,
23 typename TInput = ResolveType<ArmnnIType>, typename TOutput = ResolveType<ArmnnOType>>
24void EndToEndLayerTest(IRuntimePtr runtime,
25 IOptimizedNetworkPtr optNet,
26 const std::map<int, std::vector<TInput>>& inputTensorData,
27 const std::map<int, std::vector<TOutput>>& expectedOutputData,
28 float tolerance = 0.000001f)
29{
30 // Loads it into the runtime.
31 NetworkId netId;
32 std::string errorMessage;
33 armnn::Status loadingStatus = runtime->LoadNetwork(netId, std::move(optNet), errorMessage);
34 CHECK_MESSAGE(loadingStatus == Status::Success, errorMessage);
35
36 InputTensors inputTensors;
37 inputTensors.reserve(inputTensorData.size());
38 for (auto&& it : inputTensorData)
39 {
40 inputTensors.push_back({it.first,
41 ConstTensor(runtime->GetInputTensorInfo(netId, it.first), it.second.data())});
42 }
43 OutputTensors outputTensors;
44 outputTensors.reserve(expectedOutputData.size());
45 std::map<int, std::vector<TOutput>> outputStorage;
46 for (auto&& it : expectedOutputData)
47 {
48 std::vector<TOutput> out(it.second.size());
49 outputStorage.emplace(it.first, out);
50 outputTensors.push_back({it.first,
51 Tensor(runtime->GetOutputTensorInfo(netId, it.first),
52 outputStorage.at(it.first).data())});
53 }
54
55 // Does the inference.
56 runtime->EnqueueWorkload(netId, inputTensors, outputTensors);
57
58 // Checks the results.
59 for (auto&& it : expectedOutputData)
60 {
61 std::vector<TOutput> out = outputStorage.at(it.first);
62 for (unsigned int i = 0; i < out.size(); ++i)
63 {
64 CHECK_MESSAGE(Compare<ArmnnOType>(it.second[i], out[i], tolerance) == true,
65 "Actual output: " << out[i] << ". Expected output:" << it.second[i]);
66
67 }
68 }
69}
70
71template<armnn::DataType ArmnnType, typename T = ResolveType<ArmnnType>>
72armnn::INetworkPtr CreateReshapeInOutNetwork(const armnn::TensorShape& inputShape,
73 const armnn::TensorShape& outputShape,
74 ReshapeDescriptor& descriptor,
75 const float qScale = 1.0f,
76 const int32_t qOffset = 0)
77{
78 armnn::INetworkPtr network(armnn::INetwork::Create());
79
80 TensorInfo inputTensorInfo(inputShape, ArmnnType, qScale, qOffset, true);
81 TensorInfo outputTensorInfo(outputShape, ArmnnType, qScale, qOffset);
82
83 IConnectableLayer* activation0 = network->AddActivationLayer(ActivationFunction::ReLu, "act0");
84 IConnectableLayer* activation1 = network->AddActivationLayer(ActivationFunction::ReLu, "act1");
85 IConnectableLayer* activation2 = network->AddActivationLayer(ActivationFunction::ReLu, "act2");
86 IConnectableLayer* activation3 = network->AddActivationLayer(ActivationFunction::ReLu, "act3");
87 IConnectableLayer* reshape = network->AddReshapeLayer(descriptor, "reshape");
88
89 IConnectableLayer* input = network->AddInputLayer(0, "input");
90 IConnectableLayer* output1 = network->AddOutputLayer(0, "output1");
91 IConnectableLayer* output2 = network->AddOutputLayer(1, "output2");
92 IConnectableLayer* output3 = network->AddOutputLayer(2, "output3");
93
94 Connect(input, activation0, inputTensorInfo, 0, 0);
95 Connect(activation0, reshape, inputTensorInfo, 0, 0);
96
97 Connect(reshape, activation1, outputTensorInfo, 0, 0);
98 Connect(reshape, activation2, outputTensorInfo, 0, 0);
99 Connect(reshape, activation3, outputTensorInfo, 0, 0);
100 Connect(activation1, output1, outputTensorInfo, 0, 0);
101 Connect(activation2, output2, outputTensorInfo, 0, 0);
102 Connect(activation3, output3, outputTensorInfo, 0, 0);
103
104 return network;
105}
106
107template<armnn::DataType ArmnnType, typename T = ResolveType<ArmnnType>>
108armnn::INetworkPtr CreateReshapeConv2dInOutNetwork(const armnn::TensorShape& inputShape,
109 const armnn::TensorShape& weightsShape,
110 const armnn::TensorShape& convOutputShape,
111 const armnn::TensorShape& outputShape,
112 std::vector<float>& weightsData,
113 ReshapeDescriptor& descriptor,
114 Convolution2dDescriptor& convolution2DDescriptor,
115 bool convFirst,
116 const float qScale = 1.0f,
117 const int32_t qOffset = 0)
118{
119 armnn::INetworkPtr network(armnn::INetwork::Create());
120 TensorInfo weightsTensorInfo(weightsShape, ArmnnType, qScale, qOffset, true);
121 ConstTensor weights(weightsTensorInfo, weightsData);
122
123 IConnectableLayer* convolution1 = network->AddConvolution2dLayer(convolution2DDescriptor, "conv2d");
124 IConnectableLayer* weightsLayer = network->AddConstantLayer(weights, "weights");
125
126 IConnectableLayer* activation1 = network->AddActivationLayer(ActivationFunction::ReLu, "act");
127 IConnectableLayer* reshape = network->AddReshapeLayer(descriptor, "reshape");
128
129 IConnectableLayer* input = network->AddInputLayer(0, "input");
130 IConnectableLayer* output = network->AddOutputLayer(0, "output");
131
132 TensorInfo inputTensorInfo(inputShape, ArmnnType, qScale, qOffset, true);
133 TensorInfo convTensorInfo(convOutputShape, ArmnnType, qScale, qOffset, true);
134 TensorInfo outputTensorInfo(outputShape, ArmnnType, qScale, qOffset);
135 TensorInfo reshapeTensorInfo(descriptor.m_TargetShape, ArmnnType, qScale, qOffset, true);
136
137 if (convFirst)
138 {
139 Connect(input, convolution1, inputTensorInfo, 0, 0);
140 Connect(weightsLayer, convolution1, weightsTensorInfo, 0, 1);
141 Connect(convolution1, reshape, convTensorInfo, 0, 0);
142 Connect(reshape, activation1, reshapeTensorInfo, 0, 0);
143 Connect(activation1, output, outputTensorInfo, 0, 0);
144 }
145 else
146 {
147 Connect(input, activation1, inputTensorInfo, 0, 0);
148 Connect(activation1, reshape, inputTensorInfo, 0, 0);
149 Connect(reshape, convolution1, reshapeTensorInfo, 0, 0);
150 Connect(weightsLayer, convolution1, weightsTensorInfo, 0, 1);
151 Connect(convolution1, output, outputTensorInfo, 0, 0);
152 }
153 return network;
154}
155
156template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
157void ReshapeRemovalEndToEnd(const std::vector<armnn::BackendId>& backends)
158{
159 using namespace armnn;
160
161 const TensorShape& inputShape = { 2, 3 };
162 const TensorShape& outputShape = { 6 };
163
164 ReshapeDescriptor descriptor;
165 descriptor.m_TargetShape = outputShape;
166
167 INetworkPtr network = CreateReshapeInOutNetwork<ArmnnType>(inputShape, outputShape, descriptor);
168
169 CHECK(network);
170
171 std::vector<T> data{ 1, 2, 3,
172 4, 5, 6 };
173
174 std::map<int, std::vector<float>> inputTensorData = { { 0, data } };
175 std::map<int, std::vector<float>> expectedOutputData = { { 0, data }, { 1, data }, { 2, data } };
176
177 // Create runtime in which test will run
178 IRuntime::CreationOptions options;
179 IRuntimePtr runtime(IRuntime::Create(options));
180
181 // optimize the network
182 IOptimizedNetworkPtr optNet = Optimize(*network, backends, runtime->GetDeviceSpec());
183
184 Graph& graph = GetGraphForTesting(optNet.get());
185 CHECK(CheckSequence(graph.cbegin(), graph.cend(),
186 LayerNameAndTypeCheck(LayerType::Input, "input"),
187 LayerNameAndTypeCheck(LayerType::Activation, "act0"),
188 LayerNameAndTypeCheck(LayerType::Activation, "act1"),
189 LayerNameAndTypeCheck(LayerType::Activation, "act2"),
190 LayerNameAndTypeCheck(LayerType::Activation, "act3"),
191 LayerNameAndTypeCheck(LayerType::Output, "output1"),
192 LayerNameAndTypeCheck(LayerType::Output, "output2"),
193 LayerNameAndTypeCheck(LayerType::Output, "output3")));
194
195 EndToEndLayerTest<ArmnnType, ArmnnType>(std::move(runtime), std::move(optNet), inputTensorData, expectedOutputData);
196}
197
198template<armnn::DataType ArmnnType, typename T = armnn::ResolveType<ArmnnType>>
199void ReshapeRemovalNCHWEndToEnd(const std::vector<armnn::BackendId>& backends, bool shouldBeRemoved, bool convFirst)
200{
201 using namespace armnn;
202
203 // shapes are different if convFirst or not
204 //these are convFirst
205 TensorShape inputShape;
206 TensorShape convOutputShape;
207 TensorShape weightsShape;
208 TensorShape reshapeShape;
209 TensorShape outputShape;
210
211 if (convFirst)
212 {
213 inputShape = { 1, 1, 5, 5 };
214 convOutputShape = { 1, 1, 3, 3 };
215 weightsShape = { 1, 1, 3, 3 };
216 reshapeShape = { 9 };
217 outputShape = { 9 };
218 }
219 else
220 {
221 inputShape = { 5, 5 };
222 reshapeShape = { 1, 1, 5, 5 };
223 convOutputShape = { 1, 1, 3, 3 };
224 weightsShape = { 1, 1, 3, 3 };
225 outputShape = { 1, 1, 3, 3 };
226 }
227
228 ReshapeDescriptor descriptor;
229 descriptor.m_TargetShape = reshapeShape;
230
231 Convolution2dDescriptor convolution2DDescriptor;
232 convolution2DDescriptor.m_PadLeft = 0;
233 convolution2DDescriptor.m_PadRight = 0;
234 convolution2DDescriptor.m_PadTop = 0;
235 convolution2DDescriptor.m_PadBottom = 0;
236 convolution2DDescriptor.m_StrideX = 1;
237 convolution2DDescriptor.m_StrideY = 1;
238 convolution2DDescriptor.m_DataLayout = DataLayout::NCHW;
239 convolution2DDescriptor.m_BiasEnabled = false;
240
241 TensorInfo inputInfo(inputShape, DataType::Float32, true);
242 TensorInfo outputInfo(convOutputShape, DataType::Float32);
243 TensorInfo weightsInfo(weightsShape, DataType::Float32, true);
244
245 std::vector<float> inputData =
246 {
247 1.0f, 8.0f, 3.0f, 4.0f, 6.0f,
248 5.0f, 7.0f, 3.0f, 1.0f, 8.0f,
249 2.0f, 3.0f, 9.0f, 8.0f, 1.0f,
250 3.0f, 6.0f, 1.0f, 1.0f, 9.0f,
251 5.0f, 3.0f, 9.0f, 3.0f, 2.0f
252 };
253
254 std::vector<float> weightsData =
255 {
256 4.0f, 0.0f, 3.0f,
257 5.0f, 0.0f, 2.0f,
258 6.0f, 0.0f, 1.0f
259 };
260
261 std::vector<float> outputData =
262 {
263 65.0f, 107.0f, 116.0f,
264 76.0f, 99.0f, 98.0f,
265 91.0f, 89.0f, 118.0f
266 };
267
268 INetworkPtr network = CreateReshapeConv2dInOutNetwork<DataType::Float32>(inputShape,
269 weightsShape,
270 convOutputShape,
271 outputShape,
272 weightsData,
273 descriptor,
274 convolution2DDescriptor,
275 convFirst);
276 CHECK(network);
277
278 std::map<int, std::vector<float>> inputTensorData = { { 0, inputData } };
279 std::map<int, std::vector<float>> expectedOutputData = { { 0, outputData } };
280
281 // Create runtime in which test will run
282 IRuntime::CreationOptions options;
283 IRuntimePtr runtime(IRuntime::Create(options));
284
285 // optimize the network
286 IOptimizedNetworkPtr optNet = Optimize(*network, backends, runtime->GetDeviceSpec());
287
288 Graph& graph = GetGraphForTesting(optNet.get());
289
290 if (shouldBeRemoved)
291 {
292 if (convFirst)
293 {
294 CHECK(CheckSequence(graph.cbegin(), graph.cend(),
295 LayerNameAndTypeCheck(LayerType::Input, "input"),
296 LayerNameAndTypeCheck(LayerType::Constant, "weights"),
297 LayerNameAndTypeCheck(LayerType::Convolution2d, "conv2d"),
298 LayerNameAndTypeCheck(LayerType::Activation, "act"),
299 LayerNameAndTypeCheck(LayerType::Output, "output")));
300 }
301 else
302 {
303 CHECK(CheckSequence(graph.cbegin(), graph.cend(),
304 LayerNameAndTypeCheck(LayerType::Input, "input"),
305 LayerNameAndTypeCheck(LayerType::Constant, "weights"),
306 LayerNameAndTypeCheck(LayerType::Activation, "act"),
307 LayerNameAndTypeCheck(LayerType::Convolution2d, "conv2d"),
308 LayerNameAndTypeCheck(LayerType::Output, "output")));
309 }
310 }
311 else
312 {
313 if (convFirst)
314 {
315 CHECK(CheckSequence(graph.cbegin(), graph.cend(),
316 LayerNameAndTypeCheck(LayerType::Input, "input"),
317 LayerNameAndTypeCheck(LayerType::Constant, "weights"),
318 LayerNameAndTypeCheck(LayerType::Convolution2d, "conv2d"),
319 LayerNameAndTypeCheck(LayerType::Reshape, "reshape"),
320 LayerNameAndTypeCheck(LayerType::Activation, "act"),
321 LayerNameAndTypeCheck(LayerType::Output, "output")));
322 }
323 else
324 {
325 CHECK(CheckSequence(graph.cbegin(), graph.cend(),
326 LayerNameAndTypeCheck(LayerType::Input, "input"),
327 LayerNameAndTypeCheck(LayerType::Constant, "weights"),
328 LayerNameAndTypeCheck(LayerType::Activation, "act"),
329 LayerNameAndTypeCheck(LayerType::Reshape, "reshape"),
330 LayerNameAndTypeCheck(LayerType::Convolution2d, "conv2d"),
331 LayerNameAndTypeCheck(LayerType::Output, "output")));
332 }
333 }
334
335 EndToEndLayerTest<ArmnnType, ArmnnType>(std::move(runtime), std::move(optNet), inputTensorData, expectedOutputData);
336}
337
338template<typename Descriptor, typename LayerType>
339void CheckIsNCHW()
340{
341 Graph graph;
342 Descriptor nchwDesc;
343 nchwDesc.m_DataLayout = DataLayout::NCHW;
344 auto nchwLayer = graph.AddLayer<LayerType>(nchwDesc, "");
345 CHECK(IsNCHW(*nchwLayer));
346
347 Descriptor nhwcDesc;
348 nhwcDesc.m_DataLayout = DataLayout::NHWC;
349 auto nhwcLayer = graph.AddLayer<LayerType>(nhwcDesc, "");
350 CHECK_FALSE(IsNCHW(*nhwcLayer));
351}
352
353TEST_CASE("CheckIsNCHW")
354{
355 Graph graph;
356 BatchMatMulDescriptor descriptor1;
357 descriptor1.m_DataLayoutX = DataLayout::NHWC;
358 descriptor1.m_DataLayoutY = DataLayout::NHWC;
359 auto batchMatMulLayer1 = graph.AddLayer<BatchMatMulLayer>(descriptor1, "");
360 CHECK_FALSE(IsNCHW(*batchMatMulLayer1));
361
362 BatchMatMulDescriptor descriptor2;
363 descriptor2.m_DataLayoutX = DataLayout::NCHW;
364 descriptor2.m_DataLayoutY = DataLayout::NHWC;
365 auto batchMatMulLayer2 = graph.AddLayer<BatchMatMulLayer>(descriptor2, "");
366 CHECK(IsNCHW(*batchMatMulLayer2));
367
368 BatchMatMulDescriptor descriptor3;
369 descriptor3.m_DataLayoutX = DataLayout::NHWC;
370 descriptor3.m_DataLayoutY = DataLayout::NCHW;
371 auto batchMatMulLayer3 = graph.AddLayer<BatchMatMulLayer>(descriptor3, "");
372 CHECK(IsNCHW(*batchMatMulLayer3));
373
374 BatchMatMulDescriptor descriptor4;
375 descriptor4.m_DataLayoutX = DataLayout::NCHW;
376 descriptor4.m_DataLayoutY = DataLayout::NCHW;
377 auto batchMatMulLayer4 = graph.AddLayer<BatchMatMulLayer>(descriptor4, "");
378 CHECK(IsNCHW(*batchMatMulLayer4));
379
380 CheckIsNCHW<BatchToSpaceNdDescriptor, BatchToSpaceNdLayer>();
381 CheckIsNCHW<Convolution2dDescriptor, Convolution2dLayer>();
382 CheckIsNCHW<Convolution3dDescriptor, Convolution3dLayer>();
383 CheckIsNCHW<DepthwiseConvolution2dDescriptor, DepthwiseConvolution2dLayer>();
384 CheckIsNCHW<InstanceNormalizationDescriptor, InstanceNormalizationLayer>();
385 CheckIsNCHW<L2NormalizationDescriptor, L2NormalizationLayer>();
386 CheckIsNCHW<NormalizationDescriptor, NormalizationLayer>();
387 CheckIsNCHW<Pooling2dDescriptor, Pooling2dLayer>();
388 CheckIsNCHW<Pooling3dDescriptor, Pooling3dLayer>();
389 CheckIsNCHW<SpaceToBatchNdDescriptor, SpaceToBatchNdLayer>();
390 CheckIsNCHW<SpaceToDepthDescriptor, SpaceToDepthLayer>();
391 CheckIsNCHW<StridedSliceDescriptor, StridedSliceLayer>();
392
393 // Check Default
394 auto elementwiseLayer = graph.AddLayer<ElementwiseBinaryLayer>(BinaryOperation::Add, "");
395 CHECK_FALSE(IsNCHW(*elementwiseLayer));
396}
397
398
399} // Namespace