1 //
2 // Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "Layer.hpp"
6
7 #include "Graph.hpp"
8
9 #include <armnn/backends/TensorHandle.hpp>
10 #include <armnn/backends/WorkloadData.hpp>
11
12 #include <armnn/utility/NumericCast.hpp>
13
14 #include <armnnUtils/TensorUtils.hpp>
15
16 #include <client/include/IProfilingService.hpp>
17
18 #include <fmt/format.h>
19
20 #include <numeric>
21
22 namespace armnn
23 {
24
25 // Instantiate the static member variable
26 NullDescriptor Layer::m_NullDescriptor;
27
AssertNumberOfInputSlots(Layer & layer)28 void AssertNumberOfInputSlots(Layer& layer)
29 {
30 switch (layer.GetType())
31 {
32 case LayerType::Convolution2d:
33 case LayerType::DepthwiseConvolution2d:
34 case LayerType::FullyConnected:
35 {
36 ARMNN_ASSERT(layer.GetNumInputSlots() == 2 ||
37 layer.GetNumInputSlots() == 3);
38 break;
39 }
40 default:
41 {
42 ARMNN_ASSERT(layer.GetNumInputSlots() == 1);
43 break;
44 }
45 }
46 }
47
Insert(Layer & layer)48 void InputSlot::Insert(Layer& layer)
49 {
50 ARMNN_ASSERT(layer.GetNumOutputSlots() == 1);
51
52 OutputSlot* const prevSlot = GetConnectedOutputSlot();
53
54 if (prevSlot != nullptr)
55 {
56 // Disconnects parent from this.
57 prevSlot->Disconnect(*this);
58
59 AssertNumberOfInputSlots(layer);
60
61 // Connects inserted layer to parent.
62 int idx = prevSlot->Connect(layer.GetInputSlot(0));
63 prevSlot->SetEdgeStrategy(armnn::numeric_cast<unsigned int>(idx), EdgeStrategy::Undefined);
64
65 // Sets tensor info for inserted layer.
66 const TensorInfo& tensorInfo = prevSlot->GetTensorInfo();
67 layer.GetOutputHandler().SetTensorInfo(tensorInfo);
68 }
69
70 // Connects inserted layer to this.
71 layer.GetOutputSlot(0).Connect(*this);
72 layer.GetOutputSlot(0).SetEdgeStrategy(0, EdgeStrategy::Undefined);
73 }
74
GetConnection(unsigned int index) const75 const InputSlot* OutputSlot::GetConnection(unsigned int index) const
76 {
77 ValidateConnectionIndex(index);
78 return m_Connections[index];
79 }
80
GetConnection(unsigned int index)81 InputSlot* OutputSlot::GetConnection(unsigned int index)
82 {
83 ValidateConnectionIndex(index);
84 return m_Connections[index];
85 }
86
SetTensorInfo(const TensorInfo & tensorInfo)87 void OutputSlot::SetTensorInfo(const TensorInfo& tensorInfo)
88 {
89 GetOutputHandler().SetTensorInfo(tensorInfo);
90 }
91
GetTensorInfo() const92 const TensorInfo& OutputSlot::GetTensorInfo() const
93 {
94 return GetOutputHandler().GetTensorInfo();
95 }
96
IsTensorInfoSet() const97 bool OutputSlot::IsTensorInfoSet() const
98 {
99 if (GetOwningLayer().GetShapeInferenceMethod() == ShapeInferenceMethod::InferAndValidate)
100 {
101 GetOwningLayer().ValidateTensorShapesFromInputs();
102 }
103 return GetOutputHandler().IsTensorInfoSet();
104 }
105
ValidateTensorShape(const TensorShape & shape) const106 bool OutputSlot::ValidateTensorShape(const TensorShape& shape) const
107 {
108 ARMNN_ASSERT_MSG(IsTensorInfoSet(), "TensorInfo must be set in order to validate the shape.");
109 return shape == m_OutputHandler.GetTensorInfo().GetShape();
110 }
111
Connect(InputSlot & destination)112 int OutputSlot::Connect(InputSlot& destination)
113 {
114 destination.SetConnection(this);
115 m_Connections.push_back(&destination);
116 m_EdgeStrategies.push_back(EdgeStrategy::Undefined);
117 return armnn::numeric_cast<int>(m_Connections.size() - 1);
118 }
119
Disconnect(InputSlot & slot)120 void OutputSlot::Disconnect(InputSlot& slot)
121 {
122 slot.SetConnection(nullptr);
123 auto it = std::find(m_Connections.begin(), m_Connections.end(), &slot);
124
125 if (it == m_Connections.end())
126 {
127 return;
128 }
129
130 auto idx = std::distance(m_Connections.begin(), it);
131 m_Connections.erase(std::remove(m_Connections.begin(), m_Connections.end(), &slot), m_Connections.end());
132
133 m_EdgeStrategies.erase(m_EdgeStrategies.begin() + idx);
134 }
135
DisconnectAll()136 void OutputSlot::DisconnectAll()
137 {
138 while (GetNumConnections() > 0)
139 {
140 InputSlot& connection = *GetConnection(0);
141 Disconnect(connection);
142 }
143 }
144
MoveAllConnections(OutputSlot & destination)145 void OutputSlot::MoveAllConnections(OutputSlot& destination)
146 {
147 while (GetNumConnections() > 0)
148 {
149 ARMNN_ASSERT_MSG(m_EdgeStrategies[0] == EdgeStrategy::Undefined,
150 "Cannot move connections once memory strategies have be established.");
151
152 InputSlot& connection = *GetConnection(0);
153 Disconnect(connection);
154 destination.Connect(connection);
155 destination.GetOutputHandler().SetTensorInfo(GetOutputHandler().GetTensorInfo());
156 }
157 }
158
CalculateIndexOnOwner() const159 unsigned int OutputSlot::CalculateIndexOnOwner() const
160 {
161 for (unsigned int i = 0; i < GetOwningLayer().GetNumOutputSlots(); i++)
162 {
163 if (GetOwningLayer().GetOutputSlot(i) == (*this))
164 {
165 return i;
166 }
167 }
168 ARMNN_ASSERT_MSG(false, "Did not find slot on owner.");
169 return 0; // Error
170 }
171
operator ==(const OutputSlot & other) const172 bool OutputSlot::operator==(const OutputSlot& other) const
173 {
174 bool isSame = other.GetNumConnections() == GetNumConnections();
175 if (!isSame)
176 {
177 return false;
178 }
179
180 for (unsigned int i = 0; i < GetNumConnections(); i++)
181 {
182 isSame &= other.GetConnection(i) == GetConnection(i);
183 }
184 return isSame;
185 }
186
ValidateConnectionIndex(unsigned int index) const187 void OutputSlot::ValidateConnectionIndex(unsigned int index) const
188 {
189 if (armnn::numeric_cast<std::size_t>(index) >= m_Connections.size())
190 {
191 throw InvalidArgumentException((fmt::format("GetConnection: Invalid index {} provided", index)));
192 }
193 }
194
GetOwningLayerGuid() const195 LayerGuid OutputSlot::GetOwningLayerGuid() const
196 {
197 return GetOwningLayer().GetGuid();
198 }
199
SetTensorHandleFactory(const ITensorHandleFactory::FactoryId & id)200 void OutputSlot::SetTensorHandleFactory(const ITensorHandleFactory::FactoryId& id)
201 {
202 m_TensorHandleFactoryId = id;
203 }
204
GetTensorHandleFactoryId() const205 ITensorHandleFactory::FactoryId OutputSlot::GetTensorHandleFactoryId() const
206 {
207 return m_TensorHandleFactoryId;
208 }
209
SetEdgeStrategy(unsigned int connectionIndex,EdgeStrategy strategy)210 void OutputSlot::SetEdgeStrategy(unsigned int connectionIndex, EdgeStrategy strategy)
211 {
212 m_EdgeStrategies[connectionIndex] = strategy;
213 }
214
GetEdgeStrategyForConnection(unsigned int connectionIdx) const215 EdgeStrategy OutputSlot::GetEdgeStrategyForConnection(unsigned int connectionIdx) const
216 {
217 return m_EdgeStrategies[connectionIdx];
218 }
219
Layer(unsigned int numInputSlots,unsigned int numOutputSlots,LayerType type,DataLayout layout,const char * name)220 Layer::Layer(unsigned int numInputSlots,
221 unsigned int numOutputSlots,
222 LayerType type,
223 DataLayout layout,
224 const char* name)
225 : m_OutputHandlers(numOutputSlots)
226 , m_ShapeInferenceMethod(ShapeInferenceMethod::ValidateOnly)
227 , m_LayerName(name ? name : "")
228 , m_Type(type)
229 , m_BackendId()
230 , m_BackendHint(EmptyOptional())
231 , m_Guid(arm::pipe::IProfilingService::GetNextGuid())
232 {
233 IgnoreUnused(layout);
234 m_InputSlots.reserve(numInputSlots);
235 for (unsigned int i = 0; i < numInputSlots; ++i)
236 {
237 m_InputSlots.emplace_back(*this, i);
238 }
239
240 m_OutputSlots.reserve(numOutputSlots);
241 for (unsigned int i = 0; i < numOutputSlots; ++i)
242 {
243 m_OutputSlots.emplace_back(*this, m_OutputHandlers[i]);
244 }
245 }
246
Layer(unsigned int numInputSlots,unsigned int numOutputSlots,LayerType type,const char * name)247 Layer::Layer(unsigned int numInputSlots,
248 unsigned int numOutputSlots,
249 LayerType type,
250 const char* name)
251 : Layer(numInputSlots, numOutputSlots, type, DataLayout::NCHW, name)
252 {
253 }
254
CollectWorkloadInputs(WorkloadDataCollector & dataCollector) const255 void Layer::CollectWorkloadInputs(WorkloadDataCollector& dataCollector) const
256 {
257 for (auto&& inputSlot : GetInputSlots())
258 {
259 // The graph must be well-formed at this point.
260 ARMNN_ASSERT(inputSlot.GetConnection());
261 const OutputHandler& outputHandler = inputSlot.GetConnectedOutputSlot()->GetOutputHandler();
262 dataCollector.Push(outputHandler.GetData(), outputHandler.GetTensorInfo());
263 }
264 }
265
CollectWorkloadOutputs(WorkloadDataCollector & dataCollector) const266 void Layer::CollectWorkloadOutputs(WorkloadDataCollector& dataCollector) const
267 {
268 for (auto&& outputHandler : m_OutputHandlers)
269 {
270 outputHandler.CollectWorkloadOutputs(dataCollector);
271 }
272 }
273
SetAdditionalInfo(QueueDescriptor & descriptor) const274 void Layer::SetAdditionalInfo(QueueDescriptor& descriptor) const
275 {
276 descriptor.m_AdditionalInfoObject = m_AdditionalInfoObject.get();
277 }
278
CreateTensorHandles(const TensorHandleFactoryRegistry & registry,const IWorkloadFactory & workloadFactory,const bool IsMemoryManaged)279 void Layer::CreateTensorHandles(const TensorHandleFactoryRegistry& registry,
280 const IWorkloadFactory& workloadFactory,
281 const bool IsMemoryManaged)
282 {
283 for (unsigned int idx=0; idx < GetNumOutputSlots(); idx++)
284 {
285
286 OutputSlot& slot = GetOutputSlot(idx);
287 ITensorHandleFactory::FactoryId factoryId = slot.GetTensorHandleFactoryId();
288
289 OutputHandler& handler = GetOutputHandler(idx);
290 if (factoryId == ITensorHandleFactory::LegacyFactoryId)
291 {
292 handler.CreateTensorHandles(workloadFactory, IsMemoryManaged);
293 }
294 else
295 {
296 ITensorHandleFactory* handleFactory;
297 handleFactory = registry.GetFactory(factoryId);
298 ARMNN_ASSERT(handleFactory);
299 handler.CreateTensorHandles(*handleFactory, IsMemoryManaged);
300 }
301 }
302 }
303
ReleaseConstantData()304 void Layer::ReleaseConstantData()
305 {
306 // Now free up the static data.
307 OperateOnConstantTensors([](std::shared_ptr<ConstTensorHandle>& handle)
308 {
309 handle.reset();
310 });
311 }
312
GetDataType() const313 DataType Layer::GetDataType() const
314 {
315 if (GetNumInputSlots() > 0) // Ignore the input layer.
316 {
317 return GetInputSlot(0).GetConnection()->GetTensorInfo().GetDataType();
318 }
319 return GetOutputSlot(0).GetTensorInfo().GetDataType();
320 }
321
ResetPriority() const322 void Layer::ResetPriority() const
323 {
324 m_Priority = 0;
325 m_Visiting = false;
326 }
327
GetPriority() const328 LayerPriority Layer::GetPriority() const
329 {
330 constexpr LayerPriority inputPrio = std::numeric_limits<LayerPriority>::lowest();
331 constexpr LayerPriority outputPrio = std::numeric_limits<LayerPriority>::max();
332
333 if (GetType() == LayerType::Input)
334 {
335 m_Priority = inputPrio;
336 }
337 else if (GetType() == LayerType::Output)
338 {
339 m_Priority = outputPrio;
340 }
341 else if (m_Priority == 0)
342 {
343 if (m_Visiting)
344 {
345 throw GraphValidationException("Graph has circular dependencies: cannot walk");
346 }
347
348 auto maxPrio = [](const LayerPriority prio, const InputSlot& slot) -> LayerPriority
349 {
350 const OutputSlot *outputSlot = slot.GetConnectedOutputSlot();
351 if (outputSlot)
352 {
353 const Layer& input = outputSlot->GetOwningLayer();
354 return std::max(prio, input.GetPriority());
355 }
356 else
357 {
358 // unconnected input slot
359 return prio;
360 }
361 };
362
363 m_Visiting = true;
364 LayerPriority parentPrio = std::accumulate(GetInputSlots().cbegin(), GetInputSlots().cend(), 0U, maxPrio);
365 m_Visiting = false;
366
367 if (parentPrio >= outputPrio)
368 {
369 throw GraphValidationException("Graph has too many edges");
370 }
371
372 m_Priority = parentPrio + 1U;
373 }
374
375 return m_Priority;
376 }
377
VerifyLayerConnections(unsigned int expectedConnections,const CheckLocation & location) const378 void Layer::VerifyLayerConnections(unsigned int expectedConnections, const CheckLocation& location) const
379 {
380 ARMNN_ASSERT(GetNumInputSlots() == expectedConnections);
381
382 for (unsigned int i=0; i<expectedConnections; ++i)
383 {
384 if (GetInputSlot(i).GetConnection() == nullptr)
385 {
386 throw LayerValidationException(
387 fmt::format("Input connection #{0} must be connected "
388 "for {1} layer {2} {3}",
389 i,
390 GetLayerTypeAsCString(this->GetType()),
391 GetNameStr(),
392 location.AsString()));
393 }
394 }
395 }
396
InferOutputShapes(const std::vector<TensorShape> & inputShapes) const397 std::vector<TensorShape> Layer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const
398 {
399 ARMNN_ASSERT(GetNumInputSlots() != 0);
400 ARMNN_ASSERT(GetNumOutputSlots() != 0);
401
402 // By default we return what we got, meaning the output shape(s) are the same as the input(s).
403 // This only works if the number of inputs and outputs are the same. Since we are in the Layer
404 // base class, this means the implementation needs to be overridden in the specific layers for
405 // the other cases. So the missing implementation justifies the UnimplementedException.
406
407 if (GetNumInputSlots() != GetNumOutputSlots())
408 {
409 throw UnimplementedException(
410 fmt::format("Default implementation for InferOutputShapes can only be used for "
411 "layers with the same number of input and output slots. This doesn't "
412 "hold for {0} layer {1} (#inputs={2} #outputs={3}) {4}",
413 GetLayerTypeAsCString(this->GetType()),
414 GetNameStr(),
415 GetNumInputSlots(),
416 GetNumOutputSlots(),
417 CHECK_LOCATION().AsString()));
418 }
419 return inputShapes;
420 }
421
ValidateAndCopyShape(const TensorShape & outputShape,const TensorShape & inferredShape,const ShapeInferenceMethod shapeInferenceMethod,const std::string & layerName,const unsigned int outputSlotIndex)422 void Layer::ValidateAndCopyShape(const TensorShape& outputShape,
423 const TensorShape& inferredShape,
424 const ShapeInferenceMethod shapeInferenceMethod,
425 const std::string& layerName,
426 const unsigned int outputSlotIndex)
427 {
428 if (shapeInferenceMethod == ShapeInferenceMethod::ValidateOnly)
429 {
430 if (m_AllowExpandedDims)
431 {
432 std::vector<unsigned int> outputDims = armnnUtils::SqueezeDims(outputShape);
433 std::vector<unsigned int> inferredDims = armnnUtils::SqueezeDims(inferredShape);
434
435 if (outputDims.size() != inferredDims.size())
436 {
437 std::stringstream ss;
438 ss << layerName << ": TensorShape set on OutputSlot[" << outputSlotIndex <<
439 "] does not match the inferred shape. ";
440 ss << outputShape << " != " << inferredShape;
441 throw LayerValidationException(ss.str());
442 }
443 for (unsigned int i = 0; i < outputDims.size(); ++i)
444 {
445 if (outputDims[i] != inferredDims[i])
446 {
447 std::stringstream ss;
448 ss << layerName << ": TensorShape set on OutputSlot[" << outputSlotIndex <<
449 "] does not match the inferred shape at dimension index [";
450 ss << i << "] " << outputShape << " != " << inferredShape;
451 throw LayerValidationException(ss.str());
452 }
453 }
454 return;
455 }
456 else
457 {
458 ConditionalThrowIfNotEqual<LayerValidationException>(
459 layerName + ": TensorShape set on OutputSlot[0] does not match the inferred shape.",
460 outputShape,
461 inferredShape);
462 return;
463 }
464 }
465
466 if (outputShape.GetDimensionality() == Dimensionality::Specified)
467 {
468 for (unsigned int i = 0; i < outputShape.GetNumDimensions(); ++i)
469 {
470 if (outputShape.GetDimensionSpecificity(i) && outputShape[i] != inferredShape[i])
471 {
472 std::stringstream ss;
473 ss << layerName << ": TensorShape set on OutputSlot[" << outputSlotIndex <<
474 "] does not match the inferred shape at dimension index [";
475 ss << i << "] " << outputShape << " != " << inferredShape;
476 throw LayerValidationException(ss.str());
477 }
478 }
479 }
480
481 TensorInfo info = GetOutputSlot(outputSlotIndex).GetTensorInfo();
482
483 armnn::TensorInfo inferredTensorInfo(inferredShape,
484 info.GetDataType(),
485 info.GetQuantizationScale(),
486 info.GetQuantizationOffset());
487
488 GetOutputSlot(outputSlotIndex).SetTensorInfo(inferredTensorInfo);
489 }
490
VerifyShapeInferenceType(const TensorShape & outputShape,ShapeInferenceMethod shapeInferenceMethod)491 void Layer::VerifyShapeInferenceType(const TensorShape& outputShape, ShapeInferenceMethod shapeInferenceMethod)
492 {
493 if (shapeInferenceMethod == ShapeInferenceMethod::ValidateOnly)
494 {
495 ConditionalThrow<LayerValidationException>(
496 outputShape.GetDimensionality() != Dimensionality::NotSpecified,
497 "Dimensionality can not be NotSpecified while using ShapeInferenceMethod::ValidateOnly");
498
499 ConditionalThrow<LayerValidationException>(
500 outputShape.AreAllDimensionsSpecified(),
501 "Unspecified dimension while using ShapeInferenceMethod::ValidateOnly");
502 }
503 }
504
SerializeLayerParameters(ParameterStringifyFunction & fn) const505 void Layer::SerializeLayerParameters(ParameterStringifyFunction& fn) const
506 {
507 std::string guid = std::to_string(m_Guid);
508 std::string layerType = GetLayerTypeAsCString(m_Type);
509 std::string backendId = std::string(m_BackendId);
510 if (!(guid.compare("") == 0) && !guid.empty())
511 {
512 fn("Guid", guid);
513 }
514 if(!(m_LayerName.compare("") == 0) && !m_LayerName.empty())
515 {
516 fn("LayerName",m_LayerName);
517 }
518 if(!(layerType.compare("") == 0) && !layerType.empty())
519 {
520 fn("LayerType",layerType);
521 }
522 if(!(backendId.compare("") == 0) && !backendId.empty())
523 {
524 fn("BackendID",backendId);
525 }
526 std::shared_ptr<ActivationDescriptor>
527 activationDescPtr = GetAdditionalInformation<ActivationDescriptor>();
528
529 if (activationDescPtr)
530 {
531 StringifyLayerParameters<ActivationDescriptor>::Serialize(fn, *activationDescPtr.get());
532 }
533 }
534
535 // default implementation of ExecuteStrategy
ExecuteStrategy(IStrategy & strategy) const536 void Layer::ExecuteStrategy(IStrategy& strategy) const
537 {
538 strategy.ExecuteStrategy(this, BaseDescriptor(), {}, GetName());
539 }
540
GetConstantTensorsByRef()541 Layer::ConstantTensors Layer::GetConstantTensorsByRef()
542 {
543 const Layer *constThis = const_cast<const Layer*>(this);
544 ConstantTensors res;
545
546 ImmutableConstantTensors immutableData = constThis->GetConstantTensorsByRef();
547 for (auto i : immutableData)
548 {
549 res.push_back(const_cast<std::shared_ptr<ConstTensorHandle>&>(i.get()));
550 }
551 return res;
552 }
553
GetOwningIConnectableLayer() const554 const IConnectableLayer& OutputSlot::GetOwningIConnectableLayer() const
555 {
556 return m_OwningLayer;
557 }
558
GetOwningIConnectableLayer()559 IConnectableLayer& OutputSlot::GetOwningIConnectableLayer()
560 {
561 return m_OwningLayer;
562 }
563
GetOwningIConnectableLayer() const564 const IConnectableLayer& InputSlot::GetOwningIConnectableLayer() const
565 {
566 return m_OwningLayer;
567 }
568
GetOwningIConnectableLayer()569 IConnectableLayer& InputSlot::GetOwningIConnectableLayer()
570 {
571 return m_OwningLayer;
572 }
573
574 } // namespace armnn
575