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