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