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, ®istry, 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