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