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 #include <ProfilingService.hpp>
9 #include <armnn/utility/NumericCast.hpp>
10 #include <backendsCommon/WorkloadData.hpp>
11 #include <backendsCommon/CpuTensorHandle.hpp>
12
13 #include <fmt/format.h>
14
15 #include <numeric>
16
17 namespace armnn
18 {
19
Insert(Layer & layer)20 void InputSlot::Insert(Layer& layer)
21 {
22 ARMNN_ASSERT(layer.GetNumOutputSlots() == 1);
23
24 OutputSlot* const prevSlot = GetConnectedOutputSlot();
25
26 if (prevSlot != nullptr)
27 {
28 // Disconnects parent from this.
29 prevSlot->Disconnect(*this);
30
31 // Connects inserted layer to parent.
32 ARMNN_ASSERT(layer.GetNumInputSlots() == 1);
33 int idx = prevSlot->Connect(layer.GetInputSlot(0));
34 prevSlot->SetEdgeStrategy(armnn::numeric_cast<unsigned int>(idx), EdgeStrategy::Undefined);
35
36 // Sets tensor info for inserted layer.
37 const TensorInfo& tensorInfo = prevSlot->GetTensorInfo();
38 layer.GetOutputHandler().SetTensorInfo(tensorInfo);
39 }
40
41 // Connects inserted layer to this.
42 layer.GetOutputSlot(0).Connect(*this);
43 layer.GetOutputSlot(0).SetEdgeStrategy(0, EdgeStrategy::Undefined);
44 }
45
GetConnection(unsigned int index) const46 const InputSlot* OutputSlot::GetConnection(unsigned int index) const
47 {
48 ValidateConnectionIndex(index);
49 return m_Connections[index];
50 }
51
GetConnection(unsigned int index)52 InputSlot* OutputSlot::GetConnection(unsigned int index)
53 {
54 ValidateConnectionIndex(index);
55 return m_Connections[index];
56 }
57
SetTensorInfo(const TensorInfo & tensorInfo)58 void OutputSlot::SetTensorInfo(const TensorInfo& tensorInfo)
59 {
60 GetOutputHandler().SetTensorInfo(tensorInfo);
61 }
62
GetTensorInfo() const63 const TensorInfo& OutputSlot::GetTensorInfo() const
64 {
65 return GetOutputHandler().GetTensorInfo();
66 }
67
IsTensorInfoSet() const68 bool OutputSlot::IsTensorInfoSet() const
69 {
70 if (GetOwningLayer().GetShapeInferenceMethod() == ShapeInferenceMethod::InferAndValidate)
71 {
72 GetOwningLayer().ValidateTensorShapesFromInputs();
73 }
74 return GetOutputHandler().IsTensorInfoSet();
75 }
76
ValidateTensorShape(const TensorShape & shape) const77 bool OutputSlot::ValidateTensorShape(const TensorShape& shape) const
78 {
79 ARMNN_ASSERT_MSG(IsTensorInfoSet(), "TensorInfo must be set in order to validate the shape.");
80 return shape == m_OutputHandler.GetTensorInfo().GetShape();
81 }
82
Connect(InputSlot & destination)83 int OutputSlot::Connect(InputSlot& destination)
84 {
85 destination.SetConnection(this);
86 m_Connections.push_back(&destination);
87 m_EdgeStrategies.push_back(EdgeStrategy::Undefined);
88 return armnn::numeric_cast<int>(m_Connections.size() - 1);
89 }
90
Disconnect(InputSlot & slot)91 void OutputSlot::Disconnect(InputSlot& slot)
92 {
93 slot.SetConnection(nullptr);
94 auto it = std::find(m_Connections.begin(), m_Connections.end(), &slot);
95
96 if (it == m_Connections.end())
97 {
98 return;
99 }
100
101 auto idx = std::distance(m_Connections.begin(), it);
102 m_Connections.erase(std::remove(m_Connections.begin(), m_Connections.end(), &slot), m_Connections.end());
103
104 m_EdgeStrategies.erase(m_EdgeStrategies.begin() + idx);
105 }
106
DisconnectAll()107 void OutputSlot::DisconnectAll()
108 {
109 while (GetNumConnections() > 0)
110 {
111 InputSlot& connection = *GetConnection(0);
112 Disconnect(connection);
113 }
114 }
115
MoveAllConnections(OutputSlot & destination)116 void OutputSlot::MoveAllConnections(OutputSlot& destination)
117 {
118 while (GetNumConnections() > 0)
119 {
120 ARMNN_ASSERT_MSG(m_EdgeStrategies[0] == EdgeStrategy::Undefined,
121 "Cannot move connections once memory strategies have be established.");
122
123 InputSlot& connection = *GetConnection(0);
124 Disconnect(connection);
125 destination.Connect(connection);
126 destination.GetOutputHandler().SetTensorInfo(GetOutputHandler().GetTensorInfo());
127 }
128 }
129
CalculateIndexOnOwner() const130 unsigned int OutputSlot::CalculateIndexOnOwner() const
131 {
132 for (unsigned int i = 0; i < GetOwningLayer().GetNumOutputSlots(); i++)
133 {
134 if (GetOwningLayer().GetOutputSlot(i) == (*this))
135 {
136 return i;
137 }
138 }
139 ARMNN_ASSERT_MSG(false, "Did not find slot on owner.");
140 return 0; // Error
141 }
142
operator ==(const OutputSlot & other) const143 bool OutputSlot::operator==(const OutputSlot& other) const
144 {
145 bool isSame = other.GetNumConnections() == GetNumConnections();
146 if (!isSame)
147 {
148 return false;
149 }
150
151 for (unsigned int i = 0; i < GetNumConnections(); i++)
152 {
153 isSame &= other.GetConnection(i) == GetConnection(i);
154 }
155 return isSame;
156 }
157
ValidateConnectionIndex(unsigned int index) const158 void OutputSlot::ValidateConnectionIndex(unsigned int index) const
159 {
160 if (armnn::numeric_cast<std::size_t>(index) >= m_Connections.size())
161 {
162 throw InvalidArgumentException((fmt::format("GetConnection: Invalid index {} provided", index)));
163 }
164 }
165
GetOwningLayerGuid() const166 LayerGuid OutputSlot::GetOwningLayerGuid() const
167 {
168 return GetOwningLayer().GetGuid();
169 }
170
SetTensorHandleFactory(const ITensorHandleFactory::FactoryId & id)171 void OutputSlot::SetTensorHandleFactory(const ITensorHandleFactory::FactoryId& id)
172 {
173 m_TensorHandleFactoryId = id;
174 }
175
GetTensorHandleFactoryId() const176 ITensorHandleFactory::FactoryId OutputSlot::GetTensorHandleFactoryId() const
177 {
178 return m_TensorHandleFactoryId;
179 }
180
SetEdgeStrategy(unsigned int connectionIndex,EdgeStrategy strategy)181 void OutputSlot::SetEdgeStrategy(unsigned int connectionIndex, EdgeStrategy strategy)
182 {
183 m_EdgeStrategies[connectionIndex] = strategy;
184 }
185
GetEdgeStrategyForConnection(unsigned int connectionIdx) const186 EdgeStrategy OutputSlot::GetEdgeStrategyForConnection(unsigned int connectionIdx) const
187 {
188 return m_EdgeStrategies[connectionIdx];
189 }
190
Layer(unsigned int numInputSlots,unsigned int numOutputSlots,LayerType type,DataLayout layout,const char * name)191 Layer::Layer(unsigned int numInputSlots,
192 unsigned int numOutputSlots,
193 LayerType type,
194 DataLayout layout,
195 const char* name)
196 : m_OutputHandlers(numOutputSlots)
197 , m_ShapeInferenceMethod(ShapeInferenceMethod::ValidateOnly)
198 , m_LayerName(name ? name : "")
199 , m_Type(type)
200 , m_BackendId()
201 , m_BackendHint(EmptyOptional())
202 , m_Guid(profiling::ProfilingService::GetNextGuid())
203 {
204 IgnoreUnused(layout);
205 m_InputSlots.reserve(numInputSlots);
206 for (unsigned int i = 0; i < numInputSlots; ++i)
207 {
208 m_InputSlots.emplace_back(*this, i);
209 }
210
211 m_OutputSlots.reserve(numOutputSlots);
212 for (unsigned int i = 0; i < numOutputSlots; ++i)
213 {
214 m_OutputSlots.emplace_back(*this, m_OutputHandlers[i]);
215 }
216 }
217
Layer(unsigned int numInputSlots,unsigned int numOutputSlots,LayerType type,const char * name)218 Layer::Layer(unsigned int numInputSlots,
219 unsigned int numOutputSlots,
220 LayerType type,
221 const char* name)
222 : Layer(numInputSlots, numOutputSlots, type, DataLayout::NCHW, name)
223 {
224 }
225
CollectWorkloadInputs(WorkloadDataCollector & dataCollector) const226 void Layer::CollectWorkloadInputs(WorkloadDataCollector& dataCollector) const
227 {
228 for (auto&& inputSlot : GetInputSlots())
229 {
230 // The graph must be well-formed at this point.
231 ARMNN_ASSERT(inputSlot.GetConnection());
232 const OutputHandler& outputHandler = inputSlot.GetConnectedOutputSlot()->GetOutputHandler();
233 dataCollector.Push(outputHandler.GetData(), outputHandler.GetTensorInfo());
234 }
235 }
236
CollectWorkloadOutputs(WorkloadDataCollector & dataCollector) const237 void Layer::CollectWorkloadOutputs(WorkloadDataCollector& dataCollector) const
238 {
239 for (auto&& outputHandler : m_OutputHandlers)
240 {
241 outputHandler.CollectWorkloadOutputs(dataCollector);
242 }
243 }
244
SetAdditionalInfo(QueueDescriptor & descriptor) const245 void Layer::SetAdditionalInfo(QueueDescriptor& descriptor) const
246 {
247 descriptor.m_AdditionalInfoObject = m_AdditionalInfoObject.get();
248 }
249
CreateTensorHandles(const TensorHandleFactoryRegistry & registry,const IWorkloadFactory & workloadFactory,const bool IsMemoryManaged)250 void Layer::CreateTensorHandles(const TensorHandleFactoryRegistry& registry,
251 const IWorkloadFactory& workloadFactory,
252 const bool IsMemoryManaged)
253 {
254 for (unsigned int idx=0; idx < GetNumOutputSlots(); idx++)
255 {
256
257 OutputSlot& slot = GetOutputSlot(idx);
258 ITensorHandleFactory::FactoryId factoryId = slot.GetTensorHandleFactoryId();
259
260 OutputHandler& handler = GetOutputHandler(idx);
261 if (factoryId == ITensorHandleFactory::LegacyFactoryId)
262 {
263 handler.CreateTensorHandles(workloadFactory, IsMemoryManaged);
264 }
265 else
266 {
267 ITensorHandleFactory* handleFactory = registry.GetFactory(factoryId);
268 ARMNN_ASSERT(handleFactory);
269 handler.CreateTensorHandles(*handleFactory, IsMemoryManaged);
270 }
271 }
272 }
273
ReleaseConstantData()274 void Layer::ReleaseConstantData()
275 {
276 // Now free up the static data.
277 OperateOnConstantTensors([](std::unique_ptr<ScopedCpuTensorHandle>& handle)
278 {
279 handle.reset(nullptr);
280 });
281 }
282
GetDataType() const283 DataType Layer::GetDataType() const
284 {
285 if (GetNumInputSlots() > 0) // Ignore the input layer.
286 {
287 return GetInputSlot(0).GetConnection()->GetTensorInfo().GetDataType();
288 }
289 return GetOutputSlot(0).GetTensorInfo().GetDataType();
290 }
291
ResetPriority() const292 void Layer::ResetPriority() const
293 {
294 m_Priority = 0;
295 m_Visiting = false;
296 }
297
GetPriority() const298 LayerPriority Layer::GetPriority() const
299 {
300 constexpr LayerPriority inputPrio = std::numeric_limits<LayerPriority>::lowest();
301 constexpr LayerPriority outputPrio = std::numeric_limits<LayerPriority>::max();
302
303 if (GetType() == LayerType::Input)
304 {
305 m_Priority = inputPrio;
306 }
307 else if (GetType() == LayerType::Output)
308 {
309 m_Priority = outputPrio;
310 }
311 else if (m_Priority == 0)
312 {
313 if (m_Visiting)
314 {
315 throw GraphValidationException("Graph has circular dependencies: cannot walk");
316 }
317
318 auto maxPrio = [](const LayerPriority prio, const InputSlot& slot) -> LayerPriority
319 {
320 const OutputSlot *outputSlot = slot.GetConnectedOutputSlot();
321 if (outputSlot)
322 {
323 const Layer& input = outputSlot->GetOwningLayer();
324 return std::max(prio, input.GetPriority());
325 }
326 else
327 {
328 // unconnected input slot
329 return prio;
330 }
331 };
332
333 m_Visiting = true;
334 LayerPriority parentPrio = std::accumulate(GetInputSlots().cbegin(), GetInputSlots().cend(), 0U, maxPrio);
335 m_Visiting = false;
336
337 if (parentPrio >= outputPrio)
338 {
339 throw GraphValidationException("Graph has too many edges");
340 }
341
342 m_Priority = parentPrio + 1U;
343 }
344
345 return m_Priority;
346 }
347
VerifyLayerConnections(unsigned int expectedConnections,const CheckLocation & location) const348 void Layer::VerifyLayerConnections(unsigned int expectedConnections, const CheckLocation& location) const
349 {
350 ARMNN_ASSERT(GetNumInputSlots() == expectedConnections);
351
352 for (unsigned int i=0; i<expectedConnections; ++i)
353 {
354 if (GetInputSlot(i).GetConnection() == nullptr)
355 {
356 throw LayerValidationException(
357 fmt::format("Input connection #{0} must be connected "
358 "for {1} layer {2} {3}",
359 i,
360 GetLayerTypeAsCString(this->GetType()),
361 GetNameStr(),
362 location.AsString()));
363 }
364 }
365 }
366
InferOutputShapes(const std::vector<TensorShape> & inputShapes) const367 std::vector<TensorShape> Layer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const
368 {
369 ARMNN_ASSERT(GetNumInputSlots() != 0);
370 ARMNN_ASSERT(GetNumOutputSlots() != 0);
371
372 // By default we return what we got, meaning the output shape(s) are the same as the input(s).
373 // This only works if the number of inputs and outputs are the same. Since we are in the Layer
374 // base class, this means the implementation needs to be overridden in the specific layers for
375 // the other cases. So the missing implementation justifies the UnimplementedException.
376
377 if (GetNumInputSlots() != GetNumOutputSlots())
378 {
379 throw UnimplementedException(
380 fmt::format("Default implementation for InferOutputShapes can only be used for "
381 "layers with the same number of input and output slots. This doesn't "
382 "hold for {0} layer {1} (#inputs={2} #outputs={3}) {4}",
383 GetLayerTypeAsCString(this->GetType()),
384 GetNameStr(),
385 GetNumInputSlots(),
386 GetNumOutputSlots(),
387 CHECK_LOCATION().AsString()));
388 }
389 return inputShapes;
390 }
391
ValidateAndCopyShape(const TensorShape & outputShape,const TensorShape & inferredShape,const ShapeInferenceMethod shapeInferenceMethod,const std::string & layerName,const unsigned int outputSlotIndex)392 void Layer::ValidateAndCopyShape(const TensorShape& outputShape,
393 const TensorShape& inferredShape,
394 const ShapeInferenceMethod shapeInferenceMethod,
395 const std::string& layerName,
396 const unsigned int outputSlotIndex)
397 {
398 if (shapeInferenceMethod == ShapeInferenceMethod::ValidateOnly)
399 {
400 ConditionalThrowIfNotEqual<LayerValidationException>(
401 layerName + ": TensorShape set on OutputSlot[0] does not match the inferred shape.",
402 outputShape,
403 inferredShape);
404 return;
405 }
406
407 if (outputShape.GetDimensionality() == Dimensionality::Specified)
408 {
409 for (unsigned int i = 0; i < outputShape.GetNumDimensions(); ++i)
410 {
411 if (outputShape.GetDimensionSpecificity(i) && outputShape[i] != inferredShape[i])
412 {
413 std::stringstream ss;
414 ss << layerName << ": TensorShape set on OutputSlot[" << outputSlotIndex <<
415 "] does not match the inferred shape at dimension index [";
416 ss << i << "] " << outputShape << " != " << inferredShape;
417 throw LayerValidationException(ss.str());
418 }
419 }
420 }
421
422 TensorInfo info = GetOutputSlot(outputSlotIndex).GetTensorInfo();
423
424 armnn::TensorInfo inferredTensorInfo(inferredShape,
425 info.GetDataType(),
426 info.GetQuantizationScale(),
427 info.GetQuantizationOffset());
428
429 GetOutputSlot(outputSlotIndex).SetTensorInfo(inferredTensorInfo);
430 }
431
VerifyShapeInferenceType(const TensorShape & outputShape,ShapeInferenceMethod shapeInferenceMethod)432 void Layer::VerifyShapeInferenceType(const TensorShape& outputShape, ShapeInferenceMethod shapeInferenceMethod)
433 {
434 if (shapeInferenceMethod == ShapeInferenceMethod::ValidateOnly)
435 {
436 ConditionalThrow<LayerValidationException>(
437 outputShape.GetDimensionality() != Dimensionality::NotSpecified,
438 "Dimensionality can not be NotSpecified while using ShapeInferenceMethod::ValidateOnly");
439
440 ConditionalThrow<LayerValidationException>(
441 outputShape.AreAllDimensionsSpecified(),
442 "Unspecified dimension while using ShapeInferenceMethod::ValidateOnly");
443 }
444 }
445
SerializeLayerParameters(ParameterStringifyFunction & fn) const446 void Layer::SerializeLayerParameters(ParameterStringifyFunction& fn) const
447 {
448 std::string guid = std::to_string(m_Guid);
449 std::string layerType = GetLayerTypeAsCString(m_Type);
450 std::string backendId = std::string(m_BackendId);
451 if (!(guid.compare("") == 0) && !guid.empty())
452 {
453 fn("Guid", guid);
454 }
455 if(!(m_LayerName.compare("") == 0) && !m_LayerName.empty())
456 {
457 fn("LayerName",m_LayerName);
458 }
459 if(!(layerType.compare("") == 0) && !layerType.empty())
460 {
461 fn("LayerType",layerType);
462 }
463 if(!(backendId.compare("") == 0) && !backendId.empty())
464 {
465 fn("BackendID",backendId);
466 }
467 std::shared_ptr<ActivationDescriptor>
468 activationDescPtr = GetAdditionalInformation<ActivationDescriptor>();
469
470 if (activationDescPtr)
471 {
472 StringifyLayerParameters<ActivationDescriptor>::Serialize(fn, *activationDescPtr.get());
473 }
474 }
475
476 } // namespace armnn
477