ArmNN
 24.08
SubgraphViewSelector.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017,2024 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
7 #include "Graph.hpp"
8 
12 
13 #include <algorithm>
14 #include <map>
15 #include <queue>
16 #include <unordered_set>
17 
18 namespace armnn
19 {
20 
21 namespace
22 {
23 
24 /// Intermediate data-structure to store the subgraph that a layer has been assigned to.
25 /// This is a "disjoint set" data structure that allows efficient merging of subgraphs,
26 /// which is a key part of the algorithm. Subgraphs are arranged in singly-linked trees
27 /// (with each node storing a pointer to its parent). Subgraphs in the same tree are considered
28 /// to have been merged. Merging subgraphs is performed by attaching one tree to another,
29 /// which is a simple pointer update.
30 ///
31 /// NOTE: Due to the way this is stored, it is almost never correct to directly compare pointers
32 /// to two PartialSubgraphs to check if two layers belong in the same subgraph. Instead you
33 /// should use IsMergedWith().
34 ///
35 /// This structure also stores information about the dependencies of each subgraph, which is needed
36 /// to determine whether certain subgraphs can be merged. Checking whether a subgraph
37 /// depends on another subgraph is a frequent operation in the algorithm (see AssignSplitId) and so this is optimized
38 /// in preference to the merging of subgraphs. This leads to an approach where each subgraph stores
39 /// a set of all the subgraphs it depends on (for a fast lookup). In order to efficiently update this
40 /// set as subgraphs are merged means we also store a set of subgraphs which *depend on us* (i.e. the
41 /// complement of our dependencies).
42 class PartialSubgraph
43 {
44 public:
45  /// If this subgraph has been merged with another then there is an agreed "representative" for the combined
46  /// subgraph, which uniquely identifies the subgraph.
47  PartialSubgraph* GetRepresentative()
48  {
49  // Recurse up the tree to find the root node.
50  if (m_Parent == nullptr)
51  {
52  return this;
53  }
54  else
55  {
56  PartialSubgraph* result = m_Parent->GetRepresentative();
57  // Update our parent pointer to point directly to the root in order to speed up future calls to this method.
58  // This essentially "flattens" the tree.
59  m_Parent = result;
60  return result;
61  }
62  }
63 
64  /// Merges this subgraph with another.
65  void MergeWith(PartialSubgraph* other)
66  {
67  if (m_Parent == nullptr)
68  {
69  other = other->GetRepresentative();
70  if (this == other)
71  {
72  // Already merged - no-op
73  return;
74  }
75  m_Parent = other;
76 
77  // Update others' dependency sets to point to the new representative rather than us.
78  // Keeping these up-to-date means we can rely on these sets containing representatives when
79  // we perform a lookup in HasAntecedent() and so don't need to resolve the representative for each element
80  // of the set. See description at the top of this class for more rationale.
81  for (PartialSubgraph* a : m_Antecedents)
82  {
83  size_t numErased = a->m_Dependants.erase(this);
84  if (numErased != 1)
85  {
86  throw armnn::Exception("number of dependents erased must only be 1.");
87  }
88  a->m_Dependants.insert(m_Parent);
89  }
90  for (PartialSubgraph* a : m_Dependants)
91  {
92  size_t numErased = a->m_Antecedents.erase(this);
93  if (numErased != 1)
94  {
95  throw armnn::Exception("number of antecedents erased must only be 1.");
96  }
97  IgnoreUnused(numErased);
98  a->m_Antecedents.insert(m_Parent);
99  }
100 
101  // Merge our dependency sets into our new representative.
102  // We no longer need to maintain our own sets, as requests will always be forwarded to the representative.
103  m_Parent->m_Antecedents.insert(m_Antecedents.begin(), m_Antecedents.end());
104  m_Antecedents.clear();
105  m_Parent->m_Dependants.insert(m_Dependants.begin(), m_Dependants.end());
106  m_Dependants.clear();
107  }
108  else
109  {
110  // Defer request to the representative
111  GetRepresentative()->MergeWith(other);
112  }
113  }
114 
115  /// Checks if this subgraph has been merged with the given subgraph.
116  bool IsMergedWith(PartialSubgraph* other)
117  {
118  return GetRepresentative() == other->GetRepresentative();
119  }
120 
121  /// Marks the given subgraph as a direct antecedent (dependency) of this one.
122  void AddDirectAntecedent(PartialSubgraph* antecedent)
123  {
124  if (m_Parent == nullptr)
125  {
126  antecedent = antecedent->GetRepresentative();
127 
128  m_Antecedents.insert(antecedent);
129  // Also record all of its antecedents, so that we end up with direct and indirect antecedents.
130  // This makes the lookup in HasAntecedent() faster.
131  m_Antecedents.insert(antecedent->m_Antecedents.begin(), antecedent->m_Antecedents.end());
132  // All of our dependents also need to include the new antecedents
133  for (PartialSubgraph* d : m_Dependants)
134  {
135  d->m_Antecedents.insert(antecedent);
136  d->m_Antecedents.insert(antecedent->m_Antecedents.begin(), antecedent->m_Antecedents.end());
137  }
138 
139  // Store reverse dependencies as well, required so that we can efficiently navigate the graph
140  // when making updates.
141  antecedent->m_Dependants.insert(this);
142  antecedent->m_Dependants.insert(m_Dependants.begin(), m_Dependants.end());
143  for (PartialSubgraph* a : antecedent->m_Antecedents)
144  {
145  a->m_Dependants.insert(this);
146  a->m_Dependants.insert(m_Dependants.begin(), m_Dependants.end());
147  }
148  }
149  else
150  {
151  // Defer request to the representative
152  GetRepresentative()->AddDirectAntecedent(antecedent);
153  }
154  }
155 
156  /// Checks if this subgraph is dependent on the given subgraph, either directly or indirectly.
157  bool HasAntecedent(PartialSubgraph* antecedent)
158  {
159  if (m_Parent == nullptr)
160  {
161  antecedent = antecedent->GetRepresentative();
162  // Thanks to keeping this set updated in MergeWith and AddDirectAntecedent, we can do an efficient lookup.
163  return m_Antecedents.count(antecedent) > 0;
164  }
165  else
166  {
167  // Defer request to the representative
168  return GetRepresentative()->HasAntecedent(antecedent);
169  }
170  }
171 
172 private:
173  /// Pointer to the parent node in the tree. If this is null then we are the representative for our merged subgraph.
174  PartialSubgraph* m_Parent;
175  /// The representatives of all the subgraphs which we depend on, either directly or indirectly.
176  std::unordered_set<PartialSubgraph*> m_Antecedents;
177  /// The representatives of all the subgraphs which depend on us, either directly or indirectly.
178  std::unordered_set<PartialSubgraph*> m_Dependants;
179 };
180 
181 /// Intermediate data structure to store information associated with a particular layer.
182 struct LayerSelectionInfo
183 {
184  using LayerInfoContainer = std::map<IConnectableLayer*, LayerSelectionInfo>;
185  using LayerInfoQueue = std::queue<LayerSelectionInfo*>;
186 
187  LayerSelectionInfo(Layer* layer, const SubgraphViewSelector::LayerSelectorFunction& selector)
188  : m_Layer{layer}
189  , m_Subgraph{nullptr}
190  , m_IsSelected{selector(*layer)}
191  , m_IsProcessed(false)
192  {
193  }
194 
195  bool IsInputLayer() const
196  {
197  return m_Layer->GetType() == armnn::LayerType::Input || m_Layer->GetType() == armnn::LayerType::Constant;
198  }
199 
200  void CollectNonSelectedInputs(LayerSelectionInfo::LayerInfoContainer& layerInfos,
201  SubgraphView::IInputSlots& inputSlots)
202  {
203  for (auto&& slot = PolymorphicDowncast<Layer*>(m_Layer)->BeginInputSlots();
204  slot != PolymorphicDowncast<Layer*>(m_Layer)->EndInputSlots();
205  ++slot)
206  {
207  OutputSlot* parentLayerOutputSlot = slot->GetConnectedOutputSlot();
208 
209  if (!parentLayerOutputSlot)
210  {
211  throw armnn::NullPointerException("The input slots must be connected here.");
212  }
213 
214  if (parentLayerOutputSlot)
215  {
216  Layer& parentLayer = parentLayerOutputSlot->GetOwningLayer();
217  auto parentInfo = layerInfos.find(&parentLayer);
218  if (parentInfo == layerInfos.end() ||
219  !m_Subgraph->IsMergedWith(parentInfo->second.m_Subgraph.get()))
220  {
221  // Avoid collecting duplicate input slots
222  InputSlot* inputSlot = &(*slot);
223  if (std::find(inputSlots.begin(), inputSlots.end(), inputSlot) == inputSlots.end())
224  {
225  inputSlots.push_back(inputSlot);
226  }
227  }
228  }
229  }
230  }
231 
232  void CollectNonSelectedOutputSlots(LayerSelectionInfo::LayerInfoContainer& layerInfos,
233  SubgraphView::IOutputSlots& outputSlots)
234  {
235  for (auto&& slot = PolymorphicDowncast<Layer*>(m_Layer)->BeginOutputSlots();
236  slot != PolymorphicDowncast<Layer*>(m_Layer)->EndOutputSlots();
237  ++slot)
238  {
239  for (InputSlot* childLayerInputSlot : slot->GetConnections())
240  {
241  Layer& childLayer = childLayerInputSlot->GetOwningLayer();
242  auto childInfo = layerInfos.find(&childLayer);
243  if (childInfo == layerInfos.end() ||
244  !m_Subgraph->IsMergedWith(childInfo->second.m_Subgraph.get()))
245  {
246  // Avoid collecting duplicate output slots
247  OutputSlot* outputSlot = &(*slot);
248  if (std::find(outputSlots.begin(), outputSlots.end(), outputSlot) == outputSlots.end())
249  {
250  outputSlots.push_back(outputSlot);
251  }
252  }
253  }
254  }
255  }
256 
257  IConnectableLayer* m_Layer;
258  /// Which subgraph this layer has been assigned to. Only valid once m_IsProcessed is true.
259  /// Two layers with different m_Subgraph pointers may in fact have been merged into the same subgraph -
260  /// see the description of the PartialSubgraph class.
261  std::shared_ptr<PartialSubgraph> m_Subgraph;
264 };
265 
266 } // namespace <anonymous>
267 
270 {
271  SubgraphView subgraph(graph);
272  return SubgraphViewSelector::SelectSubgraphs(subgraph, selector);
273 }
274 
275 
276 template<typename Delegate>
277 void ForEachLayerInput(LayerSelectionInfo::LayerInfoContainer& layerInfos,
278  LayerSelectionInfo& layerInfo,
279  Delegate function)
280 {
281  Layer& layer = *PolymorphicDowncast<Layer*>(layerInfo.m_Layer);
282 
283  for (auto inputSlot : layer.GetInputSlots())
284  {
285  auto connectedInput = PolymorphicDowncast<OutputSlot*>(inputSlot.GetConnection());
286  if (!connectedInput)
287  {
288  throw armnn::Exception("Dangling input slot detected.");
289  }
290  Layer& inputLayer = connectedInput->GetOwningLayer();
291 
292  auto parentInfo = layerInfos.find(&inputLayer);
293  if (parentInfo != layerInfos.end())
294  {
295  function(parentInfo->second);
296  }
297  }
298 }
299 
300 template<typename Delegate>
301 void ForEachLayerOutput(LayerSelectionInfo::LayerInfoContainer& layerInfos,
302  LayerSelectionInfo& layerInfo,
303  Delegate function)
304 {
305  Layer& layer = *PolymorphicDowncast<Layer*>(layerInfo.m_Layer);
306 
307  for (auto& outputSlot : layer.GetOutputSlots())
308  {
309  for (auto& output : outputSlot.GetConnections())
310  {
311  Layer& childLayer = output->GetOwningLayer();
312 
313  auto childInfo = layerInfos.find(&childLayer);
314  if (childInfo != layerInfos.end())
315  {
316  function(childInfo->second);
317  }
318  }
319  }
320 }
321 
322 void AssignSplitId(LayerSelectionInfo::LayerInfoContainer& layerInfos, LayerSelectionInfo& layerInfo)
323 {
324  // Check each input to see if we can attach ourselves to any of the subgraphs that have already been assigned.
325  ForEachLayerInput(layerInfos, layerInfo, [&](LayerSelectionInfo& parentInfo)
326  {
327  // We can only attach ourselves to the subgraph from this input if there isn't a cut here.
328  if (layerInfo.m_IsSelected == parentInfo.m_IsSelected)
329  {
330  // We also need to check that merging into this subgraph won't cause a dependency cycle between subgraphs.
331  // This will be the case if the subgraph that we will become part of is already a dependency
332  // of one of the subgraphs that are input to this layer, e.g:
333  //
334  // 0 | The numbers (0, 1) are the subgraph IDs of each layer and we are looking at layer X.
335  // / \ |
336  // 1 0 | We can't merge X into subgraph 0, because the left-hand input already depends on subgraph 0.
337  // \ / | We can however merge X into subgraph 1.
338  // X |
339  //
340  bool dependenciesOk = true;
341  ForEachLayerInput(layerInfos, layerInfo, [&](LayerSelectionInfo& otherParentInfo)
342  {
343  // We call HasAntecedent() ~ n^2 times, where n is the number of inputs to this layer.
344  // Hence it is important that this is efficient - see PartialSubgraph class description.
345  if (otherParentInfo.m_Subgraph->HasAntecedent(parentInfo.m_Subgraph.get()))
346  {
347  dependenciesOk = false;
348  }
349  });
350 
351  if (dependenciesOk)
352  {
353  // Merge into the subgraph of this input. If we have already been merged into another subgraph
354  // (from another input of this layer), then merge both of them together.
355  if (layerInfo.m_Subgraph == nullptr)
356  {
357  layerInfo.m_Subgraph = parentInfo.m_Subgraph;
358  }
359  else
360  {
361  // We call MergeWith() ~ n times, where n is the number of inputs to this layer.
362  // Therefore it does not need to be as performant as HasAntecedent().
363  layerInfo.m_Subgraph->MergeWith(parentInfo.m_Subgraph.get());
364  }
365  }
366  }
367  });
368 
369  // If we weren't able to merge into an existing subgraph then we need to make a new one
370  if (layerInfo.m_Subgraph == nullptr)
371  {
372  layerInfo.m_Subgraph = std::make_shared<PartialSubgraph>();
373  }
374 
375  // Record dependencies of the chosen subgraph based on the inputs of this layer.
376  ForEachLayerInput(layerInfos, layerInfo, [&](LayerSelectionInfo& parentInfo)
377  {
378  // These functions are called ~n times, where n is the number of inputs to this layer.
379  // Therefore it does not need to be as performant as HasAntecedent().
380  if (!layerInfo.m_Subgraph->IsMergedWith(parentInfo.m_Subgraph.get()))
381  {
382  layerInfo.m_Subgraph->AddDirectAntecedent(parentInfo.m_Subgraph.get());
383  }
384  });
385 }
386 
387 bool IsReadyForSplitAssignment(LayerSelectionInfo::LayerInfoContainer& layerInfos, LayerSelectionInfo& layerInfo)
388 {
389  bool ready = true;
390  ForEachLayerInput(layerInfos, layerInfo,
391  [&ready](LayerSelectionInfo& parentInfo)
392  {
393  if (!parentInfo.m_IsProcessed)
394  {
395  ready = false;
396  }
397  });
398  return ready;
399 }
400 
401 SubgraphViewSelector::Subgraphs
402 SubgraphViewSelector::SelectSubgraphs(SubgraphView& subgraph, const LayerSelectorFunction& selector)
403 {
404  LayerSelectionInfo::LayerInfoContainer layerInfos;
405 
406  LayerSelectionInfo::LayerInfoQueue processQueue;
407  const SubgraphView::IConnectableLayers& subgraphLayers = subgraph.GetIConnectableLayers();
408  for (auto& layer : subgraphLayers)
409  {
410 
411  auto emplaced = layerInfos.emplace(layer, LayerSelectionInfo{PolymorphicDowncast<Layer*>(layer), selector});
412  LayerSelectionInfo& layerInfo = emplaced.first->second;
413 
414  // Start with Input type layers
415  if (layerInfo.IsInputLayer())
416  {
417  processQueue.push(&layerInfo);
418  }
419  }
420 
421  const SubgraphView::IInputSlots& subgraphInputSlots = subgraph.GetIInputSlots();
422  for (auto& inputSlot : subgraphInputSlots)
423  {
424  Layer& layer = PolymorphicDowncast<InputSlot*>(inputSlot)->GetOwningLayer();
425  auto emplaced = layerInfos.emplace(&layer, LayerSelectionInfo{&layer, selector});
426  LayerSelectionInfo& layerInfo = emplaced.first->second;
427 
428  processQueue.push(&layerInfo);
429  }
430 
431  while (!processQueue.empty())
432  {
433  LayerSelectionInfo& layerInfo = *processQueue.front();
434  processQueue.pop(); // remove front from queue
435 
436  // This layerInfo may have been added to the queue multiple times, so skip if we have already processed it
437  if (!layerInfo.m_IsProcessed)
438  {
439  // Only process this layerInfo if all inputs have been processed
440  if (!IsReadyForSplitAssignment(layerInfos, layerInfo))
441  {
442  // Put back of the process queue if we can't process it just yet
443  processQueue.push(&layerInfo);
444  continue; // Skip to next iteration
445  }
446 
447  // Now we do the processing
448  AssignSplitId(layerInfos, layerInfo);
449 
450  // Queue any child nodes for processing
451  ForEachLayerOutput(layerInfos, layerInfo, [&processQueue](LayerSelectionInfo& childInfo)
452  {
453  processQueue.push(&childInfo);
454  });
455 
456  // We don't need to process this node again
457  layerInfo.m_IsProcessed = true;
458  }
459  }
460 
461  // Collect all selected layers keyed by subgraph representative into a map
462  using SelectionInfoPtrs = std::vector<LayerSelectionInfo*>;
463  std::map<PartialSubgraph*, SelectionInfoPtrs> splitMap;
464  for (auto& info : layerInfos)
465  {
466  if (info.second.m_IsSelected)
467  {
468  auto it = splitMap.find(info.second.m_Subgraph->GetRepresentative());
469  if (it == splitMap.end())
470  {
471  splitMap.insert(
472  std::make_pair(info.second.m_Subgraph->GetRepresentative(), SelectionInfoPtrs{&info.second}));
473  }
474  else
475  {
476  it->second.push_back(&info.second);
477  }
478  }
479  }
480 
481  // Now each entry in splitMap represents a subgraph
482  Subgraphs result;
483  for (auto& splitGraph : splitMap)
484  {
488  for (auto&& infoPtr : splitGraph.second)
489  {
490  infoPtr->CollectNonSelectedInputs(layerInfos, inputs);
491  infoPtr->CollectNonSelectedOutputSlots(layerInfos, outputs);
492  layers.push_back(infoPtr->m_Layer);
493  }
494 
495  // Sort lists into deterministic order, not relying on pointer values which may be different on each execution.
496  // This makes debugging the optimised graph much easier as subsequent stages can also be deterministic.
497  std::sort(inputs.begin(), inputs.end(), [](const IInputSlot* a, const IInputSlot* b)
498  {
499  auto* castA = PolymorphicDowncast<const InputSlot*>(a);
500  auto* castB = PolymorphicDowncast<const InputSlot*>(b);
501  const LayerGuid guidA = castA->GetOwningLayer().GetGuid();
502  const LayerGuid guidB = castB->GetOwningLayer().GetGuid();
503  if (guidA < guidB)
504  {
505  return true;
506  }
507  else if (guidA == guidB)
508  {
509  return (castA->GetSlotIndex() < castB->GetSlotIndex());
510  }
511  return false;
512  });
513  std::sort(outputs.begin(), outputs.end(), [](const IOutputSlot* a, const IOutputSlot* b)
514  {
515  auto* castA = PolymorphicDowncast<const OutputSlot*>(a);
516  auto* castB = PolymorphicDowncast<const OutputSlot*>(b);
517  const LayerGuid guidA = castA->GetOwningLayer().GetGuid();
518  const LayerGuid guidB = castB->GetOwningLayer().GetGuid();
519  if (guidA < guidB)
520  {
521  return true;
522  }
523  else if (guidA == guidB)
524  {
525  return (a->CalculateIndexOnOwner() < b->CalculateIndexOnOwner());
526  }
527  return false;
528  });
529  layers.sort([](const IConnectableLayer* a, const IConnectableLayer* b) { return a->GetGuid() < b->GetGuid(); });
530 
531  // Create a new sub-graph with the new lists of input/output slots and layer
532  result.emplace_back(std::make_unique<SubgraphView>(std::move(layers),
533  std::move(inputs),
534  std::move(outputs)));
535  }
536 
537  // Sort subgraphs list into deterministic order, not relying on pointer values which may be different on each
538  // execution. This makes debugging the optimised graph much easier as subsequent stages can also be
539  // deterministic.
540  std::sort(result.begin(), result.end(), [](const SubgraphView::SubgraphViewPtr& a,
541  const SubgraphView::SubgraphViewPtr& b)
542  {
543  return a->GetIConnectableLayers().front()->GetGuid() < b->GetIConnectableLayers().front()->GetGuid();
544  });
545 
546  return result;
547 }
548 
549 } // namespace armnn
armnn::SubgraphView::IOutputSlots
std::vector< IOutputSlot * > IOutputSlots
Definition: SubgraphView.hpp:60
SubgraphViewSelector.hpp
armnn::AssignSplitId
void AssignSplitId(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo)
Definition: SubgraphViewSelector.cpp:322
m_Layer
IConnectableLayer * m_Layer
Definition: SubgraphViewSelector.cpp:257
Graph.hpp
armnn::IConnectableLayer::GetGuid
virtual LayerGuid GetGuid() const =0
Returns the unique id of the layer.
IgnoreUnused.hpp
armnn::Layer::GetInputSlots
const std::vector< InputSlot > & GetInputSlots() const
Definition: Layer.hpp:258
armnn::Layer
Definition: Layer.hpp:230
Assert.hpp
armnn::SubgraphView::IConnectableLayers
std::list< IConnectableLayer * > IConnectableLayers
Definition: SubgraphView.hpp:62
armnn::IOutputSlot
An output connection slot for a layer.
Definition: INetwork.hpp:53
armnn::SubgraphViewSelector::SelectSubgraphs
static Subgraphs SelectSubgraphs(Graph &graph, const LayerSelectorFunction &selector)
Selects subgraphs from a graph based on the selector function and the algorithm.
Definition: SubgraphViewSelector.cpp:269
armnn::SubgraphView::GetIConnectableLayers
const IConnectableLayers & GetIConnectableLayers() const
Definition: SubgraphView.cpp:281
m_IsProcessed
bool m_IsProcessed
Definition: SubgraphViewSelector.cpp:263
armnn::ForEachLayerInput
void ForEachLayerInput(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo, Delegate function)
Definition: SubgraphViewSelector.cpp:277
PolymorphicDowncast.hpp
armnn::SubgraphView::IInputSlots
std::vector< IInputSlot * > IInputSlots
Definition: SubgraphView.hpp:58
armnn::SubgraphViewSelector::LayerSelectorFunction
std::function< bool(const Layer &)> LayerSelectorFunction
Definition: SubgraphViewSelector.hpp:25
armnn::SubgraphView
The SubgraphView class represents a subgraph of a Graph.
Definition: SubgraphView.hpp:31
armnn::Layer::GetOutputSlots
const std::vector< OutputSlot > & GetOutputSlots() const
Definition: Layer.hpp:259
armnn::Exception
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:46
armnn::BoostLogSeverityMapping::info
@ info
armnn::IsReadyForSplitAssignment
bool IsReadyForSplitAssignment(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo)
Definition: SubgraphViewSelector.cpp:387
m_Subgraph
std::shared_ptr< PartialSubgraph > m_Subgraph
Which subgraph this layer has been assigned to.
Definition: SubgraphViewSelector.cpp:261
armnn::IgnoreUnused
void IgnoreUnused(Ts &&...)
Definition: IgnoreUnused.hpp:14
armnn::SubgraphView::GetIInputSlots
const IInputSlots & GetIInputSlots() const
Definition: SubgraphView.cpp:236
armnn::ForEachLayerOutput
void ForEachLayerOutput(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo, Delegate function)
Definition: SubgraphViewSelector.cpp:301
armnn::IOutputSlot::CalculateIndexOnOwner
virtual unsigned int CalculateIndexOnOwner() const =0
armnn
Copyright (c) 2021 ARM Limited and Contributors.
Definition: 01_00_quick_start.dox:6
armnn::IConnectableLayer
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:80
armnn::IInputSlot
An input connection slot for a layer.
Definition: INetwork.hpp:25
armnn::LayerType::Input
@ Input
m_IsSelected
bool m_IsSelected
Definition: SubgraphViewSelector.cpp:262
armnn::NullPointerException
Definition: Exceptions.hpp:146
armnn::SubgraphViewSelector::Subgraphs
std::vector< SubgraphView::SubgraphViewPtr > Subgraphs
Definition: SubgraphViewSelector.hpp:24
armnn::Graph
Definition: Graph.hpp:30
armnn::LayerType::Constant
@ Constant