1 //
2 // Copyright © 2019 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5
6 #include <TestUtils.hpp>
7
8 #include <Network.hpp>
9 #include <Optimizer.hpp>
10
11 #include <doctest/doctest.h>
12
13 using namespace armnn;
14
15 TEST_SUITE("Optimizer")
16 {
17 using namespace armnn::optimizations;
18
19 namespace
20 {
21
22 /// Shared function for the below tests, so that we test the same network in both cases.
CreateTestNetworkImpl()23 std::unique_ptr<NetworkImpl> CreateTestNetworkImpl()
24 {
25 std::unique_ptr<NetworkImpl> network(new NetworkImpl());
26
27 auto input = network->AddInputLayer(0, "input");
28 const TensorInfo inputInfo({ 1, 2, 3, 4 }, DataType::Float32);
29 input->GetOutputSlot(0).SetTensorInfo(inputInfo);
30
31 // Insert Permute which swaps batches and channels dimensions
32 auto permute = network->AddPermuteLayer(PermuteDescriptor(PermutationVector{ 3, 1, 2, 0 }), "permute");
33 const TensorInfo permuteInfo({ 4, 2, 3, 1 }, DataType::Float32);
34 permute->GetOutputSlot(0).SetTensorInfo(permuteInfo);
35 input->GetOutputSlot(0).Connect(permute->GetInputSlot(0));
36
37 // Insert BatchToSpace
38 BatchToSpaceNdDescriptor batchToSpaceDesc;
39 batchToSpaceDesc.m_BlockShape = { 2, 2 };
40 batchToSpaceDesc.m_DataLayout = DataLayout::NHWC;
41 auto batchToSpace = network->AddBatchToSpaceNdLayer(batchToSpaceDesc, "batchToSpace");
42 const TensorInfo batchToSpaceInfo({ 1, 4, 6, 1 }, DataType::Float32);
43 batchToSpace->GetOutputSlot(0).SetTensorInfo(batchToSpaceInfo);
44 permute->GetOutputSlot(0).Connect(batchToSpace->GetInputSlot(0));
45
46 auto output = network->AddOutputLayer(0, "output");
47 batchToSpace->GetOutputSlot(0).Connect(output->GetInputSlot(0));
48
49 return network;
50 }
51
52 /// Shared function for the below tests, so that we test the same network in both cases.
CreateTransposeTestNetworkImpl()53 std::unique_ptr<NetworkImpl> CreateTransposeTestNetworkImpl()
54 {
55 // Create a network
56 std::unique_ptr<NetworkImpl> network(new NetworkImpl());
57
58 auto input = network->AddInputLayer(0, "input");
59 const TensorInfo inputInfo({ 1, 2, 3, 4 }, DataType::Float32);
60 input->GetOutputSlot(0).SetTensorInfo(inputInfo);
61
62 // Insert Permute which swaps batches and channels dimensions
63 auto permute = network->AddTransposeLayer(TransposeDescriptor(PermutationVector{ 3, 1, 2, 0 }), "permute");
64 const TensorInfo permuteInfo({ 4, 2, 3, 1 }, DataType::Float32);
65 permute->GetOutputSlot(0).SetTensorInfo(permuteInfo);
66 input->GetOutputSlot(0).Connect(permute->GetInputSlot(0));
67
68 // Insert BatchToSpace
69 BatchToSpaceNdDescriptor batchToSpaceDesc;
70 batchToSpaceDesc.m_BlockShape = { 2, 2 };
71 batchToSpaceDesc.m_DataLayout = DataLayout::NHWC;
72 auto batchToSpace = network->AddBatchToSpaceNdLayer(batchToSpaceDesc, "batchToSpace");
73 const TensorInfo batchToSpaceInfo({ 1, 4, 6, 1 }, DataType::Float32);
74 batchToSpace->GetOutputSlot(0).SetTensorInfo(batchToSpaceInfo);
75 permute->GetOutputSlot(0).Connect(batchToSpace->GetInputSlot(0));
76
77 auto output = network->AddOutputLayer(0, "output");
78 batchToSpace->GetOutputSlot(0).Connect(output->GetInputSlot(0));
79
80 return network;
81 }
82
83 } // namespace
84
85 /// Tests that the optimization performed by PermuteAndBatchToSpaceAsDepthToSpace is as expected.
86 /// Note this does not ensure the correctness of the optimization - that is done in the below test.
87 TEST_CASE("PermuteAndBatchToSpaceAsDepthToSpaceOptimizerTest")
88 {
89 std::unique_ptr<NetworkImpl> network = CreateTestNetworkImpl();
90 Graph graph = network.get()->GetGraph();
91
92 // Confirm initial graph is as we expect
93 CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<InputLayer>, &IsLayerOfType<PermuteLayer>,
94 &IsLayerOfType<BatchToSpaceNdLayer>, &IsLayerOfType<OutputLayer>));
95
96 // Perform the optimization which should merge the two layers into a DepthToSpace
97 armnn::Optimizer::Pass(graph, MakeOptimizations(PermuteAndBatchToSpaceAsDepthToSpace()));
98
99 // Check that the replacement has been made as expected
__anon06dee2ec0202(const Layer* const layer) 100 auto checkDepthToSpace = [](const Layer* const layer) -> bool {
101 return IsLayerOfType<DepthToSpaceLayer>(layer) &&
102 static_cast<const DepthToSpaceLayer*>(layer)->GetParameters().m_BlockSize == 2 &&
103 static_cast<const DepthToSpaceLayer*>(layer)->GetParameters().m_DataLayout == DataLayout::NHWC &&
104 layer->GetOutputHandler().GetTensorInfo() == TensorInfo({ 1, 4, 6, 1 }, DataType::Float32);
105 };
106
107 CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<InputLayer>, checkDepthToSpace,
108 &IsLayerOfType<OutputLayer>));
109
110 // Check the new layer has the two merged layers listed as related layers
111 std::list<std::string> testRelatedLayers = { "batchToSpace", "permute" };
112 CHECK(CheckRelatedLayers<DepthToSpaceLayer>(graph, testRelatedLayers));
113 }
114
115 /// Tests that the optimization performed by PermuteAndBatchToSpaceAsDepthToSpace is as expected.
116 /// Note this does not ensure the correctness of the optimization - that is done in the below test.
117 TEST_CASE("TransposeAndBatchToSpaceAsDepthToSpaceOptimizerTest")
118 {
119 std::unique_ptr<NetworkImpl> network = CreateTransposeTestNetworkImpl();
120 Graph graph = network.get()->GetGraph();
121
122 // Confirm initial graph is as we expect
123 CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<InputLayer>, &IsLayerOfType<TransposeLayer>,
124 &IsLayerOfType<BatchToSpaceNdLayer>, &IsLayerOfType<OutputLayer>));
125
126 // Perform the optimization which should merge the two layers into a DepthToSpace
127 armnn::Optimizer::Pass(graph, MakeOptimizations(TransposeAndBatchToSpaceAsDepthToSpace()));
128
129 // Check that the replacement has been made as expected
__anon06dee2ec0302(const Layer* const layer) 130 auto checkDepthToSpace = [](const Layer* const layer) -> bool {
131 return IsLayerOfType<DepthToSpaceLayer>(layer) &&
132 static_cast<const DepthToSpaceLayer*>(layer)->GetParameters().m_BlockSize == 2 &&
133 static_cast<const DepthToSpaceLayer*>(layer)->GetParameters().m_DataLayout == DataLayout::NHWC &&
134 layer->GetOutputHandler().GetTensorInfo() == TensorInfo({ 1, 4, 6, 1 }, DataType::Float32);
135 };
136
137 CHECK(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<InputLayer>, checkDepthToSpace,
138 &IsLayerOfType<OutputLayer>));
139
140 // Check the new layer has the two merged layers listed as related layers
141 std::list<std::string> testRelatedLayers = { "batchToSpace", "permute" };
142 CHECK(CheckRelatedLayers<DepthToSpaceLayer>(graph, testRelatedLayers));
143 }
144
145 // This unit test needs the reference backend, it's not available if the reference backend is not built
146 #if defined(ARMNNREF_ENABLED)
147
148 /// Shared function for the below tests, so that we test the same network in both cases.
CreateTestNetwork()149 INetworkPtr CreateTestNetwork()
150 {
151 // Create a network
152 INetworkPtr network = INetwork::Create();
153
154 auto input = network->AddInputLayer(0, "input");
155 const TensorInfo inputInfo({ 1, 2, 3, 4 }, DataType::Float32);
156 input->GetOutputSlot(0).SetTensorInfo(inputInfo);
157
158 // Insert Permute which swaps batches and channels dimensions
159 auto permute = network->AddPermuteLayer(PermuteDescriptor(PermutationVector{ 3, 1, 2, 0 }), "permute");
160 const TensorInfo permuteInfo({ 4, 2, 3, 1 }, DataType::Float32);
161 permute->GetOutputSlot(0).SetTensorInfo(permuteInfo);
162 input->GetOutputSlot(0).Connect(permute->GetInputSlot(0));
163
164 // Insert BatchToSpace
165 BatchToSpaceNdDescriptor batchToSpaceDesc;
166 batchToSpaceDesc.m_BlockShape = { 2, 2 };
167 batchToSpaceDesc.m_DataLayout = DataLayout::NHWC;
168 auto batchToSpace = network->AddBatchToSpaceNdLayer(batchToSpaceDesc, "batchToSpace");
169 const TensorInfo batchToSpaceInfo({ 1, 4, 6, 1 }, DataType::Float32);
170 batchToSpace->GetOutputSlot(0).SetTensorInfo(batchToSpaceInfo);
171 permute->GetOutputSlot(0).Connect(batchToSpace->GetInputSlot(0));
172
173 auto output = network->AddOutputLayer(0, "output");
174 batchToSpace->GetOutputSlot(0).Connect(output->GetInputSlot(0));
175
176 return network;
177 }
178
179 /// Shared function for the below tests, so that we test the same network in both cases.
CreateTransposeTestNetwork()180 INetworkPtr CreateTransposeTestNetwork()
181 {
182 // Create a network
183 INetworkPtr network = INetwork::Create();
184
185 auto input = network->AddInputLayer(0, "input");
186 const TensorInfo inputInfo({ 1, 2, 3, 4 }, DataType::Float32);
187 input->GetOutputSlot(0).SetTensorInfo(inputInfo);
188
189 // Insert Permute which swaps batches and channels dimensions
190 auto permute = network->AddTransposeLayer(TransposeDescriptor(PermutationVector{ 3, 1, 2, 0 }), "permute");
191 const TensorInfo permuteInfo({ 4, 2, 3, 1 }, DataType::Float32);
192 permute->GetOutputSlot(0).SetTensorInfo(permuteInfo);
193 input->GetOutputSlot(0).Connect(permute->GetInputSlot(0));
194
195 // Insert BatchToSpace
196 BatchToSpaceNdDescriptor batchToSpaceDesc;
197 batchToSpaceDesc.m_BlockShape = { 2, 2 };
198 batchToSpaceDesc.m_DataLayout = DataLayout::NHWC;
199 auto batchToSpace = network->AddBatchToSpaceNdLayer(batchToSpaceDesc, "batchToSpace");
200 const TensorInfo batchToSpaceInfo({ 1, 4, 6, 1 }, DataType::Float32);
201 batchToSpace->GetOutputSlot(0).SetTensorInfo(batchToSpaceInfo);
202 permute->GetOutputSlot(0).Connect(batchToSpace->GetInputSlot(0));
203
204 auto output = network->AddOutputLayer(0, "output");
205 batchToSpace->GetOutputSlot(0).Connect(output->GetInputSlot(0));
206
207 return network;
208 }
209
210 /// Tests that a optimization performed by PermuteAndBatchToSpaceAsDepthToSpace does not change the behaviour
211 /// of the network (i.e. it still produces the correct output).
212 TEST_CASE("PermuteAndBatchToSpaceAsDepthToSpaceCorrectnessTest")
213 {
214 INetworkPtr network = CreateTestNetwork();
215
216 IRuntimePtr runtime = IRuntime::Create(IRuntime::CreationOptions());
217 IOptimizedNetworkPtr optimizedNetwork = Optimize(*network, { Compute::CpuRef }, runtime->GetDeviceSpec());
218
219 // Confirm that the optimization has actually taken place
220 const Graph& optGraph = GetGraphForTesting(optimizedNetwork.get());
221 CHECK(CheckSequence(optGraph.cbegin(), optGraph.cend(), &IsLayerOfType<InputLayer>,
222 &IsLayerOfType<DepthToSpaceLayer>, &IsLayerOfType<OutputLayer>));
223
224 // Load the graph into a runtime so we can check it produces the correct output
225 NetworkId netId;
226 runtime->LoadNetwork(netId, std::move(optimizedNetwork));
227
228 std::vector<float> inputData{
229 // Each row here is a row of pixels where each pixel has 4 channels
230 // clang-format off
231 1.0f, 2.0f, 3.0f, 4.0f, 10.0f, 20.0f, 30.0f, 40.0f, 100.0f, 200.0f, 300.0f, 400.0f,
232 -1.0f, -2.0f, -3.0f, -4.0f, -10.0f, -20.0f, -30.0f, -40.0f, -100.0f, -200.0f, -300.0f, -400.0f,
233 // clang-format on
234 };
235 ConstTensor input(TensorInfo({ 1, 2, 3, 4 }, DataType::Float32, 0.0f, 0, true), inputData);
236 InputTensors inputs = { { 0, input } };
237 std::vector<float> outputData(4 * 6);
238 Tensor output(TensorInfo({ 1, 4, 6, 1 }, DataType::Float32), outputData.data());
239 OutputTensors outputs = { { 0, output } };
240 runtime->EnqueueWorkload(netId, inputs, outputs);
241
242 // Check the output is as expected.
243 // Note this output has been generated by running the network *without* the optimization.
244 std::vector<float> expectedOutput = {
245 // Rows and columns here match exactly with the tensor, as there is only 1 channel.
246 // clang-format off
247 1.0f, 2.0f, 10.0f, 20.0f, 100.0f, 200.0f,
248 3.0f, 4.0f, 30.0f, 40.0f, 300.0f, 400.0f,
249
250 -1.0f, -2.0f, -10.0f, -20.0f, -100.0f, -200.0f,
251 -3.0f, -4.0f, -30.0f, -40.0f, -300.0f, -400.0f,
252 // clang-format on
253 };
254 CHECK(outputData == expectedOutput);
255 }
256
257 /// Tests that a optimization performed by PermuteAndBatchToSpaceAsDepthToSpace does not change the behaviour
258 /// of the network (i.e. it still produces the correct output).
259 TEST_CASE("TransposeAndBatchToSpaceAsDepthToSpaceCorrectnessTest")
260 {
261 INetworkPtr network = CreateTransposeTestNetwork();
262
263 IRuntimePtr runtime = IRuntime::Create(IRuntime::CreationOptions());
264 IOptimizedNetworkPtr optimizedNetwork = Optimize(*network, { Compute::CpuRef }, runtime->GetDeviceSpec());
265
266 // Confirm that the optimization has actually taken place
267 const Graph& optGraph = GetGraphForTesting(optimizedNetwork.get());
268 CHECK(CheckSequence(optGraph.cbegin(), optGraph.cend(), &IsLayerOfType<InputLayer>,
269 &IsLayerOfType<DepthToSpaceLayer>, &IsLayerOfType<OutputLayer>));
270
271 // Load the graph into a runtime so we can check it produces the correct output
272 NetworkId netId;
273 runtime->LoadNetwork(netId, std::move(optimizedNetwork));
274
275 std::vector<float> inputData{
276 // Each row here is a row of pixels where each pixel has 4 channels
277 // clang-format off
278 1.0f, 2.0f, 3.0f, 4.0f, 10.0f, 20.0f, 30.0f, 40.0f, 100.0f, 200.0f, 300.0f, 400.0f,
279 -1.0f, -2.0f, -3.0f, -4.0f, -10.0f, -20.0f, -30.0f, -40.0f, -100.0f, -200.0f, -300.0f, -400.0f,
280 // clang-format on
281 };
282 ConstTensor input(TensorInfo({ 1, 2, 3, 4 }, DataType::Float32, 0.0f, 0, true), inputData);
283 InputTensors inputs = { { 0, input } };
284 std::vector<float> outputData(4 * 6);
285 Tensor output(TensorInfo({ 1, 4, 6, 1 }, DataType::Float32), outputData.data());
286 OutputTensors outputs = { { 0, output } };
287 runtime->EnqueueWorkload(netId, inputs, outputs);
288
289 // Check the output is as expected.
290 // Note this output has been generated by running the network *without* the optimization.
291 std::vector<float> expectedOutput = {
292 // Rows and columns here match exactly with the tensor, as there is only 1 channel.
293 // clang-format off
294 1.0f, 2.0f, 10.0f, 20.0f, 100.0f, 200.0f,
295 3.0f, 4.0f, 30.0f, 40.0f, 300.0f, 400.0f,
296
297 -1.0f, -2.0f, -10.0f, -20.0f, -100.0f, -200.0f,
298 -3.0f, -4.0f, -30.0f, -40.0f, -300.0f, -400.0f,
299 // clang-format on
300 };
301 CHECK(outputData == expectedOutput);
302 }
303 #endif
304
305 }