ArmNN
 25.02
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
#define ARMNN_NO_DEPRECATE_WARN_BEGIN
Definition: Deprecated.hpp:33
#define ARMNN_NO_DEPRECATE_WARN_END
Definition: Deprecated.hpp:34
#define CHECK_LOCATION()
Definition: Exceptions.hpp:203
This layer represents a merge operation.
Definition: ConcatLayer.hpp:14
void ExecuteStrategy(IStrategy &strategy) const override
Apply a visitor to this layer.
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,...
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...
void ValidateTensorShapesFromInputs() override
Check if the input tensor shape(s) will lead to a valid configuration of ConcatLayer.
ConcatLayer * Clone(Graph &graph) const override
Creates a dynamically-allocated copy of this layer.
ConcatLayer(const OriginsDescriptor &param, const char *name)
Constructor to create a ConcatLayer.
Definition: ConcatLayer.cpp:18
virtual std::unique_ptr< IWorkload > CreateWorkload(const IWorkloadFactory &factory) const override
Makes a workload for the Concat type.
Definition: ConcatLayer.cpp:23
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:47
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
virtual std::vector< Capability > GetCapabilities(const IConnectableLayer *layer, const IConnectableLayer *connectedLayer, CapabilityClass capabilityClass)
static const FactoryId LegacyFactoryId
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.
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
const OutputSlot * GetConnectedOutputSlot() const
Definition: Layer.hpp:56
const TensorInfo & GetTensorInfo() const override
Gets the TensorInfo for this InputSlot.
Definition: Layer.cpp:614
void VerifyLayerConnections(unsigned int expectedConnections, const CheckLocation &location) const
Definition: Layer.cpp:410
const OutputSlot & GetOutputSlot(unsigned int index=0) const override
Get the const output slot handle by slot index.
Definition: Layer.hpp:339
void VerifyShapeInferenceType(const TensorShape &outputShape, ShapeInferenceMethod shapeInferenceMethod)
Definition: Layer.cpp:526
Layer(unsigned int numInputSlots, unsigned int numOutputSlots, LayerType type, const char *name)
Definition: Layer.cpp:260
const char * GetName() const override
Returns the name of the layer.
Definition: Layer.hpp:332
std::vector< OutputHandler > m_OutputHandlers
Definition: Layer.hpp:440
unsigned int GetNumInputSlots() const override
Returns the number of connectable input slots.
Definition: Layer.hpp:334
const InputSlot & GetInputSlot(unsigned int index) const override
Get a const input slot handle by slot index.
Definition: Layer.hpp:337
void ValidateAndCopyShape(const TensorShape &outputShape, const TensorShape &inferredShape, const ShapeInferenceMethod shapeInferenceMethod, const std::string &layerName, const unsigned int outputSlotIndex=0)
Definition: Layer.cpp:457
const OutputHandler & GetOutputHandler(unsigned int i=0) const
Definition: Layer.hpp:245
void SetAdditionalInfo(QueueDescriptor &descriptor) const
Definition: Layer.cpp:303
ShapeInferenceMethod m_ShapeInferenceMethod
Definition: Layer.hpp:441
WorkloadInfo PrepInfoAndDesc(QueueDescriptor &descriptor) const
Helper function to reduce duplication in *Layer::CreateWorkload.
OriginsDescriptor m_Param
The parameters for the layer (not including tensor-valued weights etc.).
const OriginsDescriptor & GetParameters() const override
const TensorInfo & GetTensorInfo() const
Gets the matching TensorInfo for the output.
ITensorHandle * GetData() const
Gets the allocated tensor memory.
const InputSlot * GetConnection(unsigned int index) const override
Definition: Layer.cpp:83
Layer & GetOwningLayer() const
Definition: Layer.hpp:132
const OutputHandler & GetOutputHandler() const
Definition: Layer.hpp:139
const TensorInfo & GetTensorInfo() const override
Definition: Layer.cpp:100
ITensorHandleFactory::FactoryId GetTensorHandleFactoryId() const
Definition: Layer.cpp:218
ITensorHandleFactory * GetFactory(ITensorHandleFactory::FactoryId id) const
Find a TensorHandleFactory by Id Returns nullptr if not found.
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
const TensorShape & GetShape() const
Definition: Tensor.hpp:193
Copyright (c) 2021 ARM Limited and Contributors.
LayerType
When adding a new layer, adapt also the LastLayer enum value in the enum class LayerType below.
Definition: Types.hpp:494
std::vector< ViewOrigin > m_ViewOrigins
An OriginsDescriptor for the ConcatLayer.
uint32_t GetNumViews() const
Get the number of views.
unsigned int GetConcatAxis() const
Get the concatenation axis value.
uint32_t GetNumDimensions() const
Get the number of dimensions.
const uint32_t * GetViewOrigin(uint32_t idx) const
Return the view origin at the int value idx.