ArmNN
 22.02
Graph.hpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #pragma once
6 
7 #include "LayersFwd.hpp"
8 #include "IGraphObservable.hpp"
9 #include "Profiling.hpp"
10 
11 #include <armnn/Types.hpp>
12 #include <armnn/TensorFwd.hpp>
13 #include <armnn/NetworkFwd.hpp>
14 #include <armnn/Exceptions.hpp>
15 #include <armnn/utility/Assert.hpp>
18 
19 #include <list>
20 #include <map>
21 #include <unordered_map>
22 #include <unordered_set>
23 #include <vector>
24 
25 namespace armnn
26 {
27 
28 class SubgraphView;
29 
30 class Graph
31 {
32 public:
33  template <typename LayerType>
34  static LayerType* PtrCast(Layer* const layer)
35  {
36  return PolymorphicDowncast<LayerType*>(layer);
37  }
38 
39  template <typename Func>
40  void ForEachLayer(Func func) const
41  {
42  for (auto it = m_Layers.begin(); it != m_Layers.end(); )
43  {
44  auto next = std::next(it);
45  func(*it);
46  it = next;
47  }
48  }
49 
50  using LayerList = std::list<Layer*>;
51 
52  // Const so pointers in the list can't be modified externally.
53  using Iterator = LayerList::const_iterator;
54  using IteratorDifference = Iterator::difference_type;
55 
59 
60  /// Wrapper class returned by Graph::GetInputLayers()
62  {
63  explicit InputLayersAccessor(const Graph& graph) : m_Graph(graph) {}
64 
66  {
67  return { m_Graph.m_Layers.begin(), &(PtrCast<const InputLayer>) };
68  }
69 
71  {
72  return { std::next(m_Graph.m_Layers.begin(), static_cast<IteratorDifference>(m_Graph.GetNumInputs())),
73  &(PtrCast<const InputLayer>) };
74  }
75 
76  const Graph& m_Graph;
77  };
78 
79  /// Wrapper class returned by Graph::GetOutputLayers()
81  {
82  explicit OutputLayersAccessor(const Graph& graph) : m_Graph(graph) {}
83 
85  {
86  return { std::prev(m_Graph.m_Layers.end(), static_cast<IteratorDifference>(m_Graph.GetNumOutputs())),
87  &(PtrCast<const OutputLayer>) };
88  }
89 
91  {
92  return { m_Graph.m_Layers.end(), &(PtrCast<const OutputLayer>) };
93  }
94 
95  const Graph& m_Graph;
96  };
97 
98  Graph(bool shapeInferenceMethod = false)
99  : m_LayersInOrder(true)
100  , m_ShapeInferenceMethod(shapeInferenceMethod ? ShapeInferenceMethod::InferAndValidate :
102  , m_Profiler(std::make_shared<IProfiler>())
103  {}
104 
105  Graph(const Graph& other);
106 
107  Graph& operator=(const Graph& other) = delete;
108 
109  Graph(Graph&& other)
110  {
111  *this = std::move(other);
112  }
113 
115  {
116  m_InputIds = std::move(other.m_InputIds);
117  m_OutputIds = std::move(other.m_OutputIds);
118  m_LayersInOrder = std::move(other.m_LayersInOrder);
119  m_Views = std::move(other.m_Views);
120  m_Profiler = std::move(other.m_Profiler);
121 
122  other.ForEachLayer([this](Layer* otherLayer)
123  {
124  otherLayer->Reparent(*this, m_Layers.end());
125  });
126 
127  ARMNN_ASSERT(other.m_PosInGraphMap.empty());
128  ARMNN_ASSERT(other.m_Layers.empty());
129 
130  return *this;
131  }
132 
134  {
135  ForEachLayer([](Layer* layer)
136  {
137  delete layer;
138  });
139  }
140 
141  Status Print() const;
142 
143  Status SerializeToDot(std::ostream& stream);
144 
145  /// Adds a new layer, of type LayerType, to the graph constructed with the arguments passed.
146  template <typename LayerT, typename... Args>
147  LayerT* AddLayer(Args&&... args);
148 
149  /// Inserts a new layer between the output slot currently connected to insertBefore
150  /// and insertBefore itself.
151  template <typename LayerT, typename... Args>
152  LayerT* InsertNewLayer(InputSlot& insertBefore, Args&&... args);
153 
154  /// Inserts a new layer between insertAfter and the input slot(s) currently connected to it
155  template <typename LayerT, typename... Args>
156  LayerT* InsertNewLayer(OutputSlot& insertAfter, Args&&... args);
157 
158  /// Deletes the layer at the specified position.
159  void EraseLayer(Iterator pos);
160 
161  /// Deletes the layer. Sets @a layer to nullptr on return.
162  /// Templated to support pointers to any layer type.
163  template <typename LayerT>
164  void EraseLayer(LayerT*& layer);
165 
166  /// Returns iterator pointing to the beginning of the list. Lowercase for range-based for loops.
167  Iterator begin() { return m_Layers.begin(); }
168  /// Returns iterator pointing to the end of the list. Lowercase for range-based for loops.
169  Iterator end() { return m_Layers.end(); }
170 
171  /// Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops.
172  ConstIterator begin() const { return {m_Layers.begin(), &(PtrCast<const Layer>)}; }
173  /// Returns const iterator pointing to the end of the list. Lowercase for range-based for loops.
174  ConstIterator end() const { return {m_Layers.end(), &(PtrCast<const Layer>)}; }
175 
176  /// Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops.
177  ConstIterator cbegin() const { return begin(); }
178  /// Returns const iterator pointing to the end of the list. Lowercase for range-based for loops.
179  ConstIterator cend() const { return end(); }
180 
181  /// Sorts layers in topological order and return this.
182  Graph& TopologicalSort() { const_cast<const Graph*>(this)->TopologicalSort(); return *this; }
183  const Graph& TopologicalSort() const;
184 
185  size_t GetNumInputs() const { return m_InputIds.size(); }
186  size_t GetNumOutputs() const { return m_OutputIds.size(); }
187 
188  /// Returns a wrapper object with begin(), end() methods to iterate over the input layers
189  /// in a range-based for loop.
191 
192  /// Returns a wrapper object with begin(), end() methods to iterate over the output layers
193  /// in a range-based for loop.
195 
196  size_t GetNumLayers() const { return m_Layers.size(); }
197 
198  /// Allocates memory for all tensors under output tensor handers of each layer.
200 
201  /// Modifies the graph in-place, removing edges connecting layers using different compute devices,
202  /// and relinking them via an intermediary copy layers.
203  void AddCompatibilityLayers(std::map<BackendId, std::unique_ptr<class IBackendInternal>>& backends,
204  TensorHandleFactoryRegistry& registry);
205 
206  /// Substitutes the given sub-graph with either a new layer or a new sub-graph.
207  /// In either case, the given layer or all the layers in the given sub-graph must belong to this graph.
208  void SubstituteSubgraph(SubgraphView& subgraph, IConnectableLayer* substituteLayer);
209  void SubstituteSubgraph(SubgraphView& subgraph, const SubgraphView& substituteSubgraph);
210 
211  /// For each ConstantLayer in Graph, ensures TensorInfo is set on all output slots.
212  /// LayerValidationException thrown if no TensorInfo is set.
214 
215  void InferTensorInfos();
216 
217  void AttachObservable(IGraphObservable* const observable, GraphEvent notifyOnEvent) {
218  m_Views[notifyOnEvent].emplace_back(observable);
219  }
220 
221  void DetachObservable(IGraphObservable* const observable, GraphEvent notifyOnEvent) {
222  m_Views[notifyOnEvent].remove(observable);
223  }
224 
225  /// Gets the position of a layer in the graph.
226  Iterator GetPosInGraph(Layer& layer);
227 
228  const std::shared_ptr<IProfiler>& GetProfiler() const;
229 
230 private:
231  template <typename LayerT>
232  class LayerInGraphBase;
233 
234  template <typename LayerT>
235  class LayerInGraph;
236 
237  Iterator ForwardToEndOfInputs(Iterator it) const
238  {
239  while ((it != m_Layers.end()) && ((*it)->GetType() == LayerType::Input))
240  {
241  ++it;
242  }
243  return it;
244  }
245 
246  Iterator RewindToBeginOfOutputs(Iterator it) const
247  {
248  while ((it != m_Layers.begin()) && ((*std::prev(it))->GetType() == LayerType::Output))
249  {
250  --it;
251  }
252  return it;
253  }
254 
255  void NotifyObservables(GraphEvent event, Layer* graphState)
256  {
257  // Iterate over all observables observing this event
258  for (auto& observable : m_Views[event])
259  {
260  observable->Update(graphState);
261  }
262  }
263 
264  std::unordered_set<LayerBindingId> m_InputIds;
265  std::unordered_set<LayerBindingId> m_OutputIds;
266  std::unordered_map<const Layer*, Iterator> m_PosInGraphMap;
267 
268  void ReplaceSubgraphConnections(const SubgraphView& subgraph, const SubgraphView& substituteSubgraph);
269  void EraseSubgraphLayers(SubgraphView &subgraph);
270 
271  /// Mutable to allow sorting on const object.
272  mutable LayerList m_Layers;
273  mutable bool m_LayersInOrder;
274 
275  std::map<const GraphEvent, std::list<IGraphObservable*>> m_Views;
276  ShapeInferenceMethod m_ShapeInferenceMethod;
277  std::shared_ptr<IProfiler> m_Profiler;
278 
279  // Throws exception due to a layer input not being connected to an output slot.
280  /// Also verifies weights and bias are set for FullyConnected layers.
281  void ConstructErrorMessageForUnconnectedInputs(Layer* const layer,
282  unsigned int slotIndex);
283 
284  friend class SubgraphView;
285 };
286 
287 /// Common base class for layers in the graph.
288 template <typename LayerT>
289 class Graph::LayerInGraphBase : public LayerT
290 {
291 protected:
292  template <typename... Args>
293  LayerInGraphBase(Graph& graph, Iterator insertBefore, Args&&... args)
294  : LayerT(std::forward<Args>(args)...), m_Graph(&graph)
295  {
296  Insert(*m_Graph, insertBefore);
297  }
298  ~LayerInGraphBase()
299  {
300  Remove(*m_Graph);
301  }
302 
303  void Reparent(Graph& destGraph, Iterator insertBefore) override
304  {
305  Insert(destGraph, insertBefore);
306  Remove(*m_Graph);
307 
308  m_Graph = &destGraph;
309  }
310 
311 private:
312  void Insert(Graph& graph, Iterator insertBefore)
313  {
314  graph.m_PosInGraphMap.emplace(this, graph.m_Layers.emplace(insertBefore, this));
315  }
316 
317  void Remove(Graph& graph)
318  {
319  auto layerIt = graph.GetPosInGraph(*this);
320  graph.m_Layers.erase(layerIt);
321 
322  const size_t numErased = graph.m_PosInGraphMap.erase(this);
323  IgnoreUnused(numErased);
324  ARMNN_ASSERT(numErased == 1);
325  }
326 
327 protected:
328  Graph* m_Graph;
329 };
330 
331 /// Input/Output layers specialize this template.
332 template <typename LayerT>
333 class Graph::LayerInGraph final : public LayerInGraphBase<LayerT>
334 {
335 public:
336  template <typename... Args>
337  LayerInGraph(Graph& graph, Args&&... args)
338  : LayerInGraphBase<LayerT>(graph,
339  // Insert at the back of the intermediate layers (before outputs).
340  std::prev(graph.end(), IteratorDifference(graph.GetNumOutputs())),
341  std::forward<Args>(args)...)
342  {
343  }
344  template <typename... Args>
345  LayerInGraph(Graph& graph, Iterator insertBefore, Args&&... args)
346  : LayerInGraphBase<LayerT>(graph,
347  // Make sure it's inserted after all inputs and before all outputs.
348  graph.ForwardToEndOfInputs(graph.RewindToBeginOfOutputs(insertBefore)),
349  std::forward<Args>(args)...)
350  {
351  }
352 };
353 
354 /// Inputs add/remove their binding id to m_InputIds in the graph.
355 template <>
356 class Graph::LayerInGraph<InputLayer> final : public LayerInGraphBase<InputLayer>
357 {
358 public:
359  template <typename... Args>
360  LayerInGraph(Graph& graph, Args&&... args)
361  : LayerInGraphBase<InputLayer>(graph,
362  // Always add to the back of the inputs.
363  std::next(graph.begin(), IteratorDifference(graph.GetNumInputs())),
364  std::forward<Args>(args)...)
365  {
366  const bool isNewId = m_Graph->m_InputIds.emplace(GetBindingId()).second;
367  if (!isNewId)
368  {
369  throw InvalidArgumentException("A layer already exists with the specified id");
370  }
371  }
372  template <typename... Args>
373  LayerInGraph(Graph& graph, Iterator, Args&&... args)
374  // Ignore Iterator argument. Always add to the back of the inputs.
375  : LayerInGraph(graph, std::forward<Args>(args)...)
376  {
377  }
378  ~LayerInGraph() override
379  {
380  const size_t numErased = m_Graph->m_InputIds.erase(GetBindingId());
381  IgnoreUnused(numErased);
382  ARMNN_ASSERT(numErased == 1);
383  }
384 };
385 
386 /// Outputs add/remove their binding id to m_OutputIds in the graph.
387 template <>
388 class Graph::LayerInGraph<OutputLayer> final : public LayerInGraphBase<OutputLayer>
389 {
390 public:
391  template <typename... Args>
392  LayerInGraph(Graph& graph, Args&&... args)
393  : LayerInGraphBase<OutputLayer>(graph,
394  // Always add to the back of the outputs.
395  graph.end(),
396  std::forward<Args>(args)...)
397  {
398  const bool isNewId = m_Graph->m_OutputIds.emplace(GetBindingId()).second;
399  if (!isNewId)
400  {
401  throw InvalidArgumentException("A layer already exists with the specified id");
402  }
403  }
404  ~LayerInGraph() override
405  {
406  const size_t numErased = m_Graph->m_OutputIds.erase(GetBindingId());
407  IgnoreUnused(numErased);
408  ARMNN_ASSERT(numErased == 1);
409  }
410 };
411 
413 {
414  auto it = m_PosInGraphMap.find(&layer);
415  ARMNN_ASSERT(it != m_PosInGraphMap.end());
416  return it->second;
417 }
418 
419 template <typename LayerT, typename... Args>
420 inline LayerT* Graph::AddLayer(Args&&... args)
421 {
422  m_LayersInOrder = m_LayersInOrder &&
423  ((LayerEnumOf<LayerT>() == LayerType::Input) || (LayerEnumOf<LayerT>() == LayerType::Output));
424  LayerT* const layer = new LayerInGraph<LayerT>(*this, std::forward<Args>(args)...);
425 
426  layer->SetShapeInferenceMethod(m_ShapeInferenceMethod);
427 
428  NotifyObservables(GraphEvent::LayerAdded, layer);
429 
430  return layer;
431 }
432 
433 template <typename LayerT, typename... Args>
434 inline LayerT* Graph::InsertNewLayer(InputSlot& insertBefore, Args&&... args)
435 {
436  // Insert after the parent if any, or before the child otherwise, so the topological order is kept.
437  OutputSlot* parentOut = insertBefore.GetConnectedOutputSlot();
438  const Iterator pos = (parentOut != nullptr)
439  ? std::next(GetPosInGraph(parentOut->GetOwningLayer()))
440  : GetPosInGraph(insertBefore.GetOwningLayer());
441  LayerT* const layer = new LayerInGraph<LayerT>(*this, pos, std::forward<Args>(args)...);
442  insertBefore.Insert(*layer);
443 
444  NotifyObservables(GraphEvent::LayerAdded, layer);
445 
446  return layer;
447 }
448 
449 template <typename LayerT, typename... Args>
450 inline LayerT* Graph::InsertNewLayer(OutputSlot& insertAfter, Args&&... args)
451 {
452  Layer& owningLayer = insertAfter.GetOwningLayer();
453 
454  const Iterator pos = std::next(GetPosInGraph(owningLayer));
455  LayerT* const layer = new LayerInGraph<LayerT>(*this, pos, std::forward<Args>(args)...);
456 
457  ARMNN_ASSERT(layer->GetNumInputSlots() == 1);
458 
459  insertAfter.MoveAllConnections(layer->GetOutputSlot());
460  insertAfter.Connect(layer->GetInputSlot(0));
461 
462  NotifyObservables(GraphEvent::LayerAdded, layer);
463 
464  return layer;
465 }
466 
467 inline void Graph::EraseLayer(Iterator pos)
468 {
469  NotifyObservables(GraphEvent::LayerErased, *pos);
470 
471  delete *pos;
472 }
473 
474 template <typename LayerT>
475 inline void Graph::EraseLayer(LayerT*& layer)
476 {
477  ARMNN_ASSERT(layer != nullptr);
478  EraseLayer(GetPosInGraph(*layer));
479  layer = nullptr;
480 }
481 
482 } // namespace armnn
Graph & operator=(const Graph &other)=delete
Iterator begin()
Returns iterator pointing to the beginning of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:167
Graph(bool shapeInferenceMethod=false)
Definition: Graph.hpp:98
void Insert(Layer &layer)
Definition: Layer.cpp:23
ConstIteratorInputs begin() const
Definition: Graph.hpp:65
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:66
Status SerializeToDot(std::ostream &stream)
Definition: Graph.cpp:118
void AttachObservable(IGraphObservable *const observable, GraphEvent notifyOnEvent)
Definition: Graph.hpp:217
virtual void Reparent(Graph &dest, std::list< Layer *>::const_iterator iterator)=0
LayerT * AddLayer(Args &&... args)
Adds a new layer, of type LayerType, to the graph constructed with the arguments passed.
Definition: Graph.hpp:420
LayerInGraph(Graph &graph, Args &&... args)
Definition: Graph.hpp:392
ConstIterator cbegin() const
Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:177
Layer & GetOwningLayer() const
Definition: Layer.hpp:118
int Connect(InputSlot &destination)
Definition: Layer.cpp:86
LayerInGraph(Graph &graph, Iterator, Args &&... args)
Definition: Graph.hpp:373
void EraseLayer(Iterator pos)
Deletes the layer at the specified position.
Definition: Graph.hpp:467
static LayerType * PtrCast(Layer *const layer)
Definition: Graph.hpp:34
size_t GetNumOutputs() const
Definition: Graph.hpp:186
Copyright (c) 2021 ARM Limited and Contributors.
void IgnoreUnused(Ts &&...)
LayerList::const_iterator Iterator
Definition: Graph.hpp:53
OutputLayersAccessor(const Graph &graph)
Definition: Graph.hpp:82
ConstIteratorOutputs begin() const
Definition: Graph.hpp:84
The SubgraphView class represents a subgraph of a Graph.
Iterator GetPosInGraph(Layer &layer)
Gets the position of a layer in the graph.
Definition: Graph.hpp:412
A layer user-provided data can be bound to (e.g. inputs, outputs).
Definition: OutputLayer.hpp:13
void ForEachLayer(Func func) const
Definition: Graph.hpp:40
ConstIterator end() const
Returns const iterator pointing to the end of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:174
Wrapper class returned by Graph::GetInputLayers()
Definition: Graph.hpp:61
Validate all output shapes.
Status
enumeration
Definition: Types.hpp:29
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
const OutputSlot * GetConnectedOutputSlot() const
Definition: Layer.hpp:56
Iterator::difference_type IteratorDifference
Definition: Graph.hpp:54
Layer & GetOwningLayer() const
Definition: Layer.hpp:53
Wrapper class returned by Graph::GetOutputLayers()
Definition: Graph.hpp:80
InputLayersAccessor(const Graph &graph)
Definition: Graph.hpp:63
void VerifyConstantLayerSetTensorInfo() const
For each ConstantLayer in Graph, ensures TensorInfo is set on all output slots.
Definition: Graph.cpp:537
void SubstituteSubgraph(SubgraphView &subgraph, IConnectableLayer *substituteLayer)
Substitutes the given sub-graph with either a new layer or a new sub-graph.
Definition: Graph.cpp:433
const std::shared_ptr< IProfiler > & GetProfiler() const
Definition: Graph.cpp:643
OutputLayersAccessor GetOutputLayers() const
Returns a wrapper object with begin(), end() methods to iterate over the output layers in a range-bas...
Definition: Graph.hpp:194
Graph & operator=(Graph &&other)
Definition: Graph.hpp:114
void InferTensorInfos()
Definition: Graph.cpp:560
std::list< Layer * > LayerList
Definition: Graph.hpp:50
ConstIterator begin() const
Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:172
ConstIteratorOutputs end() const
Definition: Graph.hpp:90
A layer user-provided data can be bound to (e.g. inputs, outputs).
Definition: InputLayer.hpp:13
Iterator end()
Returns iterator pointing to the end of the list. Lowercase for range-based for loops.
Definition: Graph.hpp:169
Infer missing output shapes and validate all output shapes.
ConstIterator cend() const
Returns const iterator pointing to the end of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:179
Graph & TopologicalSort()
Sorts layers in topological order and return this.
Definition: Graph.hpp:182
InputLayersAccessor GetInputLayers() const
Returns a wrapper object with begin(), end() methods to iterate over the input layers in a range-base...
Definition: Graph.hpp:190
size_t GetNumLayers() const
Definition: Graph.hpp:196
LayerT * InsertNewLayer(InputSlot &insertBefore, Args &&... args)
Inserts a new layer between the output slot currently connected to insertBefore and insertBefore itse...
Definition: Graph.hpp:434
ConstIteratorInputs end() const
Definition: Graph.hpp:70
Status AllocateDynamicBuffers()
Allocates memory for all tensors under output tensor handers of each layer.
Definition: Graph.cpp:179
Graph(Graph &&other)
Definition: Graph.hpp:109
size_t GetNumInputs() const
Definition: Graph.hpp:185
ShapeInferenceMethod
The ShapeInferenceMethod modify how the output shapes are treated.
Definition: Types.hpp:208
void AddCompatibilityLayers(std::map< BackendId, std::unique_ptr< class IBackendInternal >> &backends, TensorHandleFactoryRegistry &registry)
Modifies the graph in-place, removing edges connecting layers using different compute devices...
Definition: Graph.cpp:301
Status Print() const
Definition: Graph.cpp:61
void DetachObservable(IGraphObservable *const observable, GraphEvent notifyOnEvent)
Definition: Graph.hpp:221
void MoveAllConnections(OutputSlot &destination)
Moves all connections to another OutputSlot.
Definition: Layer.cpp:119
LayerInGraph(Graph &graph, Args &&... args)
Definition: Graph.hpp:360
LayerType
When adding a new layer, adapt also the LastLayer enum value in the enum class LayerType below...
Definition: Types.hpp:458