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