1 //
2 // Copyright © 2017-2023 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5
6 #include <armnnUtils/TensorUtils.hpp>
7
8 #include <armnn/backends/ITensorHandle.hpp>
9 #include <armnn/utility/Assert.hpp>
10 #include <armnn/utility/NumericCast.hpp>
11
12 #include <fmt/format.h>
13
14 using namespace armnn;
15
16 namespace armnnUtils
17 {
18
GetTensorShape(unsigned int numberOfBatches,unsigned int numberOfChannels,unsigned int height,unsigned int width,const DataLayout dataLayout)19 TensorShape GetTensorShape(unsigned int numberOfBatches,
20 unsigned int numberOfChannels,
21 unsigned int height,
22 unsigned int width,
23 const DataLayout dataLayout)
24 {
25 switch (dataLayout)
26 {
27 case DataLayout::NCHW:
28 return TensorShape({numberOfBatches, numberOfChannels, height, width});
29 case DataLayout::NHWC:
30 return TensorShape({numberOfBatches, height, width, numberOfChannels});
31 default:
32 throw InvalidArgumentException("Unknown data layout ["
33 + std::to_string(static_cast<int>(dataLayout)) +
34 "]", CHECK_LOCATION());
35 }
36 }
37
GetTensorInfo(unsigned int numberOfBatches,unsigned int numberOfChannels,unsigned int height,unsigned int width,const DataLayout dataLayout,const DataType dataType)38 TensorInfo GetTensorInfo(unsigned int numberOfBatches,
39 unsigned int numberOfChannels,
40 unsigned int height,
41 unsigned int width,
42 const DataLayout dataLayout,
43 const DataType dataType)
44 {
45 switch (dataLayout)
46 {
47 case DataLayout::NCHW:
48 return TensorInfo({numberOfBatches, numberOfChannels, height, width}, dataType);
49 case DataLayout::NHWC:
50 return TensorInfo({numberOfBatches, height, width, numberOfChannels}, dataType);
51 default:
52 throw InvalidArgumentException("Unknown data layout ["
53 + std::to_string(static_cast<int>(dataLayout)) +
54 "]", CHECK_LOCATION());
55 }
56 }
57
GetTensorInfo(unsigned int numberOfBatches,unsigned int numberOfChannels,unsigned int depth,unsigned int height,unsigned int width,const DataLayout dataLayout,const DataType dataType)58 TensorInfo GetTensorInfo(unsigned int numberOfBatches,
59 unsigned int numberOfChannels,
60 unsigned int depth,
61 unsigned int height,
62 unsigned int width,
63 const DataLayout dataLayout,
64 const DataType dataType)
65 {
66 switch (dataLayout)
67 {
68 case DataLayout::NDHWC:
69 return TensorInfo({numberOfBatches, depth, height, width, numberOfChannels}, dataType);
70 case DataLayout::NCDHW:
71 return TensorInfo({numberOfBatches, numberOfChannels, depth, height, width}, dataType);
72 default:
73 throw InvalidArgumentException("Unknown data layout ["
74 + std::to_string(static_cast<int>(dataLayout)) +
75 "]", CHECK_LOCATION());
76 }
77 }
78
FindMinMax(ITensorHandle * tensorHandle)79 std::pair<float, float> FindMinMax(ITensorHandle* tensorHandle)
80 {
81 auto tensor_data = static_cast<const float *>(tensorHandle->Map(true));
82 auto tensor_size = tensorHandle->GetShape().GetNumElements();
83
84 // Set min/max initially to first value in tensor
85 float min = tensor_data[0];
86 float max = tensor_data[0];
87
88 // Loop over rest of tensor and update min/max if necessary
89 for (unsigned int val = 1; val < tensor_size; val++)
90 {
91 if (tensor_data[val] < min)
92 {
93 min = tensor_data[val];
94 }
95 else if (tensor_data[val] > max)
96 {
97 max = tensor_data[val];
98 }
99 }
100
101 tensorHandle->Unmap();
102
103 return std::make_pair(min, max);
104 }
105
ReduceDims(const TensorShape & tensorShape,unsigned int dimensions)106 TensorShape ReduceDims(const TensorShape& tensorShape, unsigned int dimensions)
107 {
108 if (tensorShape.GetNumDimensions() <= dimensions)
109 {
110 return tensorShape;
111 }
112 std::vector<unsigned int> newShape;
113
114 unsigned int dimsToSkip = tensorShape.GetNumDimensions() - dimensions;
115 unsigned int dimsSkipped = 0;
116 bool insertRemainder = false;
117
118 for (unsigned int i = 0; i < tensorShape.GetNumDimensions(); ++i)
119 {
120 if (tensorShape[i] == 1 && dimsSkipped < dimsToSkip && !insertRemainder)
121 {
122 ++dimsSkipped;
123 continue;
124 }
125 newShape.push_back(tensorShape[i]);
126 // Once we insert the first dimension we can't skip any more
127 insertRemainder = true;
128 }
129 return TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data());
130 }
131
ReduceDims(const TensorInfo & tensorInfo,unsigned int dimensions)132 TensorInfo ReduceDims(const TensorInfo& tensorInfo, unsigned int dimensions)
133 {
134 TensorInfo strippedTensor(tensorInfo);
135 TensorShape strippedShape = ReduceDims(tensorInfo.GetShape(), dimensions);
136 strippedTensor.SetShape(strippedShape);
137 return strippedTensor;
138 }
139
ExpandDims(const TensorShape & tensorShape,int axis)140 TensorShape ExpandDims(const TensorShape& tensorShape, int axis)
141 {
142 unsigned int outputDim = tensorShape.GetNumDimensions() + 1;
143
144 if (axis < -armnn::numeric_cast<int>(outputDim) || axis > armnn::numeric_cast<int>(tensorShape.GetNumDimensions()))
145 {
146 throw InvalidArgumentException(fmt::format("Invalid expansion axis {} for {}D input tensor. {}",
147 axis,
148 tensorShape.GetNumDimensions(),
149 CHECK_LOCATION().AsString()));
150 }
151
152 if (axis < 0)
153 {
154 axis = armnn::numeric_cast<int>(outputDim) + axis;
155 }
156
157 std::vector<unsigned int> outputShape;
158 outputShape.reserve(tensorShape.GetNumDimensions());
159 for (unsigned int i = 0; i < tensorShape.GetNumDimensions(); ++i)
160 {
161 outputShape.push_back(tensorShape[i]);
162 }
163 outputShape.insert(outputShape.begin() + axis, 1);
164
165 return { outputDim, outputShape.data() };
166 }
167
ExpandDimsToRank(const TensorShape & tensorShape,unsigned int rank)168 TensorShape ExpandDimsToRank(const TensorShape& tensorShape, unsigned int rank)
169 {
170 // Can't expand if rank is smaller than current shape
171 if (tensorShape.GetNumDimensions() >= rank)
172 {
173 return tensorShape;
174 }
175
176 std::vector<unsigned int> newShape;
177
178 // First add 1s to the beginning of the tensorInfo to fill in the space
179 for (unsigned int i = 0; i < rank - tensorShape.GetNumDimensions(); ++i)
180 {
181 newShape.push_back(1);
182 }
183
184 // Then iterate through the original shape and append it to the new shape with the added 1s
185 for (unsigned int i = 0; i < tensorShape.GetNumDimensions(); ++i)
186 {
187 newShape.push_back(tensorShape[i]);
188 }
189
190 return TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data());
191 }
192
SqueezeDims(const TensorShape & tensorShape)193 std::vector<unsigned int> SqueezeDims(const TensorShape& tensorShape)
194 {
195 std::vector<unsigned int> squeezedDims;
196
197 for (unsigned int i = 0; i < tensorShape.GetNumDimensions(); ++i)
198 {
199 if (tensorShape[i] != 1)
200 {
201 squeezedDims.push_back(tensorShape[i]);
202 }
203 }
204 return squeezedDims;
205 }
206
GetNumElementsBetween(const TensorShape & shape,const unsigned int firstAxisInclusive,const unsigned int lastAxisExclusive)207 unsigned int GetNumElementsBetween(const TensorShape& shape,
208 const unsigned int firstAxisInclusive,
209 const unsigned int lastAxisExclusive)
210 {
211 ARMNN_ASSERT(firstAxisInclusive <= lastAxisExclusive);
212 ARMNN_ASSERT(lastAxisExclusive <= shape.GetNumDimensions());
213 unsigned int count = 1;
214 for (unsigned int i = firstAxisInclusive; i < lastAxisExclusive; i++)
215 {
216 count *= shape[i];
217 }
218 return count;
219 }
220
GetUnsignedAxis(const unsigned int inputDimension,const int axis)221 unsigned int GetUnsignedAxis(const unsigned int inputDimension, const int axis)
222 {
223 ARMNN_ASSERT_MSG(axis < armnn::numeric_cast<int>(inputDimension),
224 "Required axis index greater than number of dimensions.");
225 ARMNN_ASSERT_MSG(axis >= -armnn::numeric_cast<int>(inputDimension),
226 "Required axis index lower than negative of the number of dimensions");
227
228 unsigned int uAxis = axis < 0 ?
229 inputDimension - armnn::numeric_cast<unsigned int>(abs(axis))
230 : armnn::numeric_cast<unsigned int>(axis);
231 return uAxis;
232 }
233
GetNumElementsAfter(const armnn::TensorShape & shape,unsigned int axis)234 unsigned int GetNumElementsAfter(const armnn::TensorShape& shape, unsigned int axis)
235 {
236 unsigned int numDim = shape.GetNumDimensions();
237 ARMNN_ASSERT(axis <= numDim - 1);
238 unsigned int count = 1;
239 for (unsigned int i = axis+1; i < numDim; i++)
240 {
241 count *= shape[i];
242 }
243 return count;
244 }
245
GetPerAxisParams(const armnn::TensorInfo & info)246 std::pair<unsigned int, std::vector<float>> GetPerAxisParams(const armnn::TensorInfo& info)
247 {
248 const std::vector<float>& scales = info.GetQuantizationScales();
249 armnn::Optional<unsigned int> quantizationDim = info.GetQuantizationDim();
250 if (!info.HasPerAxisQuantization())
251 {
252 throw armnn::InvalidArgumentException(
253 std::string("Per-axis quantization params not set for tensor of type ") +
254 armnn::GetDataTypeName(info.GetDataType()), CHECK_LOCATION());
255 }
256 unsigned int axisFactor = GetNumElementsAfter(info.GetShape(), quantizationDim.value()) ;
257
258 return { axisFactor, scales };
259 }
260
261 template<typename PrimitiveType>
CheckSizes(const std::vector<PrimitiveType> & data,const armnn::TensorInfo & tensorInfo,unsigned int size=1)262 void CheckSizes(const std::vector<PrimitiveType>& data, const armnn::TensorInfo& tensorInfo, unsigned int size = 1)
263 {
264 if (data.size() / size != tensorInfo.GetNumElements())
265 {
266 throw InvalidArgumentException(
267 fmt::format("The data does not contain the expected number of elements {} != {}. {}",
268 data.size(), tensorInfo.GetNumElements(), CHECK_LOCATION().AsString()));
269 }
270 }
271
272 template<typename PrimitiveType>
ToFloatArray(const std::vector<PrimitiveType> & data,const armnn::TensorInfo & tensorInfo)273 std::unique_ptr<float[]> ToFloatArray(const std::vector<PrimitiveType>& data, const armnn::TensorInfo& tensorInfo)
274 {
275 CheckSizes(data, tensorInfo);
276
277 std::unique_ptr<float[]> returnBuffer(new float[tensorInfo.GetNumElements()]);
278
279 if (tensorInfo.HasPerAxisQuantization())
280 {
281 unsigned int axis = tensorInfo.GetQuantizationDim().value();
282 auto axisDimensionality = tensorInfo.GetShape()[axis];
283 auto axisFactor = armnnUtils::GetNumElementsAfter(tensorInfo.GetShape(), axis);
284
285 for (unsigned int i = 0; i < tensorInfo.GetNumElements(); ++i)
286 {
287 unsigned int axisIndex;
288
289 if (i < axisFactor)
290 {
291 axisIndex = 0;
292 }
293 else
294 {
295 axisIndex = (i / axisFactor) % axisDimensionality;
296 }
297 returnBuffer[i] = Dequantize<PrimitiveType>(data[i],
298 tensorInfo.GetQuantizationScales()[axisIndex],
299 tensorInfo.GetQuantizationOffset());
300 }
301 }
302 else
303 {
304 for (unsigned int i = 0; i < tensorInfo.GetNumElements(); ++i)
305 {
306 returnBuffer[i] = Dequantize<PrimitiveType>(data[i],
307 tensorInfo.GetQuantizationScale(),
308 tensorInfo.GetQuantizationOffset());
309 }
310 }
311 return returnBuffer;
312 }
313
ToFloatArray(const std::vector<uint8_t> & data,const armnn::TensorInfo & tensorInfo)314 std::unique_ptr<float[]> ToFloatArray(const std::vector<uint8_t>& data, const armnn::TensorInfo& tensorInfo)
315 {
316 if (tensorInfo.GetDataType() == DataType::QAsymmS8 || tensorInfo.GetDataType() == DataType::QSymmS8)
317 {
318 CheckSizes(data, tensorInfo);
319 std::vector<int8_t> buffer(tensorInfo.GetNumElements());
320 ::memcpy(buffer.data(), data.data(), data.size());
321 return ToFloatArray<int8_t>(buffer, tensorInfo);
322 }
323 else if (tensorInfo.GetDataType() == DataType::QAsymmU8)
324 {
325 CheckSizes(data, tensorInfo);
326 return ToFloatArray<uint8_t>(data, tensorInfo);
327 }
328 else if (tensorInfo.GetDataType() == DataType::Signed32)
329 {
330 CheckSizes(data, tensorInfo, 4);
331 std::vector<int32_t> buffer(tensorInfo.GetNumElements());
332 ::memcpy(buffer.data(), data.data(), data.size());
333 return ToFloatArray<int32_t>(buffer, tensorInfo);
334 }
335 else if (tensorInfo.GetDataType() == DataType::Signed64)
336 {
337 CheckSizes(data, tensorInfo, 8);
338 std::vector<int64_t> buffer(tensorInfo.GetNumElements());
339 ::memcpy(buffer.data(), data.data(), data.size());
340 return ToFloatArray<int64_t>(buffer, tensorInfo);
341 }
342 throw InvalidArgumentException(
343 fmt::format("Unsupported datatype {}. {}",
344 GetDataTypeName(tensorInfo.GetDataType()),
345 CHECK_LOCATION().AsString()));
346 }
347
348 } // namespace armnnUtils
349