ArmNN
 24.08
ConcatLayer.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017-2024 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "ConcatLayer.hpp"
6 #include "LayerCloneBase.hpp"
7 
8 #include <armnn/TypesUtils.hpp>
12 
13 #include <queue>
14 
15 namespace armnn
16 {
17 
18 ConcatLayer::ConcatLayer(const OriginsDescriptor& param, const char* name)
19  : LayerWithParameters(param.GetNumViews(), 1, LayerType::Concat, param, name)
20 {
21 }
22 
23 std::unique_ptr<IWorkload> ConcatLayer::CreateWorkload(const IWorkloadFactory& factory) const
24 {
25  ConcatQueueDescriptor descriptor;
26 
27  // Copies the view origins to the descriptor.
28  descriptor.m_ViewOrigins.reserve(m_Param.GetNumViews());
29  for (unsigned int i = 0; i < m_Param.GetNumViews(); ++i)
30  {
31  descriptor.m_ViewOrigins.emplace_back(
32  std::vector<unsigned int>(m_Param.GetViewOrigin(i), m_Param.GetViewOrigin(i) + m_Param.GetNumDimensions()));
33  }
34  SetAdditionalInfo(descriptor);
35 
36  return factory.CreateWorkload(LayerType::Concat, descriptor, PrepInfoAndDesc(descriptor));
37 }
38 
39 template<typename FactoryType>
40 void ConcatLayer::CreateTensors(const TensorHandleFactoryRegistry& registry,
41  const FactoryType& factory,
42  bool isMemoryManaged)
43 {
44  //If sub tensors are supported then the concat
45  //just needs to make sure that the outputs of the prev layer
46  //are made subtensors of the output of the concat layer.
47  m_OutputHandlers[0].CreateTensorHandles(factory, isMemoryManaged);
48 
49  if (factory.SupportsSubTensors())
50  {
51  // check if concat is along the x or y (2 innermost dimensions)
52  uint32_t concatAxis = m_Param.GetConcatAxis();
53  auto numberOfDimensions = m_Param.GetNumDimensions();
54  bool isConcatOnXorY = m_Param.GetNumDimensions() >= 3
55  && ((concatAxis == numberOfDimensions - 1) || (concatAxis == numberOfDimensions - 2));
56 
58 
59  std::queue<ConcatLayer*> m_ConcatLayers;
60 
61  m_ConcatLayers.push(this);
62  while (!m_ConcatLayers.empty())
63  {
64  ConcatLayer* currentLayer = m_ConcatLayers.front();
65  ITensorHandle* parentTensor = currentLayer->GetOutputHandler(0).GetData();
66  const TensorInfo& parentInfo = currentLayer->GetOutputHandler(0).GetTensorInfo();
67  m_ConcatLayers.pop();
68 
69  const unsigned int numInputSlots = currentLayer->GetNumInputSlots();
70 
71  // if concat along x or y (2 innermost dimensions) and the previous layers do not require padding
72  bool canUseSubTensorOnXorY = true;
73  bool isTensorHandleFactory = std::is_same<armnn::ITensorHandleFactory, FactoryType>::value;
74  if (isTensorHandleFactory)
75  {
76  for (unsigned int i = 0; i < numInputSlots; ++i)
77  {
78  OutputSlot* slot = currentLayer->GetInputSlot(i).GetConnectedOutputSlot();
79  ITensorHandleFactory* handleFactory = registry.GetFactory(factoryId);
80  std::vector<Capability> capabilities =
81  handleFactory->GetCapabilities(&(slot->GetOwningLayer()),
82  currentLayer,
84  if (isConcatOnXorY)
85  {
86  canUseSubTensorOnXorY = false;
87  if (capabilities.empty())
88  {
89  canUseSubTensorOnXorY = true;
90  }
91  }
92 
93  // Splitter layer outputs are subtensors on the inputs whereas concat inputs are subtensors on
94  // the output. If the parent is a Splitter layer we cannot use subtensors.
95  if ((PolymorphicDowncast<const Layer*>(&(slot->GetOwningLayer())))->GetType() == LayerType::Splitter
96  && (PolymorphicDowncast<const Layer*>(currentLayer))->GetType() == LayerType::Concat)
97  {
98  canUseSubTensorOnXorY = false;
99  }
100 
101  if (!canUseSubTensorOnXorY)
102  {
103  break;
104  }
105  }
106  }
107  // First go through all the input slots and verify that we can sub-tensor all the inputs.
108  std::vector<std::unique_ptr<ITensorHandle>> subTensors(0);
109  subTensors.reserve(numInputSlots);
110  for (unsigned int i = 0; i < numInputSlots; ++i)
111  {
112  OutputSlot* slot = currentLayer->GetInputSlot(i).GetConnectedOutputSlot();
113  const TensorInfo& info = currentLayer->GetInputSlot(i).GetTensorInfo();
114 
115  auto CreateSubTensor = [&]()
116  {
117  // Make sure:
118  // 1) quantization parameters are in the same space
119  // 2) the same TensorHandleFactory is used for input and Concat layer output
120  // 3) the input does not come from a Constant layer or input layer
121  // 4) the input is only read by this concat layer
122  // 5) if concat along x or y (2 innermost dimensions) and the previous layers do not require padding
123  // 6) neither the inputs nor the output have an Overridden TensorInfo
124  if (slot &&
125  parentInfo.IsTypeSpaceMatch(info) && //(1)
126  factoryId == slot->GetTensorHandleFactoryId() && //(2)
127  slot->GetOwningLayer().GetType() != LayerType::Constant && //(3)
128  slot->GetOwningLayer().GetType() != LayerType::Input && //(3)
129  slot->GetNumConnections() == 1 &&
130  canUseSubTensorOnXorY && //(5)
132  !currentLayer->GetInputSlot(i).IsTensorInfoOverridden()) //(6)
133  {
135  return factory.CreateSubTensorHandle(*parentTensor,
136  info.GetShape(),
137  currentLayer->m_Param.GetViewOrigin(i));
139  }
140  return std::unique_ptr<ITensorHandle>();
141  };
142 
143  auto subTensor = CreateSubTensor();
144  if (!subTensor)
145  {
146  break; //Failed to create a valid sub-tensor, so stop trying with the rest of the inputs.
147  }
148  else
149  {
150  subTensors.push_back(std::move(subTensor)); // store the valid sub-tensor.
151  }
152  }
153 
154  // Ensure that ALL inputs can be substituted with valid sub-tensors
155  if (subTensors.size() < numInputSlots)
156  {
157  continue; // Don't optimize this Concat layer with sub-tensors
158  }
159 
160  // Substitute input tensors with sub-tensors by replacing the output tensors on the connected layers.
161  unsigned int i=0;
162  for (auto& subTensor : subTensors)
163  {
164  OutputSlot* slot = currentLayer->GetInputSlot(i).GetConnectedOutputSlot();
165  OutputHandler& outputHandler = slot->GetOutputHandler();
166 
167  if (!subTensor)
168  {
169  throw armnn::Exception("ConcatLayer: Expected a valid sub-tensor for substitution.");
170  }
171 
172  outputHandler.SetData(std::move(subTensor));
173 
174  Layer& inputLayer = slot->GetOwningLayer();
175  if (inputLayer.GetType() == LayerType::Concat)
176  {
177  // Continue with the substitution if the connected inputs are also concat layers
178  m_ConcatLayers.push(PolymorphicDowncast<ConcatLayer*>(&inputLayer));
179  }
180  ++i;
181  }
182  }
183  }
184 }
185 
187  const IWorkloadFactory& workloadFactory,
188  const bool isMemoryManaged)
189 {
190  OutputSlot& slot = GetOutputSlot(0);
192 
193  if (factoryId == ITensorHandleFactory::LegacyFactoryId)
194  {
195  CreateTensors(registry, workloadFactory, isMemoryManaged);
196  }
197  else
198  {
199  ITensorHandleFactory* handleFactory = registry.GetFactory(factoryId);
200  if (!handleFactory)
201  {
202  throw armnn::NullPointerException("handleFactory is returning a nullptr.");
203  }
204  CreateTensors(registry, *handleFactory, isMemoryManaged);
205  }
206 }
207 
209 {
210  return CloneBase<ConcatLayer>(graph, m_Param, GetName());
211 }
212 
213 std::vector<TensorShape> ConcatLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const
214 {
215  if (inputShapes.size() != m_Param.GetNumViews())
216  {
217  throw armnn::Exception("inputShapes' and m_NumViews' sizes do not match (\""
218  + std::to_string(inputShapes.size()) +
219  "\" vs \""
220  + std::to_string(m_Param.GetNumViews()) + "\")");
221  }
222 
223  unsigned int numDims = m_Param.GetNumDimensions();
224  for (unsigned int i=0; i< inputShapes.size(); i++)
225  {
226  auto& inputShape = inputShapes[i];
227 
228  ConditionalThrowIfNotEqual<LayerValidationException>(
229  "ConcatLayer: Num Dimensions must match all inputs.",
230  numDims,
231  inputShape.GetNumDimensions());
232  }
233 
234  // Finds the bounding box (extents) of all the views.
235  std::vector<unsigned int> extentMin(numDims);
236  std::vector<unsigned int> extentMax(numDims);
237  for (unsigned int i = 0; i < inputShapes.size(); i++)
238  {
239  const uint32_t* origin = m_Param.GetViewOrigin(i);
240  const armnn::TensorShape& shape = inputShapes[i];
241  for (unsigned int d = 0; d < numDims; d++)
242  {
243  extentMin[d] = std::min(extentMin[d], origin[d]);
244  extentMax[d] = std::max(extentMax[d], origin[d] + shape[d]);
245  }
246  }
247 
248  // Checks that the bounding box starts at the origin.
249  if (!std::all_of(extentMin.begin(), extentMin.end(), [](unsigned int s) { return s == 0; }))
250  {
251  throw LayerValidationException("ConcatLayer: there is no view that starts at the origin");
252  }
253 
254  // Checks that there are no overlaps of views (this would lead to undefined output at those locations).
255  // Checks each pair of views against each other
256  // (and doesn't bother to check against self, or check the same pair both ways round).
257  for (unsigned int a = 0; a < inputShapes.size(); a++)
258  {
259  const uint32_t* aOrigin = m_Param.GetViewOrigin(a);
260  const armnn::TensorShape& aShape = inputShapes[a];
261  for (unsigned int b = 0; b < a; b++)
262  {
263  const uint32_t* bOrigin = m_Param.GetViewOrigin(b);
264  const armnn::TensorShape& bShape = inputShapes[b];
265 
266  bool allAxesOverlap = true;
267  for (unsigned int d = 0; d < numDims && allAxesOverlap; d++)
268  {
269  unsigned int a1 = aOrigin[d];
270  unsigned int a2 = aOrigin[d] + aShape[d];
271 
272  unsigned int b1 = bOrigin[d];
273  unsigned int b2 = bOrigin[d] + bShape[d];
274 
275  if (a2 <= b1 || b2 <= a1)
276  {
277  allAxesOverlap = false;
278  }
279  }
280  if (allAxesOverlap)
281  {
282  throw LayerValidationException("ConcatLayer: Some views overlap.");
283  }
284  }
285  }
286 
287  // Checks that there are no "holes", i.e. regions of the output which is not covered by a view.
288  // Because we already checked that there are no overlaps, this can be done simply by checking that
289  // the total 'volume' of the views is the same as the output.
290  unsigned int totalViewsVolume = 0;
291  for (unsigned int i = 0; i < inputShapes.size(); i++)
292  {
293  totalViewsVolume += inputShapes[i].GetNumElements();
294  }
295  unsigned int outputVolume = 1;
296  for (unsigned int d = 0; d < numDims; d++)
297  {
298  outputVolume *= (extentMax[d] - extentMin[d]);
299  }
300 
301  ConditionalThrowIfNotEqual<LayerValidationException>(
302  "ConcatLayer: there are some gaps between views",
303  totalViewsVolume,
304  outputVolume);
305 
306  return std::vector<TensorShape>({ TensorShape({numDims, extentMax.data()}) });
307 }
308 
310 {
311  // Validates Concat layer.
312  ConditionalThrowIfNotEqual<LayerValidationException>(
313  "ConcatLayer: Num Inputs must match num views.",
315  GetNumInputSlots());
316 
318 
319  const TensorShape& outputShape = GetOutputSlot(0).GetTensorInfo().GetShape();
320 
322 
323  std::vector<TensorShape> inputShapes;
324  for (unsigned int i = 0; i < GetNumInputSlots(); ++i)
325  {
326  inputShapes.push_back(GetInputSlot(i).GetTensorInfo().GetShape());
327  }
328 
329  auto inferredShapes = InferOutputShapes(inputShapes);
330 
331  if (inferredShapes.size() != 1)
332  {
333  throw armnn::Exception("inferredShapes has "
334  + std::to_string(inferredShapes.size()) +
335  " elements - should only have 1.");
336  }
337 
338  ValidateAndCopyShape(outputShape, inferredShapes[0], m_ShapeInferenceMethod, "ConcatLayer");
339 }
340 
342 {
343  strategy.ExecuteStrategy(this, GetParameters(), {}, GetName());
344 }
345 
346 } // namespace armnn armnn
armnn::OriginsDescriptor::GetConcatAxis
unsigned int GetConcatAxis() const
Get the concatenation axis value.
Definition: Descriptors.cpp:162
armnn::OriginsDescriptor::GetNumViews
uint32_t GetNumViews() const
Get the number of views.
Definition: Descriptors.cpp:187
armnn::LayerType::Splitter
@ Splitter
armnn::ConcatQueueDescriptor
Definition: WorkloadData.hpp:130
armnn::ConcatLayer
This layer represents a merge operation.
Definition: ConcatLayer.hpp:13
armnn::OutputSlot::GetTensorInfo
const TensorInfo & GetTensorInfo() const override
Definition: Layer.cpp:100
WorkloadData.hpp
armnn::OutputSlot::GetOutputHandler
const OutputHandler & GetOutputHandler() const
Definition: Layer.hpp:139
armnn::TensorHandleFactoryRegistry
Definition: TensorHandleFactoryRegistry.hpp:23
armnn::OutputSlot
Definition: Layer.hpp:100
armnn::ConcatLayer::Clone
ConcatLayer * Clone(Graph &graph) const override
Creates a dynamically-allocated copy of this layer.
Definition: ConcatLayer.cpp:208
TypesUtils.hpp
armnn::TensorHandleFactoryRegistry::GetFactory
ITensorHandleFactory * GetFactory(ITensorHandleFactory::FactoryId id) const
Find a TensorHandleFactory by Id Returns nullptr if not found.
Definition: TensorHandleFactoryRegistry.cpp:39
armnn::TensorInfo
Definition: Tensor.hpp:152
armnn::OriginsDescriptor::GetNumDimensions
uint32_t GetNumDimensions() const
Get the number of dimensions.
Definition: Descriptors.cpp:192
CHECK_LOCATION
#define CHECK_LOCATION()
Definition: Exceptions.hpp:203
armnn::Layer::ValidateAndCopyShape
void ValidateAndCopyShape(const TensorShape &outputShape, const TensorShape &inferredShape, const ShapeInferenceMethod shapeInferenceMethod, const std::string &layerName, const unsigned int outputSlotIndex=0)
Definition: Layer.cpp:457
armnn::ITensorHandle
Definition: ITensorHandle.hpp:16
armnn::Layer::GetOutputSlot
const OutputSlot & GetOutputSlot(unsigned int index=0) const override
Get the const output slot handle by slot index.
Definition: Layer.hpp:339
armnn::ConcatLayer::CreateWorkload
virtual std::unique_ptr< IWorkload > CreateWorkload(const IWorkloadFactory &factory) const override
Makes a workload for the Concat type.
Definition: ConcatLayer.cpp:23
ARMNN_NO_DEPRECATE_WARN_BEGIN
#define ARMNN_NO_DEPRECATE_WARN_BEGIN
Definition: Deprecated.hpp:33
armnn::Layer::m_OutputHandlers
std::vector< OutputHandler > m_OutputHandlers
Definition: Layer.hpp:440
armnn::IStrategy
Definition: IStrategy.hpp:16
armnn::Layer::GetInputSlot
const InputSlot & GetInputSlot(unsigned int index) const override
Get a const input slot handle by slot index.
Definition: Layer.hpp:337
armnn::LayerWithParameters< OriginsDescriptor >::GetParameters
const OriginsDescriptor & GetParameters() const override
Definition: LayerWithParameters.hpp:19
WorkloadFactory.hpp
armnn::OutputHandler::GetData
ITensorHandle * GetData() const
Gets the allocated tensor memory.
Definition: OutputHandler.hpp:46
armnn::Layer::Layer
Layer(unsigned int numInputSlots, unsigned int numOutputSlots, LayerType type, const char *name)
Definition: Layer.cpp:260
armnn::LayerWithParameters
Definition: LayerWithParameters.hpp:14
armnn::Layer::GetName
const char * GetName() const override
Returns the name of the layer.
Definition: Layer.hpp:332
armnn::ITensorHandleFactory::LegacyFactoryId
static const FactoryId LegacyFactoryId
Definition: ITensorHandleFactory.hpp:50
armnn::ITensorHandleFactory::GetCapabilities
virtual std::vector< Capability > GetCapabilities(const IConnectableLayer *layer, const IConnectableLayer *connectedLayer, CapabilityClass capabilityClass)
Definition: ITensorHandleFactory.hpp:93
armnn::InputSlot::GetTensorInfo
const TensorInfo & GetTensorInfo() const override
Gets the TensorInfo for this InputSlot.
Definition: Layer.cpp:614
armnn::TensorShape
Definition: Tensor.hpp:20
armnn::ConcatLayer::InferOutputShapes
std::vector< TensorShape > InferOutputShapes(const std::vector< TensorShape > &inputShapes) const override
By default returns inputShapes if the number of inputs are equal to number of outputs,...
Definition: ConcatLayer.cpp:213
armnn::OutputSlot::GetOwningLayer
Layer & GetOwningLayer() const
Definition: Layer.hpp:132
armnn::LayerWithParameters< OriginsDescriptor >::m_Param
OriginsDescriptor m_Param
The parameters for the layer (not including tensor-valued weights etc.).
Definition: LayerWithParameters.hpp:52
armnn::LayerType::Concat
@ Concat
armnn::LayerWithParameters< OriginsDescriptor >::PrepInfoAndDesc
WorkloadInfo PrepInfoAndDesc(QueueDescriptor &descriptor) const
Helper function to reduce duplication in *Layer::CreateWorkload.
Definition: LayerWithParameters.hpp:44
PolymorphicDowncast.hpp
armnn::TensorInfo::IsTypeSpaceMatch
bool IsTypeSpaceMatch(const TensorInfo &other) const
Check that the types are the same and, if quantize, that the quantization parameters are the same.
Definition: Tensor.cpp:432
armnn::LayerValidationException
Definition: Exceptions.hpp:105
armnn::IWorkloadFactory
Definition: WorkloadFactory.hpp:22
armnn::ConcatLayer::ValidateTensorShapesFromInputs
void ValidateTensorShapesFromInputs() override
Check if the input tensor shape(s) will lead to a valid configuration of ConcatLayer.
Definition: ConcatLayer.cpp:309
armnn::Layer::GetOutputHandler
const OutputHandler & GetOutputHandler(unsigned int i=0) const
Definition: Layer.hpp:245
armnn::Layer::VerifyShapeInferenceType
void VerifyShapeInferenceType(const TensorShape &outputShape, ShapeInferenceMethod shapeInferenceMethod)
Definition: Layer.cpp:526
armnn::ITensorHandleFactory
Definition: ITensorHandleFactory.hpp:46
armnn::Layer::SetAdditionalInfo
void SetAdditionalInfo(QueueDescriptor &descriptor) const
Definition: Layer.cpp:303
armnn::Exception
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:46
armnn::ConcatLayer::CreateTensorHandles
virtual void CreateTensorHandles(const TensorHandleFactoryRegistry &registry, const IWorkloadFactory &factory, const bool IsMemoryManaged=true) override
Set the outputs to be appropriate sub tensors of the input if sub tensors are supported otherwise cre...
Definition: ConcatLayer.cpp:186
armnn::InputSlot::IsTensorInfoOverridden
bool IsTensorInfoOverridden() const override
Returns true if this InputSlot has an overridden TensorInfo that was set through a call to SetTensorI...
Definition: Layer.cpp:631
armnn::BoostLogSeverityMapping::info
@ info
armnn::ConcatLayer::ExecuteStrategy
void ExecuteStrategy(IStrategy &strategy) const override
Apply a visitor to this layer.
Definition: ConcatLayer.cpp:341
armnn::Layer::GetNumInputSlots
unsigned int GetNumInputSlots() const override
Returns the number of connectable input slots.
Definition: Layer.hpp:334
armnn::CapabilityClass::PaddingRequired
@ PaddingRequired
armnn::TensorInfo::GetShape
const TensorShape & GetShape() const
Definition: Tensor.hpp:193
ARMNN_NO_DEPRECATE_WARN_END
#define ARMNN_NO_DEPRECATE_WARN_END
Definition: Deprecated.hpp:34
armnn::ConcatQueueDescriptor::m_ViewOrigins
std::vector< ViewOrigin > m_ViewOrigins
Definition: WorkloadData.hpp:143
armnn::OriginsDescriptor
An OriginsDescriptor for the ConcatLayer.
Definition: Descriptors.hpp:201
armnn::OutputSlot::GetTensorHandleFactoryId
ITensorHandleFactory::FactoryId GetTensorHandleFactoryId() const
Definition: Layer.cpp:218
armnn::InputSlot::GetConnectedOutputSlot
const OutputSlot * GetConnectedOutputSlot() const
Definition: Layer.hpp:56
armnn
Copyright (c) 2021 ARM Limited and Contributors.
Definition: 01_00_quick_start.dox:6
armnn::ITensorHandleFactory::FactoryId
std::string FactoryId
Definition: ITensorHandleFactory.hpp:49
armnn::Layer::VerifyLayerConnections
void VerifyLayerConnections(unsigned int expectedConnections, const CheckLocation &location) const
Definition: Layer.cpp:410
armnn::LayerType::Input
@ Input
armnn::OutputHandler::GetTensorInfo
const TensorInfo & GetTensorInfo() const
Gets the matching TensorInfo for the output.
Definition: OutputHandler.hpp:42
armnn::OriginsDescriptor::GetViewOrigin
const uint32_t * GetViewOrigin(uint32_t idx) const
Return the view origin at the int value idx.
Definition: Descriptors.cpp:197
ConcatLayer.hpp
armnn::NullPointerException
Definition: Exceptions.hpp:146
armnn::Layer::m_ShapeInferenceMethod
ShapeInferenceMethod m_ShapeInferenceMethod
Definition: Layer.hpp:441
armnn::LayerType
LayerType
When adding a new layer, adapt also the LastLayer enum value in the enum class LayerType below.
Definition: Types.hpp:491
armnn::OutputSlot::GetConnection
const InputSlot * GetConnection(unsigned int index) const override
Definition: Layer.cpp:83
armnn::ConcatLayer::ConcatLayer
ConcatLayer(const OriginsDescriptor &param, const char *name)
Constructor to create a ConcatLayer.
Definition: ConcatLayer.cpp:18
armnn::Graph
Definition: Graph.hpp:30
armnn::IWorkloadFactory::CreateWorkload
virtual std::unique_ptr< IWorkload > CreateWorkload(LayerType type, const QueueDescriptor &descriptor, const WorkloadInfo &info) const =0
Backends should implement their own CreateWorkload function with a switch statement.
armnn::IStrategy::ExecuteStrategy
virtual void ExecuteStrategy(const IConnectableLayer *layer, const armnn::BaseDescriptor &descriptor, const std::vector< armnn::ConstTensor > &constants, const char *name, const armnn::LayerBindingId id=0)=0
LayerCloneBase.hpp
armnn::LayerType::Constant
@ Constant