• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }