ArmNN
 22.02
PermuteAndBatchToSpaceAsDepthToSpaceTests.cpp
Go to the documentation of this file.
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.
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.
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
98 
99  // Check that the replacement has been made as expected
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
128 
129  // Check that the replacement has been made as expected
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.
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.
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 
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 
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 }
TEST_SUITE("TestConstTensorLayerVisitor")
static IRuntimePtr Create(const CreationOptions &options)
Definition: Runtime.cpp:40
CPU Execution: Reference C++ kernels.
Optimizer::Optimizations MakeOptimizations(Args &&... args)
Definition: Optimizer.hpp:43
bool CheckSequence(const armnn::Graph::ConstIterator first, const armnn::Graph::ConstIterator last)
Definition: TestUtils.hpp:21
OptimizeForConnection< TransposeLayer, BatchToSpaceNdLayer, PermuteAndBatchToSpaceAsDepthToSpaceImpl< TransposeLayer > > TransposeAndBatchToSpaceAsDepthToSpace
ConstIterator cbegin() const
Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:177
static void Pass(Graph &graph, const Optimizations &optimizations)
Definition: Optimizer.cpp:16
std::unique_ptr< IRuntime, void(*)(IRuntime *runtime)> IRuntimePtr
Definition: IRuntime.hpp:31
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
std::vector< std::pair< LayerBindingId, class ConstTensor > > InputTensors
Definition: Tensor.hpp:392
Copyright (c) 2021 ARM Limited and Contributors.
A BatchToSpaceNdDescriptor for the BatchToSpaceNdLayer.
Private implementation of INetwork.
Definition: Network.hpp:31
A tensor defined by a TensorInfo (shape and data type) and a mutable backing store.
Definition: Tensor.hpp:319
OptimizeForConnection< PermuteLayer, BatchToSpaceNdLayer, PermuteAndBatchToSpaceAsDepthToSpaceImpl< PermuteLayer > > PermuteAndBatchToSpaceAsDepthToSpace
IOptimizedNetworkPtr Optimize(const INetwork &network, const std::vector< BackendId > &backendPreferences, const IDeviceSpec &deviceSpec, const OptimizerOptions &options=OptimizerOptions(), Optional< std::vector< std::string > &> messages=EmptyOptional())
Create an optimized version of the network.
Definition: Network.cpp:1680
std::vector< unsigned int > m_BlockShape
Block shape values.
int NetworkId
Definition: IRuntime.hpp:25
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
std::vector< std::pair< LayerBindingId, class Tensor > > OutputTensors
Definition: Tensor.hpp:393
std::unique_ptr< IOptimizedNetwork, void(*)(IOptimizedNetwork *network)> IOptimizedNetworkPtr
Definition: INetwork.hpp:242
Graph & GetGraphForTesting(IOptimizedNetwork *optNet)
Definition: TestUtils.cpp:47
A TransposeDescriptor for the TransposeLayer.
ConstIterator cend() const
Returns const iterator pointing to the end of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:179
armnn::INetworkPtr CreateTestNetwork(armnn::TensorInfo &inputTensorInfo)
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:241
static INetworkPtr Create(NetworkOptions networkOptions={})
Definition: Network.cpp:492
This layer represents a DepthToSpace operation.
A PermuteDescriptor for the PermuteLayer.