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