• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include "Graph.hpp"
7 #include "SubgraphView.hpp"
8 #include "LayersFwd.hpp"
9 
10 #include <armnn/backends/IBackendInternal.hpp>
11 
12 #include <armnn/BackendId.hpp>
13 #include <armnn/Logging.hpp>
14 #include <armnn/TypesUtils.hpp>
15 #include <armnn/Utils.hpp>
16 #include <armnn/utility/Assert.hpp>
17 #include <armnn/utility/NumericCast.hpp>
18 
19 #include <fmt/format.h>
20 
21 #include <unordered_map>
22 #include <DotSerializer.hpp>
23 #include <sstream>
24 
25 namespace armnn
26 {
27 
Graph(const Graph & other)28 Graph::Graph(const Graph& other)
29 :   m_LayersInOrder(other.m_LayersInOrder)
30 {
31     std::unordered_map<const Layer*, Layer*> otherToClonedMap;
32 
33     for (auto&& otherLayer : other.m_Layers)
34     {
35         Layer* const layer = otherLayer->Clone(*this);
36         otherToClonedMap.emplace(otherLayer, layer);
37     }
38 
39     // Copies slot connections.
40     for (auto&& otherLayer : other.m_Layers)
41     {
42         Layer* const thisLayer = otherToClonedMap[otherLayer];
43 
44         auto outputSlot = thisLayer->BeginOutputSlots();
45         for (auto&& otherOutputSlot : otherLayer->GetOutputSlots())
46         {
47             for (auto&& otherInputSlot : otherOutputSlot.GetConnections())
48             {
49                 const Layer& otherTgtLayer = otherInputSlot->GetOwningLayer();
50                 Layer* const thisTgtLayer = otherToClonedMap[&otherTgtLayer];
51 
52                 InputSlot& inputSlot = thisTgtLayer->GetInputSlot(otherInputSlot->GetSlotIndex());
53                 outputSlot->Connect(inputSlot);
54             }
55             outputSlot->SetTensorInfo(otherOutputSlot.GetTensorInfo());
56             ++outputSlot;
57         }
58     }
59 }
60 
Print() const61 Status Graph::Print() const
62 {
63     if (m_Layers.empty())
64     {
65         ARMNN_LOG(info) << "\n Graph is empty.\n";
66         return Status::Success;
67     }
68     ARMNN_LOG(info) << "\n";
69     ARMNN_LOG(info) << "Walking Pattern: \n";
70 
71     for (auto&& it : TopologicalSort())
72     {
73         auto numInputSlots = it->GetNumInputSlots();
74         auto numOutputSlots = it->GetNumOutputSlots();
75 
76         ARMNN_LOG(info) << it->GetName() << ":" << GetLayerTypeAsCString(it->GetType())
77                                 << ":" << it->GetBackendId().Get()
78                                 << " has " << numInputSlots << " input slots"
79                                 << " and " << numOutputSlots << " output slots.";
80 
81         for (auto i : it->GetInputSlots())
82         {
83             std::ostringstream message;
84             auto inputTensorShape = i.GetConnectedOutputSlot()->GetTensorInfo().GetShape();
85             unsigned int numDims = inputTensorShape.GetNumDimensions();
86 
87             message << "The input slot has shape [ ";
88             for (unsigned int dim=0; dim < numDims; dim++)
89             {
90                 message << inputTensorShape[dim] << ",";
91             }
92             message << " ]";
93             ARMNN_LOG(info) << message.str();
94         }
95 
96         for (unsigned int i = 0; i < it->GetNumOutputSlots(); i++)
97         {
98             const armnn::Layer *layer = it;
99             std::ostringstream message;
100             auto outputTensorShape = layer->GetOutputSlots()[i].GetTensorInfo().GetShape();
101             unsigned int numDims = outputTensorShape.GetNumDimensions();
102 
103             message << "The output slot has shape [ ";
104             for (unsigned int dim=0; dim < numDims; dim++)
105             {
106                 message << outputTensorShape[dim] << ",";
107             }
108             message << " ]";
109             ARMNN_LOG(info) << message.str();
110         }
111         ARMNN_LOG(info) << "\n";
112     }
113     ARMNN_LOG(info) << "\n\n";
114 
115     return Status::Success;
116 }
117 
SerializeToDot(std::ostream & stream)118 Status Graph::SerializeToDot(std::ostream& stream)
119 {
120     {
121         DotGraph graph(stream, "Optimized");
122 
123         {
124             // Default node attributes:
125             DotDefaults nodes(stream, "node");
126             nodes.GetAttributeSet()
127                 .AddAttribute("shape", "record");
128         }
129 
130         {
131             // Default edge attributes:
132             DotDefaults edges(stream, "edge");
133             edges.GetAttributeSet()
134                 .AddAttribute("fontsize", 8)
135                 .AddAttribute("fontcolor", "blue")
136                 .AddAttribute("fontname", "arial-bold");
137         }
138 
139         // First declares the nodes.
140         for (auto&& layer : m_Layers)
141         {
142             DotNode node(stream, layer->GetGuid(), GetLayerTypeAsCString(layer->GetType()));
143             // Extracts the layer parameters.
144             ParameterStringifyFunction extractParams = [&node](const std::string & name, const std::string & value){
145                 node.GetContents().AddContent(name + " : " + value);
146             };
147             layer->SerializeLayerParameters(extractParams);
148         }
149 
150         // Second declares the edges.
151         for (auto&& layer : m_Layers)
152         {
153             LayerGuid toId = layer->GetGuid();
154 
155             for (unsigned int i=0;i<layer->GetNumInputSlots(); i++)
156             {
157                 OutputSlot* outputSlot = static_cast<OutputSlot*>(layer->GetInputSlot(i).GetConnection());
158                 LayerGuid fromId = outputSlot->GetOwningLayer().GetGuid();
159                 DotEdge edge(stream, fromId, toId);
160 
161                 // Now print the tensor shape on the edge.
162                 {
163                     // Constructs the label attribute with HTML markup.
164                     std::stringstream ss;
165                     ss << "< " << outputSlot->GetTensorInfo().GetShape() << " >";
166                     edge.GetAttributeSet().AddAttribute("label", ss);
167                 }
168             }
169         }
170     }
171 
172     if (stream.bad())
173     {
174         return Status::Failure;
175     }
176     return Status::Success;
177 }
178 
AllocateDynamicBuffers()179 Status Graph::AllocateDynamicBuffers()
180 {
181     // Layers must be sorted in topological order
182     ARMNN_ASSERT(m_LayersInOrder);
183 
184     std::unordered_set<const ITensorHandle*> preallocatedTensors;
185     std::unordered_map<const ITensorHandle*, unsigned int> handleReferenceCounts;
186 
187     // Finds the first TensorHandle ancestor of a SubTensorHandle. If the ITensorHandle provided
188     // is a TensorHandle, the function just returns it
189     auto TraceSubTensorHandleAncestry = [](ITensorHandle* const subTensorHandle)
190     {
191         ITensorHandle* ancestor = subTensorHandle;
192         while (ancestor && ancestor->GetParent())
193         {
194             ancestor = ancestor->GetParent();
195         }
196         return ancestor;
197     };
198 
199     // Checks whether a TensorHandle has been pre-allocated
200     auto IsPreallocated = [&](ITensorHandle* const tensorHandle)
201     {
202         return tensorHandle && preallocatedTensors.find(tensorHandle) != preallocatedTensors.end();
203     };
204 
205     // Constant tensor handles need to last from the beginning of execution till the end,
206     // therefore we pre-allocate them upfront
207     for (auto&& layer : m_Layers)
208     {
209         if (layer->GetType() == LayerType::Constant)
210         {
211             for (auto&& slot = layer->BeginOutputSlots(); slot != layer->EndOutputSlots(); ++slot)
212             {
213                 ITensorHandle *tensorHandle = TraceSubTensorHandleAncestry(slot->GetOutputHandler().GetData());
214 
215                 if (tensorHandle && !IsPreallocated(tensorHandle))
216                 {
217                     tensorHandle->Allocate();
218                     preallocatedTensors.insert(tensorHandle);
219                 }
220             }
221         }
222     }
223 
224     // Iterate over the network in topological order
225     for (auto&& layer : m_Layers)
226     {
227         // Count the amount of times each output slot references a certain buffer (ITensorHandle).
228         // The first time we encounter a new tensor handle, we start managing its lifetime.
229         for (auto&& slot = layer->BeginOutputSlots(); slot != layer->EndOutputSlots(); ++slot)
230         {
231             ITensorHandle *tensorHandle = TraceSubTensorHandleAncestry(slot->GetOutputHandler().GetData());
232 
233             if (tensorHandle && !IsPreallocated(tensorHandle))
234             {
235                 unsigned int numConnections = slot->GetNumConnections();
236                 if (handleReferenceCounts.find(tensorHandle) == handleReferenceCounts.end())
237                 {
238                     handleReferenceCounts[tensorHandle] = numConnections;
239                     tensorHandle->Manage();
240                     if (handleReferenceCounts[tensorHandle] == 0u)
241                     {
242                           // if nobody consumes this tensor we call Allocate()
243                           tensorHandle->Allocate();
244                     }
245                 }
246                 else
247                 {
248                     handleReferenceCounts[tensorHandle] += numConnections;
249                 }
250             }
251         }
252 
253         // Loop through the input slots in the same layer and decrement the reference counter associated
254         // to each tensor handle we encounter. Once it reaches zero, we end the lifetime of the tensor handle
255         for (auto&& slot = layer->BeginInputSlots(); slot != layer->EndInputSlots(); ++slot)
256         {
257             ITensorHandle *tensorHandle = TraceSubTensorHandleAncestry(
258                 slot->GetConnectedOutputSlot()->GetOutputHandler().GetData());
259 
260             if (tensorHandle && !IsPreallocated(tensorHandle))
261             {
262                 --handleReferenceCounts[tensorHandle];
263 
264                 if (handleReferenceCounts[tensorHandle] == 0u)
265                 {
266                     // Stop managing lifetime of tensor handle
267                     tensorHandle->Allocate();
268                     handleReferenceCounts.erase(tensorHandle);
269                 }
270             }
271         }
272     }
273 
274     return Status::Success;
275 }
276 
TopologicalSort() const277 const Graph& Graph::TopologicalSort() const
278 {
279     if (!m_LayersInOrder)
280     {
281         // Resets layer order.
282         for (auto&& it : m_Layers)
283         {
284             it->ResetPriority();
285         }
286 
287         auto compareLayerPriority = [](const LayerList::value_type& layerA, const LayerList::value_type& layerB)
288             {
289                 return layerA->GetPriority() < layerB->GetPriority();
290             };
291 
292         m_Layers.sort(compareLayerPriority);
293 
294         m_LayersInOrder = true;
295     }
296 
297     return *this;
298 }
299 
AddCompatibilityLayers(std::map<BackendId,std::unique_ptr<IBackendInternal>> & backends,TensorHandleFactoryRegistry & registry)300 void Graph::AddCompatibilityLayers(std::map<BackendId, std::unique_ptr<IBackendInternal>>& backends,
301                                    TensorHandleFactoryRegistry& registry)
302 {
303     // Returns true if the given layer could potentially need an intermediate copy/import layer (depending on its
304     // connections to other layers).
305     auto MayNeedCompatibilityLayer = [](const Layer& layer)
306     {
307         // All layers should have been associated with a valid compute device at this point.
308         ARMNN_ASSERT(layer.GetBackendId() != Compute::Undefined);
309         // Does not need another compatibility layer if a copy or import layer is already present.
310         return layer.GetType() != LayerType::MemCopy &&
311                layer.GetType() != LayerType::MemImport;
312     };
313 
314     auto IsCompatibilityStrategy = [](EdgeStrategy strategy)
315     {
316         return strategy == EdgeStrategy::CopyToTarget ||
317                strategy == EdgeStrategy::ExportToTarget;
318     };
319 
320     ForEachLayer([this, &backends, &registry, MayNeedCompatibilityLayer, IsCompatibilityStrategy](Layer* srcLayer)
321     {
322         ARMNN_ASSERT(srcLayer);
323 
324         if (!MayNeedCompatibilityLayer(*srcLayer))
325         {
326             // The current layer does not need copy layers, move to the next one
327             return;
328         }
329 
330         const std::vector<OutputSlot>& srcOutputSlots = srcLayer->GetOutputSlots();
331         for (unsigned int srcOutputIndex = 0; srcOutputIndex < srcOutputSlots.size(); srcOutputIndex++)
332         {
333             OutputSlot& srcOutputSlot = srcLayer->GetOutputSlot(srcOutputIndex);
334             const std::vector<InputSlot*> srcConnections = srcOutputSlot.GetConnections();
335             const std::vector<EdgeStrategy> srcEdgeStrategies = srcOutputSlot.GetEdgeStrategies();
336             for (unsigned int srcConnectionIndex = 0; srcConnectionIndex < srcConnections.size(); srcConnectionIndex++)
337             {
338                 InputSlot* dstInputSlot = srcConnections[srcConnectionIndex];
339                 ARMNN_ASSERT(dstInputSlot);
340 
341                 EdgeStrategy strategy = srcEdgeStrategies[srcConnectionIndex];
342                 ARMNN_ASSERT_MSG(strategy != EdgeStrategy::Undefined,
343                                  "Undefined memory strategy found while adding copy layers for compatibility");
344 
345                 const Layer& dstLayer = dstInputSlot->GetOwningLayer();
346                 if (MayNeedCompatibilityLayer(dstLayer) &&
347                     IsCompatibilityStrategy(strategy))
348                 {
349                     // A copy layer is needed in between the source and destination layers.
350                     // Record the operation rather than attempting to modify the graph as we go.
351                     // (invalidating iterators)
352                     const std::string compLayerName = fmt::format("[ {} ({}) -> {} ({}) ]",
353                                                                   srcLayer->GetName(),
354                                                                   srcOutputIndex,
355                                                                   dstLayer.GetName(),
356                                                                   dstInputSlot->GetSlotIndex());
357                     Layer* compLayer = nullptr;
358                     if (strategy == EdgeStrategy::CopyToTarget)
359                     {
360                         compLayer = InsertNewLayer<MemCopyLayer>(*dstInputSlot, compLayerName.c_str());
361                     }
362                     else
363                     {
364                         ARMNN_ASSERT_MSG(strategy == EdgeStrategy::ExportToTarget, "Invalid edge strategy found.");
365                         compLayer = InsertNewLayer<MemImportLayer>(*dstInputSlot, compLayerName.c_str());
366                     }
367 
368                     compLayer->SetBackendId(dstLayer.GetBackendId());
369 
370                     OutputSlot& compOutputSlot = compLayer->GetOutputSlot(0);
371                     auto backendIt = backends.find(dstLayer.GetBackendId());
372                     if (backendIt != backends.end() &&
373                         backendIt->second &&
374                         backendIt->second->SupportsTensorAllocatorAPI())
375                     {
376                         auto backend = backendIt->second.get();
377                         auto tensorHandleFactoryIds = backend->GetHandleFactoryPreferences();
378                         bool found = false;
379 
380                         for (auto preference : tensorHandleFactoryIds)
381                         {
382                             auto factory = registry.GetFactory(preference);
383                             if (factory)
384                             {
385                                 auto srcPref = srcOutputSlot.GetTensorHandleFactoryId();
386                                 auto srcFactory = registry.GetFactory(srcPref);
387 
388                                 if (srcFactory)
389                                 {
390                                     bool canExportImport =
391                                         (factory->GetImportFlags() & srcFactory->GetExportFlags()) != 0;
392 
393                                     if (factory->SupportsMapUnmap() || canExportImport)
394                                     {
395                                         compOutputSlot.SetTensorHandleFactory(preference);
396                                         found = true;
397                                         break;
398                                     }
399                                 }
400                             }
401                         }
402 
403                         if (!found)
404                         {
405                             compOutputSlot.SetTensorHandleFactory(ITensorHandleFactory::LegacyFactoryId);
406                         }
407                     }
408                     else
409                     {
410                         compOutputSlot.SetTensorHandleFactory(ITensorHandleFactory::LegacyFactoryId);
411                     }
412 
413                     // The output strategy of a compatibility layer is always DirectCompatibility.
414                     compOutputSlot.SetEdgeStrategy(0, EdgeStrategy::DirectCompatibility);
415 
416                     // Recalculate the connection index on the previous layer as we have just inserted into it.
417                     const std::vector<InputSlot*>& newSourceConnections = srcOutputSlot.GetConnections();
418                     auto newSrcConnectionIndex = std::distance(newSourceConnections.begin(),
419                                                                std::find(newSourceConnections.begin(),
420                                                                          newSourceConnections.end(),
421                                                                          &compLayer->GetInputSlot(0)));
422 
423                     // The input strategy of a compatibility layer is always DirectCompatibilty.
424                     srcOutputSlot.SetEdgeStrategy(armnn::numeric_cast<unsigned int>(newSrcConnectionIndex),
425                                                     EdgeStrategy::DirectCompatibility);
426                 }
427             }
428         }
429     });
430 }
431 
SubstituteSubgraph(SubgraphView & subgraph,IConnectableLayer * substituteLayer)432 void Graph::SubstituteSubgraph(SubgraphView& subgraph, IConnectableLayer* substituteLayer)
433 {
434     ARMNN_ASSERT(substituteLayer != nullptr);
435 
436     ReplaceSubgraphConnections(subgraph, substituteLayer);
437     EraseSubgraphLayers(subgraph);
438 }
439 
SubstituteSubgraph(SubgraphView & subgraph,const SubgraphView & substituteSubgraph)440 void Graph::SubstituteSubgraph(SubgraphView& subgraph, const SubgraphView& substituteSubgraph)
441 {
442     // Look through each layer in the new subgraph and add any that are not already a member of this graph
443     substituteSubgraph.ForEachLayer([this](Layer* layer)
444     {
445         if (std::find(std::begin(m_Layers), std::end(m_Layers), layer) == std::end(m_Layers))
446         {
447             layer->Reparent(*this, m_Layers.end());
448             m_LayersInOrder = false;
449         }
450     });
451 
452     ReplaceSubgraphConnections(subgraph, substituteSubgraph);
453     EraseSubgraphLayers(subgraph);
454     TopologicalSort();
455 }
456 
ReplaceSubgraphConnections(const SubgraphView & subgraph,IConnectableLayer * substituteLayer)457 void Graph::ReplaceSubgraphConnections(const SubgraphView& subgraph, IConnectableLayer* substituteLayer)
458 {
459     ARMNN_ASSERT(substituteLayer != nullptr);
460 
461     // Create a new sub-graph with only the given layer, using
462     // the given sub-graph as a reference of which parent graph to use
463     SubgraphView substituteSubgraph(substituteLayer);
464     ReplaceSubgraphConnections(subgraph, substituteSubgraph);
465 }
466 
ReplaceSubgraphConnections(const SubgraphView & subgraph,const SubgraphView & substituteSubgraph)467 void Graph::ReplaceSubgraphConnections(const SubgraphView& subgraph, const SubgraphView& substituteSubgraph)
468 {
469     ARMNN_ASSERT_MSG(!substituteSubgraph.GetLayers().empty(), "New sub-graph used for substitution must not be empty");
470 
471     const SubgraphView::Layers& substituteSubgraphLayers = substituteSubgraph.GetLayers();
472     std::for_each(substituteSubgraphLayers.begin(), substituteSubgraphLayers.end(), [&](Layer* layer)
473     {
474         IgnoreUnused(layer);
475         ARMNN_ASSERT_MSG(std::find(m_Layers.begin(), m_Layers.end(), layer) != m_Layers.end(),
476                          "Substitute layer is not a member of graph");
477     });
478 
479     const SubgraphView::InputSlots& subgraphInputSlots = subgraph.GetInputSlots();
480     const SubgraphView::OutputSlots& subgraphOutputSlots = subgraph.GetOutputSlots();
481 
482     unsigned int subgraphNumInputSlots = armnn::numeric_cast<unsigned int>(subgraphInputSlots.size());
483     unsigned int subgraphNumOutputSlots = armnn::numeric_cast<unsigned int>(subgraphOutputSlots.size());
484 
485     const SubgraphView::InputSlots& substituteSubgraphInputSlots = substituteSubgraph.GetInputSlots();
486     const SubgraphView::OutputSlots& substituteSubgraphOutputSlots = substituteSubgraph.GetOutputSlots();
487 
488     ARMNN_ASSERT(subgraphNumInputSlots == substituteSubgraphInputSlots.size());
489     ARMNN_ASSERT(subgraphNumOutputSlots == substituteSubgraphOutputSlots.size());
490 
491     // Disconnect the sub-graph and replace it with the substitute sub-graph
492 
493     // Step 1: process input slots
494     for (unsigned int inputSlotIdx = 0; inputSlotIdx < subgraphNumInputSlots; ++inputSlotIdx)
495     {
496         InputSlot* subgraphInputSlot = subgraphInputSlots.at(inputSlotIdx);
497         ARMNN_ASSERT(subgraphInputSlot);
498 
499         IOutputSlot* connectedOutputSlot = subgraphInputSlot->GetConnection();
500         ARMNN_ASSERT(connectedOutputSlot);
501         connectedOutputSlot->Disconnect(*subgraphInputSlot);
502 
503         IInputSlot* substituteInputSlot = substituteSubgraphInputSlots.at(inputSlotIdx);
504         ARMNN_ASSERT(substituteInputSlot);
505         connectedOutputSlot->Connect(*substituteInputSlot);
506     }
507 
508     // Step 2: process output slots
509     for(unsigned int outputSlotIdx = 0; outputSlotIdx < subgraphNumOutputSlots; ++outputSlotIdx)
510     {
511         OutputSlot* subgraphOutputSlot = subgraphOutputSlots.at(outputSlotIdx);
512         ARMNN_ASSERT(subgraphOutputSlot);
513 
514         OutputSlot* substituteOutputSlot = substituteSubgraphOutputSlots.at(outputSlotIdx);
515         ARMNN_ASSERT(substituteOutputSlot);
516         subgraphOutputSlot->MoveAllConnections(*substituteOutputSlot);
517     }
518 }
519 
EraseSubgraphLayers(SubgraphView & subgraph)520 void Graph::EraseSubgraphLayers(SubgraphView &subgraph)
521 {
522     for (auto layer : subgraph.GetLayers())
523     {
524         EraseLayer(layer);
525     }
526     subgraph.Clear();
527 }
528 
InferTensorInfos()529 void Graph::InferTensorInfos()
530 {
531     for (auto&& layer : TopologicalSort())
532     {
533         for (auto&& input : layer->GetInputSlots())
534         {
535             const IOutputSlot* source = input.GetConnectedOutputSlot();
536             if (source == NULL)
537             {
538                 std::ostringstream message;
539                 message << "Input not connected on "
540                         << GetLayerTypeAsCString(layer->GetType())
541                         << " layer \""
542                         << layer->GetName()
543                         << "\"";
544                 throw LayerValidationException(message.str());
545             }
546 
547             if (!source->IsTensorInfoSet())
548             {
549                 throw LayerValidationException("All inputs must have the TensorInfo set at this point.");
550             }
551 
552             if (layer->m_ShapeInferenceMethod == ShapeInferenceMethod::ValidateOnly)
553             {
554                 layer->ValidateTensorShapesFromInputs();
555             }
556         }
557     }
558 }
559 
560 } // namespace armnn
561