ArmNN
 21.02
SubgraphViewSelector.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. 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  ARMNN_ASSERT(numErased == 1);
85  IgnoreUnused(numErased);
86  a->m_Dependants.insert(m_Parent);
87  }
88  for (PartialSubgraph* a : m_Dependants)
89  {
90  size_t numErased = a->m_Antecedents.erase(this);
91  ARMNN_ASSERT(numErased == 1);
92  IgnoreUnused(numErased);
93  a->m_Antecedents.insert(m_Parent);
94  }
95 
96  // Merge our dependency sets into our new representative.
97  // We no longer need to maintain our own sets, as requests will always be forwarded to the representative.
98  m_Parent->m_Antecedents.insert(m_Antecedents.begin(), m_Antecedents.end());
99  m_Antecedents.clear();
100  m_Parent->m_Dependants.insert(m_Dependants.begin(), m_Dependants.end());
101  m_Dependants.clear();
102  }
103  else
104  {
105  // Defer request to the representative
106  GetRepresentative()->MergeWith(other);
107  }
108  }
109 
110  /// Checks if this subgraph has been merged with the given subgraph.
111  bool IsMergedWith(PartialSubgraph* other)
112  {
113  return GetRepresentative() == other->GetRepresentative();
114  }
115 
116  /// Marks the given subgraph as a direct antecedent (dependency) of this one.
117  void AddDirectAntecedent(PartialSubgraph* antecedent)
118  {
119  if (m_Parent == nullptr)
120  {
121  antecedent = antecedent->GetRepresentative();
122 
123  m_Antecedents.insert(antecedent);
124  // Also record all of its antecedents, so that we end up with direct and indirect antecedents.
125  // This makes the lookup in HasAntecedent() faster.
126  m_Antecedents.insert(antecedent->m_Antecedents.begin(), antecedent->m_Antecedents.end());
127  // All of our dependents also need to include the new antecedents
128  for (PartialSubgraph* d : m_Dependants)
129  {
130  d->m_Antecedents.insert(antecedent);
131  d->m_Antecedents.insert(antecedent->m_Antecedents.begin(), antecedent->m_Antecedents.end());
132  }
133 
134  // Store reverse dependencies as well, required so that we can efficiently navigate the graph
135  // when making updates.
136  antecedent->m_Dependants.insert(this);
137  antecedent->m_Dependants.insert(m_Dependants.begin(), m_Dependants.end());
138  for (PartialSubgraph* a : antecedent->m_Antecedents)
139  {
140  a->m_Dependants.insert(this);
141  a->m_Dependants.insert(m_Dependants.begin(), m_Dependants.end());
142  }
143  }
144  else
145  {
146  // Defer request to the representative
147  GetRepresentative()->AddDirectAntecedent(antecedent);
148  }
149  }
150 
151  /// Checks if this subgraph is dependent on the given subgraph, either directly or indirectly.
152  bool HasAntecedent(PartialSubgraph* antecedent)
153  {
154  if (m_Parent == nullptr)
155  {
156  antecedent = antecedent->GetRepresentative();
157  // Thanks to keeping this set updated in MergeWith and AddDirectAntecedent, we can do an efficient lookup.
158  return m_Antecedents.count(antecedent) > 0;
159  }
160  else
161  {
162  // Defer request to the representative
163  return GetRepresentative()->HasAntecedent(antecedent);
164  }
165  }
166 
167 private:
168  /// Pointer to the parent node in the tree. If this is null then we are the representative for our merged subgraph.
169  PartialSubgraph* m_Parent;
170  /// The representatives of all the subgraphs which we depend on, either directly or indirectly.
171  std::unordered_set<PartialSubgraph*> m_Antecedents;
172  /// The representatives of all the subgraphs which depend on us, either directly or indirectly.
173  std::unordered_set<PartialSubgraph*> m_Dependants;
174 };
175 
176 /// Intermediate data structure to store information associated with a particular layer.
177 struct LayerSelectionInfo
178 {
179  using LayerInfoContainer = std::map<Layer*, LayerSelectionInfo>;
180  using LayerInfoQueue = std::queue<LayerSelectionInfo*>;
181 
182  LayerSelectionInfo(Layer* layer, const SubgraphViewSelector::LayerSelectorFunction& selector)
183  : m_Layer{layer}
184  , m_Subgraph{nullptr}
185  , m_IsSelected{selector(*layer)}
186  , m_IsProcessed(false)
187  {
188  }
189 
190  bool IsInputLayer() const
191  {
192  return m_Layer->GetType() == armnn::LayerType::Input || m_Layer->GetType() == armnn::LayerType::Constant;
193  }
194 
195  void CollectNonSelectedInputs(LayerSelectionInfo::LayerInfoContainer& layerInfos,
196  SubgraphView::InputSlots& inputSlots)
197  {
198  for (auto&& slot = m_Layer->BeginInputSlots(); slot != m_Layer->EndInputSlots(); ++slot)
199  {
200  OutputSlot* parentLayerOutputSlot = slot->GetConnectedOutputSlot();
201  ARMNN_ASSERT_MSG(parentLayerOutputSlot != nullptr, "The input slots must be connected here.");
202  if (parentLayerOutputSlot)
203  {
204  Layer& parentLayer = parentLayerOutputSlot->GetOwningLayer();
205  auto parentInfo = layerInfos.find(&parentLayer);
206  if (parentInfo == layerInfos.end() ||
207  !m_Subgraph->IsMergedWith(parentInfo->second.m_Subgraph.get()))
208  {
209  // Avoid collecting duplicate input slots
210  InputSlot* inputSlot = &(*slot);
211  if (std::find(inputSlots.begin(), inputSlots.end(), inputSlot) == inputSlots.end())
212  {
213  inputSlots.push_back(inputSlot);
214  }
215  }
216  }
217  }
218  }
219 
220  void CollectNonSelectedOutputSlots(LayerSelectionInfo::LayerInfoContainer& layerInfos,
221  SubgraphView::OutputSlots& outputSlots)
222  {
223  for (auto&& slot = m_Layer->BeginOutputSlots(); slot != m_Layer->EndOutputSlots(); ++slot)
224  {
225  for (InputSlot* childLayerInputSlot : slot->GetConnections())
226  {
227  Layer& childLayer = childLayerInputSlot->GetOwningLayer();
228  auto childInfo = layerInfos.find(&childLayer);
229  if (childInfo == layerInfos.end() ||
230  !m_Subgraph->IsMergedWith(childInfo->second.m_Subgraph.get()))
231  {
232  // Avoid collecting duplicate output slots
233  OutputSlot* outputSlot = &(*slot);
234  if (std::find(outputSlots.begin(), outputSlots.end(), outputSlot) == outputSlots.end())
235  {
236  outputSlots.push_back(outputSlot);
237  }
238  }
239  }
240  }
241  }
242 
244  /// Which subgraph this layer has been assigned to. Only valid once m_IsProcessed is true.
245  /// Two layers with different m_Subgraph pointers may in fact have been merged into the same subgraph -
246  /// see the description of the PartialSubgraph class.
247  std::shared_ptr<PartialSubgraph> m_Subgraph;
250 };
251 
252 } // namespace <anonymous>
253 
256 {
257  SubgraphView subgraph(graph);
258  return SubgraphViewSelector::SelectSubgraphs(subgraph, selector);
259 }
260 
261 
262 template<typename Delegate>
263 void ForEachLayerInput(LayerSelectionInfo::LayerInfoContainer& layerInfos,
264  LayerSelectionInfo& layerInfo,
265  Delegate function)
266 {
267  Layer& layer = *layerInfo.m_Layer;
268 
269  for (auto inputSlot : layer.GetInputSlots())
270  {
271  auto connectedInput = PolymorphicDowncast<OutputSlot*>(inputSlot.GetConnection());
272  ARMNN_ASSERT_MSG(connectedInput, "Dangling input slot detected.");
273  Layer& inputLayer = connectedInput->GetOwningLayer();
274 
275  auto parentInfo = layerInfos.find(&inputLayer);
276  if (parentInfo != layerInfos.end())
277  {
278  function(parentInfo->second);
279  }
280  }
281 }
282 
283 template<typename Delegate>
284 void ForEachLayerOutput(LayerSelectionInfo::LayerInfoContainer& layerInfos,
285  LayerSelectionInfo& layerInfo,
286  Delegate function)
287 {
288  Layer& layer= *layerInfo.m_Layer;
289 
290  for (auto& outputSlot : layer.GetOutputSlots())
291  {
292  for (auto& output : outputSlot.GetConnections())
293  {
294  Layer& childLayer = output->GetOwningLayer();
295 
296  auto childInfo = layerInfos.find(&childLayer);
297  if (childInfo != layerInfos.end())
298  {
299  function(childInfo->second);
300  }
301  }
302  }
303 }
304 
305 void AssignSplitId(LayerSelectionInfo::LayerInfoContainer& layerInfos, LayerSelectionInfo& layerInfo)
306 {
307  // Check each input to see if we can attach ourselves to any of the subgraphs that have already been assigned.
308  ForEachLayerInput(layerInfos, layerInfo, [&](LayerSelectionInfo& parentInfo)
309  {
310  // We can only attach ourselves to the subgraph from this input if there isn't a cut here.
311  if (layerInfo.m_IsSelected == parentInfo.m_IsSelected)
312  {
313  // We also need to check that merging into this subgraph won't cause a dependency cycle between subgraphs.
314  // This will be the case if the subgraph that we will become part of is already a dependency
315  // of one of the subgraphs that are input to this layer, e.g:
316  //
317  // 0 | The numbers (0, 1) are the subgraph IDs of each layer and we are looking at layer X.
318  // / \ |
319  // 1 0 | We can't merge X into subgraph 0, because the left-hand input already depends on subgraph 0.
320  // \ / | We can however merge X into subgraph 1.
321  // X |
322  //
323  bool dependenciesOk = true;
324  ForEachLayerInput(layerInfos, layerInfo, [&](LayerSelectionInfo& otherParentInfo)
325  {
326  // We call HasAntecedent() ~ n^2 times, where n is the number of inputs to this layer.
327  // Hence it is important that this is efficient - see PartialSubgraph class description.
328  if (otherParentInfo.m_Subgraph->HasAntecedent(parentInfo.m_Subgraph.get()))
329  {
330  dependenciesOk = false;
331  }
332  });
333 
334  if (dependenciesOk)
335  {
336  // Merge into the subgraph of this input. If we have already been merged into another subgraph
337  // (from another input of this layer), then merge both of them together.
338  if (layerInfo.m_Subgraph == nullptr)
339  {
340  layerInfo.m_Subgraph = parentInfo.m_Subgraph;
341  }
342  else
343  {
344  // We call MergeWith() ~ n times, where n is the number of inputs to this layer.
345  // Therefore it does not need to be as performant as HasAntecedent().
346  layerInfo.m_Subgraph->MergeWith(parentInfo.m_Subgraph.get());
347  }
348  }
349  }
350  });
351 
352  // If we weren't able to merge into an existing subgraph then we need to make a new one
353  if (layerInfo.m_Subgraph == nullptr)
354  {
355  layerInfo.m_Subgraph = std::make_shared<PartialSubgraph>();
356  }
357 
358  // Record dependencies of the chosen subgraph based on the inputs of this layer.
359  ForEachLayerInput(layerInfos, layerInfo, [&](LayerSelectionInfo& parentInfo)
360  {
361  // These functions are called ~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  if (!layerInfo.m_Subgraph->IsMergedWith(parentInfo.m_Subgraph.get()))
364  {
365  layerInfo.m_Subgraph->AddDirectAntecedent(parentInfo.m_Subgraph.get());
366  }
367  });
368 }
369 
370 bool IsReadyForSplitAssignment(LayerSelectionInfo::LayerInfoContainer& layerInfos, LayerSelectionInfo& layerInfo)
371 {
372  bool ready = true;
373  ForEachLayerInput(layerInfos, layerInfo,
374  [&ready](LayerSelectionInfo& parentInfo)
375  {
376  if (!parentInfo.m_IsProcessed)
377  {
378  ready = false;
379  }
380  });
381  return ready;
382 }
383 
386 {
387  LayerSelectionInfo::LayerInfoContainer layerInfos;
388 
389  LayerSelectionInfo::LayerInfoQueue processQueue;
390  for (auto& layer : subgraph)
391  {
392  auto emplaced = layerInfos.emplace(layer, LayerSelectionInfo{layer, selector});
393  LayerSelectionInfo& layerInfo = emplaced.first->second;
394 
395  // Start with Input type layers
396  if (layerInfo.IsInputLayer())
397  {
398  processQueue.push(&layerInfo);
399  }
400  }
401 
402  const SubgraphView::InputSlots& subgraphInputSlots = subgraph.GetInputSlots();
403  for (auto& inputSlot : subgraphInputSlots)
404  {
405  Layer& layer = inputSlot->GetOwningLayer();
406  auto emplaced = layerInfos.emplace(&layer, LayerSelectionInfo{&layer, selector});
407  LayerSelectionInfo& layerInfo = emplaced.first->second;
408 
409  processQueue.push(&layerInfo);
410  }
411 
412  while (!processQueue.empty())
413  {
414  LayerSelectionInfo& layerInfo = *processQueue.front();
415  processQueue.pop(); // remove front from queue
416 
417  // This layerInfo may have been added to the queue multiple times, so skip if we have already processed it
418  if (!layerInfo.m_IsProcessed)
419  {
420  // Only process this layerInfo if all inputs have been processed
421  if (!IsReadyForSplitAssignment(layerInfos, layerInfo))
422  {
423  // Put back of the process queue if we can't process it just yet
424  processQueue.push(&layerInfo);
425  continue; // Skip to next iteration
426  }
427 
428  // Now we do the processing
429  AssignSplitId(layerInfos, layerInfo);
430 
431  // Queue any child nodes for processing
432  ForEachLayerOutput(layerInfos, layerInfo, [&processQueue](LayerSelectionInfo& childInfo)
433  {
434  processQueue.push(&childInfo);
435  });
436 
437  // We don't need to process this node again
438  layerInfo.m_IsProcessed = true;
439  }
440  }
441 
442  // Collect all selected layers keyed by subgraph representative into a map
443  using SelectionInfoPtrs = std::vector<LayerSelectionInfo*>;
444  std::map<PartialSubgraph*, SelectionInfoPtrs> splitMap;
445  for (auto& info : layerInfos)
446  {
447  if (info.second.m_IsSelected)
448  {
449  auto it = splitMap.find(info.second.m_Subgraph->GetRepresentative());
450  if (it == splitMap.end())
451  {
452  splitMap.insert(
453  std::make_pair(info.second.m_Subgraph->GetRepresentative(), SelectionInfoPtrs{&info.second}));
454  }
455  else
456  {
457  it->second.push_back(&info.second);
458  }
459  }
460  }
461 
462  // Now each entry in splitMap represents a subgraph
463  Subgraphs result;
464  for (auto& splitGraph : splitMap)
465  {
468  SubgraphView::Layers layers;
469  for (auto&& infoPtr : splitGraph.second)
470  {
471  infoPtr->CollectNonSelectedInputs(layerInfos, inputs);
472  infoPtr->CollectNonSelectedOutputSlots(layerInfos, outputs);
473  layers.push_back(infoPtr->m_Layer);
474  }
475  // Create a new sub-graph with the new lists of input/output slots and layer
476  result.emplace_back(std::make_unique<SubgraphView>(std::move(inputs),
477  std::move(outputs),
478  std::move(layers)));
479  }
480 
481  return result;
482 }
483 
484 } // namespace armnn
bool m_IsSelected
void ForEachLayerInput(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo, Delegate function)
void AssignSplitId(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo)
std::vector< OutputSlot * > OutputSlots
std::function< bool(const Layer &)> LayerSelectorFunction
Copyright (c) 2021 ARM Limited and Contributors.
void IgnoreUnused(Ts &&...)
const std::vector< InputSlot > & GetInputSlots() const
Definition: Layer.hpp:237
bool IsReadyForSplitAssignment(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo)
The SubgraphView class represents a subgraph of a Graph.
Layer * m_Layer
#define ARMNN_ASSERT_MSG(COND, MSG)
Definition: Assert.hpp:15
std::vector< SubgraphViewPtr > Subgraphs
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
const std::vector< OutputSlot > & GetOutputSlots() const
Definition: Layer.hpp:238
std::vector< InputSlot * > InputSlots
bool m_IsProcessed
static Subgraphs SelectSubgraphs(Graph &graph, const LayerSelectorFunction &selector)
Selects subgraphs from a graph based on the selector function and the algorithm.
std::shared_ptr< PartialSubgraph > m_Subgraph
Which subgraph this layer has been assigned to.
std::list< Layer * > Layers
void ForEachLayerOutput(LayerSelectionInfo::LayerInfoContainer &layerInfos, LayerSelectionInfo &layerInfo, Delegate function)