ArmNN
 25.11
Loading...
Searching...
No Matches
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
12
13#include <queue>
14
15namespace armnn
16{
17
18ConcatLayer::ConcatLayer(const OriginsDescriptor& param, const char* name)
19 : LayerWithParameters(param.GetNumViews(), 1, LayerType::Concat, param, name)
20{
21}
22
23std::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
39template<typename FactoryType>
40void 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.
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)
131 !GetOutputSlot(0).GetConnection(0)->IsTensorInfoOverridden() && //(6)
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
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
213std::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
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
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.
313 "ConcatLayer: Num Inputs must match num views.",
314 m_Param.GetNumViews(),
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
#define ARMNN_NO_DEPRECATE_WARN_END
#define CHECK_LOCATION()
This layer represents a merge operation.
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.
virtual std::unique_ptr< IWorkload > CreateWorkload(const IWorkloadFactory &factory) const override
Makes a workload for the Concat type.
Base class for all ArmNN exceptions so that users can filter to just those.
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
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 InputSlot & GetInputSlot(unsigned int index) const override
Get a const input slot handle by slot index.
Definition Layer.hpp:337
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 OutputSlot & GetOutputSlot(unsigned int index=0) const override
Get the const output slot handle by slot index.
Definition Layer.hpp:339
LayerType * CloneBase(Graph &graph, Params &&... params) const
std::vector< OutputHandler > m_OutputHandlers
Definition Layer.hpp:440
const OutputHandler & GetOutputHandler(unsigned int i=0) const
Definition Layer.hpp:245
unsigned int GetNumInputSlots() const override
Returns the number of connectable input slots.
Definition Layer.hpp:334
const char * GetName() const override
Returns the name of the layer.
Definition Layer.hpp:332
void ValidateAndCopyShape(const TensorShape &outputShape, const TensorShape &inferredShape, const ShapeInferenceMethod shapeInferenceMethod, const std::string &layerName, const unsigned int outputSlotIndex=0)
Definition Layer.cpp:457
void SetAdditionalInfo(QueueDescriptor &descriptor) const
Definition Layer.cpp:303
ShapeInferenceMethod m_ShapeInferenceMethod
Definition Layer.hpp:441
LayerWithParameters(unsigned int numInputSlots, unsigned int numOutputSlots, LayerType type, const OriginsDescriptor &param, const char *name)
WorkloadInfo PrepInfoAndDesc(QueueDescriptor &descriptor) const
const OriginsDescriptor & GetParameters() const override
ITensorHandle * GetData() const
Gets the allocated tensor memory.
const TensorInfo & GetTensorInfo() const
Gets the matching TensorInfo for the output.
void SetData(std::unique_ptr< ITensorHandle > data)
const OutputHandler & GetOutputHandler() const
Definition Layer.hpp:139
Layer & GetOwningLayer() const
Definition Layer.hpp:132
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.
const TensorShape & GetShape() const
Definition Tensor.hpp:193
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
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
DestType PolymorphicDowncast(SourceType *value)
Polymorphic downcast for build in pointers only.
void ConditionalThrowIfNotEqual(const std::string &message, const ComparedType &leftHandSide, const ComparedType &rightHandSide)
ComparedType must support: operator==(const ComparedType&) operator<<(ostream&, const ComparedType&)
armnn::TensorInfo GetTensorInfo(unsigned int numberOfBatches, unsigned int numberOfChannels, unsigned int height, unsigned int width, const armnn::DataLayout dataLayout, const armnn::DataType dataType)
std::vector< ViewOrigin > m_ViewOrigins
An OriginsDescriptor for the ConcatLayer.
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.