Release 18.08
[platform/upstream/armnn.git] / src / armnn / LoadedNetwork.cpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // See LICENSE file in the project root for full license information.
4 //
5
6 #include "LoadedNetwork.hpp"
7 #include "Layer.hpp"
8 #include "Graph.hpp"
9 #include "Network.hpp"
10 #include "Runtime.hpp"
11 #include "Profiling.hpp"
12 #include "HeapProfiling.hpp"
13
14 #ifdef ARMCOMPUTECL_ENABLED
15 #include <arm_compute/core/CL/OpenCL.h>
16 #endif
17
18 #include <backends/CpuTensorHandle.hpp>
19
20 #include <boost/polymorphic_cast.hpp>
21 #include <boost/assert.hpp>
22 #include <boost/format.hpp>
23 #include <boost/log/trivial.hpp>
24
25 namespace armnn
26 {
27
28 using namespace std;
29
30 namespace
31 {
32
33 template <typename ExceptionType>
34 std::string ToErrorMessage(const char * prefix, const ExceptionType & error)
35 {
36     std::stringstream ss;
37     ss << prefix << " " << error.what();
38     return ss.str();
39 }
40
41 #if ARMCOMPUTECL_ENABLED
42 std::string ToErrorMessage(const char * prefix, const cl::Error& error)
43 {
44     std::stringstream ss;
45     ss << prefix << " " << error.what() << ".  CL error code is: " << error.err();
46     return ss.str();
47 }
48 #endif
49
50 } // anonymous
51
52 std::unique_ptr<LoadedNetwork> LoadedNetwork::MakeLoadedNetwork(std::unique_ptr<OptimizedNetwork> net,
53                                                                 std::string & errorMessage)
54 {
55     std::unique_ptr<LoadedNetwork> loadedNetwork;
56
57     try
58     {
59         loadedNetwork.reset(new LoadedNetwork(std::move(net)));
60     }
61     catch (const std::runtime_error& error)
62     {
63         errorMessage = ToErrorMessage("An error occurred when preparing the network workloads: ", error);
64         BOOST_LOG_TRIVIAL(error) << errorMessage;
65         return std::unique_ptr<LoadedNetwork>();
66     }
67     catch (const armnn::Exception& error)
68     {
69         errorMessage = ToErrorMessage("An error occurred when preparing the network workloads: ", error);
70         BOOST_LOG_TRIVIAL(error) << errorMessage;
71         return std::unique_ptr<LoadedNetwork>();
72     }
73 #if ARMCOMPUTECL_ENABLED
74     catch (const cl::Error& error)
75     {
76         errorMessage = ToErrorMessage("A CL error occurred attempting to prepare a network workload: ", error);
77         BOOST_LOG_TRIVIAL(error) << errorMessage;
78         return std::unique_ptr<LoadedNetwork>();
79     }
80 #endif
81
82     return loadedNetwork;
83 }
84
85 LoadedNetwork::LoadedNetwork(std::unique_ptr<OptimizedNetwork> net)
86     : m_CpuRef()
87     , m_OptimizedNetwork(std::move(net))
88 {
89     // Create a profiler and register it for the current thread.
90     m_Profiler = std::make_shared<Profiler>();
91     ProfilerManager::GetInstance().RegisterProfiler(m_Profiler.get());
92
93     Graph& order = m_OptimizedNetwork->GetGraph().TopologicalSort();
94     //First create tensor handlers.
95     //Handlers are created before workloads are.
96     //Because workload creation can modify some of the handlers,
97     //(for example the splitter and merger layers).
98     for (auto&& layer : order)
99     {
100         layer->CreateTensorHandles(m_OptimizedNetwork->GetGraph(), GetWorkloadFactory(*layer));
101     }
102
103     //Then create workloads.
104     for (auto&& layer : order)
105     {
106         const IWorkloadFactory& workloadFactory = GetWorkloadFactory(*layer);
107
108         switch (layer->GetType())
109         {
110         case LayerType::Input:
111         case LayerType::Output:
112             {
113                 // Inputs and outputs are treated in a special way - see EnqueueInput() and EnqueueOutput().
114                 break;
115             }
116         default:
117             {
118                 auto workload = layer->CreateWorkload(m_OptimizedNetwork->GetGraph(), workloadFactory);
119
120                 if (!workload)
121                 {
122                     const char* const layerName = layer->GetNameStr().length() != 0 ? layer->GetName() : "<Unnamed>";
123                     throw InvalidArgumentException(boost::str(
124                         boost::format("No workload created for layer (name: '%1%' type: '%2%') (compute '%3%')")
125                         % layerName % static_cast<int>(layer->GetType()) % layer->GetComputeDevice()
126                     ));
127                 }
128
129                 m_WorkloadQueue.push_back(move(workload));
130                 // release the constant data in the layer..
131                 layer->ReleaseConstantData();
132                 break;
133             }
134         }
135     }
136
137     // Set up memory.
138     m_OptimizedNetwork->GetGraph().AllocateDynamicBuffers();
139
140     // Finalize the workload factories before execution.
141     m_CpuRef.Finalize();
142     m_CpuAcc.Finalize();
143     m_GpuAcc.Finalize();
144 }
145
146 TensorInfo LoadedNetwork::GetInputTensorInfo(LayerBindingId layerId) const
147 {
148     for (auto&& inputLayer : m_OptimizedNetwork->GetGraph().GetInputLayers())
149     {
150         BOOST_ASSERT_MSG(inputLayer->GetNumOutputSlots() == 1, "Input layer should have exactly 1 output slot");
151         if (inputLayer->GetBindingId() == layerId)
152         {
153             return inputLayer->GetOutputSlot(0).GetTensorInfo();
154         }
155     }
156
157     throw InvalidArgumentException(boost::str(boost::format("No input layer is associated with id %1%") % layerId));
158 }
159
160 TensorInfo LoadedNetwork::GetOutputTensorInfo(LayerBindingId layerId) const
161 {
162     for (auto&& outputLayer : m_OptimizedNetwork->GetGraph().GetOutputLayers())
163     {
164         BOOST_ASSERT_MSG(outputLayer->GetNumInputSlots() == 1, "Output layer should have exactly 1 input slot");
165         BOOST_ASSERT_MSG(outputLayer->GetInputSlot(0).GetConnection(), "Input slot on Output layer must be connected");
166         if (outputLayer->GetBindingId() == layerId)
167         {
168             return outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo();
169         }
170     }
171
172     throw InvalidArgumentException(boost::str(boost::format("No output layer is associated with id %1%") % layerId));
173 }
174
175 const IWorkloadFactory& LoadedNetwork::GetWorkloadFactory(const Layer& layer) const
176 {
177     const IWorkloadFactory* workloadFactory = nullptr;
178
179     switch (layer.GetComputeDevice())
180     {
181         case Compute::CpuAcc:
182         {
183             workloadFactory = &m_CpuAcc;
184             break;
185         }
186         case Compute::GpuAcc:
187         {
188             workloadFactory = &m_GpuAcc;
189             break;
190         }
191         case Compute::CpuRef:
192         {
193             workloadFactory = &m_CpuRef;
194             break;
195         }
196         default:
197         {
198             break;
199         }
200     }
201
202     BOOST_ASSERT_MSG(workloadFactory, "No workload factory");
203
204     std::string reasonIfUnsupported;
205     BOOST_ASSERT_MSG(IWorkloadFactory::IsLayerSupported(layer, {}, reasonIfUnsupported),
206                      "Factory does not support layer");
207     boost::ignore_unused(reasonIfUnsupported);
208
209     return *workloadFactory;
210 }
211
212 namespace {
213
214 // Non-copyable class owning accelerator-specific tensor data.
215 class TensorPin
216 {
217 public:
218     TensorPin(std::unique_ptr<ITensorHandle> handle, const TensorInfo& info, LayerBindingId id)
219         : m_TensorHandle(std::move(handle))
220         , m_TensorInfo(info)
221         , m_Id(id)
222     {
223     }
224
225     ITensorHandle* GetTensorHandle() const { return m_TensorHandle.get(); }
226     const TensorInfo& GetTensorInfo() const { return m_TensorInfo; }
227     LayerBindingId GetBindingId() const { return m_Id; }
228
229 private:
230     std::unique_ptr<ITensorHandle> m_TensorHandle;
231     TensorInfo m_TensorInfo;
232     LayerBindingId m_Id;
233 };
234
235 static const TensorPin& GetTensorPin(LayerBindingId id,
236     const std::vector<TensorPin>& pins,
237     char const* bindingPointDesc)
238 {
239     auto it = std::find_if(pins.begin(), pins.end(),
240         [id](const TensorPin& pin)
241     {
242         return pin.GetBindingId() == id;
243     });
244
245     if (it != pins.end())
246     {
247         return *it;
248     }
249     else
250     {
251         throw InvalidArgumentException(boost::str(
252             boost::format("No tensor supplied for %1% %2%") % bindingPointDesc % id));
253     }
254 }
255
256 // Stores data that needs to be kept accessible for the entire execution of a workload.
257 class WorkloadData
258 {
259 public:
260     WorkloadData(const InputTensors& inputTensors, const OutputTensors& outputTensors)
261     {
262         m_InputTensorPins.reserve(inputTensors.size());
263         m_OutputTensorPins.reserve(outputTensors.size());
264
265         for (auto inputTensorPair : inputTensors)
266         {
267             auto inputTensor = inputTensorPair.second;
268
269             std::unique_ptr<ITensorHandle> tensorHandle =
270                 std::make_unique<ConstPassthroughCpuTensorHandle>(inputTensor.GetInfo(),inputTensor.GetMemoryArea());
271             LayerBindingId layerId = inputTensorPair.first;
272
273             m_InputTensorPins.emplace_back(std::move(tensorHandle), inputTensor.GetInfo(), layerId);
274         }
275
276         for (auto outputTensorPair : outputTensors)
277         {
278             auto outputTensor = outputTensorPair.second;
279
280             std::unique_ptr<ITensorHandle> tensorHandle =
281                 std::make_unique<PassthroughCpuTensorHandle>(outputTensor.GetInfo(), outputTensor.GetMemoryArea());
282             LayerBindingId layerId = outputTensorPair.first;
283
284             m_OutputTensorPins.emplace_back(std::move(tensorHandle), outputTensor.GetInfo(), layerId);
285         }
286     }
287
288     const TensorPin& GetInputTensorPin(LayerBindingId id) const
289     {
290         return GetTensorPin(id, m_InputTensorPins, "input");
291     }
292
293     const TensorPin& GetOutputTensorPin(LayerBindingId id) const
294     {
295         return GetTensorPin(id, m_OutputTensorPins, "output");
296     }
297
298 private:
299
300     std::vector<TensorPin> m_InputTensorPins;
301     std::vector<TensorPin> m_OutputTensorPins;
302 };
303
304 }
305
306 Status LoadedNetwork::EnqueueWorkload(const InputTensors& inputTensors,
307                                       const OutputTensors& outputTensors)
308 {
309     ARMNN_SCOPED_PROFILING_EVENT(Compute::Undefined, "EnqueueWorkload");
310
311     const Graph& graph = m_OptimizedNetwork->GetGraph();
312
313     // Walk graph to determine the order of execution.
314     if (graph.GetNumLayers() < 2)
315     {
316         BOOST_LOG_TRIVIAL(warning) << "IRuntime::EnqueueWorkload()::Less than two nodes in graph";
317         return Status::Failure;
318     }
319
320     // Data that must be kept alive for the entire execution of the workload.
321     WorkloadData workloadData(inputTensors, outputTensors);
322
323     if (graph.GetNumInputs() != inputTensors.size())
324     {
325         throw InvalidArgumentException("Number of inputs provided does not match network.");
326     }
327
328     // For each input to the network, call EnqueueInput with the data passed by the user.
329     for (const BindableLayer* inputLayer : graph.GetInputLayers())
330     {
331         const TensorPin& pin = workloadData.GetInputTensorPin(inputLayer->GetBindingId());
332         EnqueueInput(*inputLayer, pin.GetTensorHandle(), pin.GetTensorInfo());
333     }
334
335     // For each output to the network, call EnqueueOutput with the data passed by the user.
336     for (const BindableLayer* outputLayer : graph.GetOutputLayers())
337     {
338         const TensorPin& pin = workloadData.GetOutputTensorPin(outputLayer->GetBindingId());
339         EnqueueOutput(*outputLayer, pin.GetTensorHandle(), pin.GetTensorInfo());
340     }
341
342     bool executionSucceeded = true;
343
344     {
345         ARMNN_SCOPED_PROFILING_EVENT(Compute::Undefined, "Execute");
346         ARMNN_SCOPED_HEAP_PROFILING("Executing");
347         executionSucceeded = Execute();
348     }
349
350     // Hack: get rid of inputs and outputs we added.
351     TidyWorkloadQueue(graph.GetNumInputs(), graph.GetNumOutputs());
352
353     return executionSucceeded ? Status::Success : Status::Failure;
354 }
355
356 void LoadedNetwork::EnqueueInput(const BindableLayer& layer, ITensorHandle* tensorHandle, const TensorInfo& tensorInfo)
357 {
358     if (layer.GetType() != LayerType::Input)
359     {
360         throw InvalidArgumentException("EnqueueInput: given layer not an InputLayer");
361     }
362
363     if (tensorHandle == nullptr)
364     {
365         throw InvalidArgumentException("EnqueueInput: tensorHandle must not be NULL");
366     }
367
368     InputQueueDescriptor inputQueueDescriptor;
369     WorkloadInfo info;
370
371     inputQueueDescriptor.m_Inputs.push_back(tensorHandle);
372     info.m_InputTensorInfos.push_back(tensorInfo);
373
374     BOOST_ASSERT_MSG(layer.GetNumOutputSlots() == 1, "Can only handle Input Layer with one output");
375     const OutputHandler& handler = layer.GetOutputHandler();
376     const TensorInfo& outputTensorInfo = handler.GetTensorInfo();
377     ITensorHandle* outputTensorHandle = handler.GetData();
378     BOOST_ASSERT_MSG(outputTensorHandle != nullptr,
379                      "Data should have been allocated.");
380     inputQueueDescriptor.m_Outputs.push_back(outputTensorHandle);
381     info.m_OutputTensorInfos.push_back(outputTensorInfo);
382
383     const IWorkloadFactory& workloadFactory = GetWorkloadFactory(layer);
384     auto inputWorkload = workloadFactory.CreateInput(inputQueueDescriptor, info);
385     BOOST_ASSERT_MSG(inputWorkload, "No input workload created");
386     m_WorkloadQueue.insert(m_WorkloadQueue.begin(), move(inputWorkload));
387 }
388
389 void LoadedNetwork::EnqueueOutput(const BindableLayer& layer, ITensorHandle* tensorHandle, const TensorInfo& tensorInfo)
390 {
391     if (layer.GetType() != LayerType::Output)
392     {
393         throw InvalidArgumentException("EnqueueOutput: given layer not an OutputLayer");
394     }
395
396     if (tensorHandle == nullptr)
397     {
398         throw InvalidArgumentException("EnqueueOutput: tensorHandle must not be NULL");
399     }
400
401     OutputQueueDescriptor outputQueueDescriptor;
402     WorkloadInfo info;
403
404     outputQueueDescriptor.m_Outputs.push_back(tensorHandle);
405     info.m_OutputTensorInfos.push_back(tensorInfo);
406
407     BOOST_ASSERT_MSG(layer.GetNumInputSlots() == 1, "Output Layer should have exactly one input.");
408
409     // Gets the output handler from the previous node.
410     const OutputHandler& outputHandler = layer.GetInputSlots()[0].GetConnectedOutputSlot()->GetOutputHandler();
411
412     const TensorInfo& inputTensorInfo = outputHandler.GetTensorInfo();
413     ITensorHandle* inputTensorHandle = outputHandler.GetData();
414     BOOST_ASSERT_MSG(inputTensorHandle != nullptr, "Data should have been allocated.");
415
416     outputQueueDescriptor.m_Inputs.push_back(inputTensorHandle);
417     info.m_InputTensorInfos.push_back(inputTensorInfo);
418
419     const IWorkloadFactory& workloadFactory = GetWorkloadFactory(layer);
420     auto outputWorkload = workloadFactory.CreateOutput(outputQueueDescriptor, info);
421     BOOST_ASSERT_MSG(outputWorkload, "No output workload created");
422     m_WorkloadQueue.push_back(move(outputWorkload));
423 }
424
425 bool LoadedNetwork::Execute()
426 {
427     bool success = true;
428
429     m_CpuRef.Acquire();
430     m_CpuAcc.Acquire();
431     m_GpuAcc.Acquire();
432
433     try
434     {
435         for (size_t i = 0; i < m_WorkloadQueue.size(); ++i)
436         {
437             m_WorkloadQueue[i]->Execute();
438         }
439     }
440 #if ARMCOMPUTECL_ENABLED
441     catch (const cl::Error& error)
442     {
443         BOOST_LOG_TRIVIAL(error) << "A CL error occurred attempting to execute a workload: "
444             << error.what() << ". CL error code is: " << error.err();
445         success = false;
446     }
447 #endif
448     catch (const std::runtime_error& error)
449     {
450         BOOST_LOG_TRIVIAL(error) << "An error occurred attempting to execute a workload: " << error.what();
451         success = false;
452     }
453
454     // Informs the memory managers to release memory in it's respective memory group
455     m_CpuRef.Release();
456     m_CpuAcc.Release();
457     m_GpuAcc.Release();
458
459     return success;
460 }
461
462 void LoadedNetwork::TidyWorkloadQueue(size_t numInputs, size_t numOutputs)
463 {
464     m_WorkloadQueue.erase(m_WorkloadQueue.begin(), m_WorkloadQueue.begin() + boost::numeric_cast<long>(numInputs));
465     m_WorkloadQueue.erase(m_WorkloadQueue.end() - boost::numeric_cast<long>(numOutputs), m_WorkloadQueue.end());
466 }
467
468 }