ArmNN
 24.11
ReduceOperator.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2024 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 // Copyright © 2020 The TensorFlow Authors. All Rights Reserved.
6 // SPDX-License-Identifier: Apache-2.0
7 //
8 
9 #include "ReduceOperator.hpp"
10 
11 #include <armnn/TypesUtils.hpp>
13 
14 // This function is paraphrased from:
15 // tensorflow/compiler/mlir/tosa/transforms/legalize_common.cc from functions convertReduceMeanOp, convertReduceSumOp,
16 // convertReduceOpCommon
17 TosaSerializationBasicBlock* ConvertReduceToTosaOperator(const Layer* layer,
18  const std::vector<const TensorInfo*>& inputs,
19  const std::vector<const TensorInfo*>& outputs,
20  const ReduceDescriptor* reduceDescriptor)
21 {
22  // Early exits
23  if (!inputs[0])
24  {
25  throw armnn::Exception("ConvertReduceOperator: Must provide a valid input tensor.");
26  }
27 
28  if (inputs[0]->IsQuantized() ^ outputs[0]->IsQuantized())
29  {
30  throw armnn::Exception("ConvertReduceOperator: "
31  "Both input and output tensors must be either quantised or non-quantised data types.");
32  }
33 
34  if (reduceDescriptor->m_vAxis.empty())
35  {
36  throw armnn::Exception("ConvertReduceOperator: Reduce Operation with empty axis not implemented.");
37  }
38 
39  // Tensor names
40  std::string inputName = "input_";
41 
42  std::size_t intermediateCounter = 0;
43 
44  std::string outputName = "output0_";
45 
46  std::string reduceOpName = GetReduceOperationAsCString(reduceDescriptor->m_ReduceOperation);
47  std::string blockName = "Op_REDUCE_" + reduceOpName + "_block_" + GetUniqueTosaMappingID();
48 
49  std::vector<int32_t> inputShape = GetTosaTensorShape(inputs[0]->GetShape());
50  std::vector<int32_t> outputShape = GetTosaTensorShape(outputs[0]->GetShape());
51 
52  if (layer)
53  {
54  inputName = GenerateUniqueInputName(layer->GetInputSlot(0));
55  outputName = GenerateUniqueOutputName(*layer);
56  }
57 
58  std::vector<TosaSerializationTensor*> tensors;
59  std::vector<std::string> inputNames{inputName};
60 
61  DType inputType = ArmNNToDType(inputs[0]->GetDataType());
62 
63  if (inputName.substr(0, 6) == "input_")
64  {
65  tensors.emplace_back(new TosaSerializationTensor(inputName,
66  inputShape,
67  inputType,
68  {}));
69  }
70 
71  int64_t input_zp = 0;
72  int64_t output_zp = 0;
73 
74  double input_scale = 1.0;
75  double output_scale = 1.0;
76 
77  int32_t input_multiplier = 1;
78  int32_t output_multiplier = 1;
79 
80  int32_t input_shift = 0;
81  int32_t output_shift = 0;
82 
83  int64_t numElemsOnReducedAxis = 1;
84 
85  std::vector<int32_t> axes(reduceDescriptor->m_vAxis.begin(), reduceDescriptor->m_vAxis.end());
86 
87  for (int64_t axis : axes)
88  {
89  numElemsOnReducedAxis *= inputShape[static_cast<uint64_t>(axis)];
90  }
91 
92  std::vector<TosaSerializationOperator*> operators;
93 
94  bool inputQuantised = inputs[0]->IsQuantized();
95 
96  // Conditional RESCALE
97  if (inputQuantised)
98  {
99  input_zp = inputs[0]->GetQuantizationOffset();
100  output_zp = outputs[0]->GetQuantizationOffset();
101 
102  std::string outputNameRescale =
103  "intermediate" + std::to_string(intermediateCounter++) + "_" + GetUniqueTosaMappingID();
104 
105  TosaSerializationOperator* rescaleOp1 = nullptr;
106 
107  switch(reduceDescriptor->m_ReduceOperation)
108  {
109  case ReduceOperation::Sum:
110  input_shift = 20;
111 
112  input_scale = static_cast<double>(1 << input_shift) * inputs[0]->GetQuantizationScale();
113  output_scale = 1.0 / (outputs[0]->GetQuantizationScale() * static_cast<double>(1 << input_shift));
114 
115  CreateRescaleTosaOperator(inputName,
116  outputNameRescale,
117  input_scale,
118  static_cast<int32_t>(input_zp),
119  static_cast<int32_t>(output_zp),
120  true,
121  true,
122  &rescaleOp1);
123 
124  break;
125  case ReduceOperation::Mean:
126  {
127  // calculate shifts and multipliers
128  ComputeMultiplierAndShiftTosaScale32(1.0, input_multiplier, input_shift);
130  (
131  static_cast<double>(inputs[0]->GetQuantizationScale()) /
132  static_cast<double>(outputs[0]->GetQuantizationScale()),
133  output_multiplier,
134  output_shift
135  );
136 
137  int shift = 63 - __builtin_clzl(static_cast<uint64_t>(numElemsOnReducedAxis));
138  shift = std::min(shift, 32);
139  shift = std::min(shift, 62 - output_shift);
140 
141  output_multiplier = static_cast<int32_t>(
142  (static_cast<int64_t>(output_multiplier) << shift) / numElemsOnReducedAxis);
143 
144  output_shift += shift;
145 
146  CreateRescaleTosaOperator(inputName,
147  outputNameRescale,
148  input_multiplier,
149  input_shift,
150  static_cast<int32_t>(input_zp),
151  static_cast<int32_t>(output_zp),
152  true,
153  true,
154  false,
155  &rescaleOp1);
156  break;
157  }
158  default:
159  throw armnn::Exception("ConvertReduceOperator: Reduce Operation not implemented.");
160  }
161 
162  operators.emplace_back(rescaleOp1);
163 
164  tensors.emplace_back(new TosaSerializationTensor(outputNameRescale,
165  inputShape,
166  DType_INT32,
167  {}));
168  }
169 
170  // REDUCE_SUM
171  for (const auto axis : axes)
172  {
173  auto rank = static_cast<int64_t>(inputs[0]->GetNumDimensions());
174 
175  if (axis < 0 || axis >= rank)
176  {
177  throw armnn::Exception("Axis value not within range of input shape.");
178  }
179 
180  TosaAxisAttribute reduceAttribute(axis);
181 
182  std::string outputNameReduce =
183  "intermediate" + std::to_string(intermediateCounter++) + "_" + GetUniqueTosaMappingID();
184 
185  switch(reduceDescriptor->m_ReduceOperation)
186  {
187  case ReduceOperation::Sum:
188  case ReduceOperation::Mean:
189  operators.emplace_back(new TosaSerializationOperator(Op_REDUCE_SUM,
190  Attribute_AxisAttribute,
191  &reduceAttribute,
192  { tensors.back()->GetName() },
193  { outputNameReduce }));
194  break;
195  default:
196  throw armnn::Exception("ConvertReduceOperator: Reduce Operation not implemented.");
197  }
198 
199  std::vector<int32_t> outputShapeReduce = tensors.back()->GetShape();
200  outputShapeReduce[static_cast<std::size_t>(axis)] = 1;
201 
202  tensors.emplace_back(new TosaSerializationTensor(outputNameReduce,
203  outputShapeReduce,
204  tensors.back()->GetDtype(),
205  {}));
206  }
207 
208  // Conditional RESCALE
209  if (inputQuantised)
210  {
211  std::string outputNameRescale =
212  "intermediate" + std::to_string(intermediateCounter++) + "_" + GetUniqueTosaMappingID();
213 
214  TosaSerializationOperator* rescaleOp2 = nullptr;
215 
216  switch(reduceDescriptor->m_ReduceOperation)
217  {
218  case ReduceOperation::Sum:
219  CreateRescaleTosaOperator(tensors.back()->GetName(),
220  outputNameRescale,
221  output_scale,
222  static_cast<int32_t>(output_zp),
223  static_cast<int32_t>(input_zp),
224  true,
225  true,
226  &rescaleOp2);
227  break;
228  case ReduceOperation::Mean:
229  CreateRescaleTosaOperator(tensors.back()->GetName(),
230  outputNameRescale,
231  output_multiplier,
232  output_shift,
233  static_cast<int32_t>(output_zp),
234  static_cast<int32_t>(input_zp),
235  true,
236  true,
237  false,
238  &rescaleOp2);
239  break;
240  default:
241  throw armnn::Exception("ConvertReduceOperator: Reduce Operation not implemented.");
242  }
243 
244  operators.push_back(rescaleOp2);
245 
246  tensors.emplace_back(new TosaSerializationTensor(outputNameRescale,
247  tensors.back()->GetShape(),
248  inputType,
249  {}));
250  }
251 
252  // RESHAPE
253  TosaReshapeAttribute reshapeAttribute(GetTosaTensorShape(outputs[0]->GetShape()));
254 
255  std::string outputNameReshape = !inputQuantised && reduceDescriptor->m_ReduceOperation == ReduceOperation::Mean
256  ? "intermediate" + std::to_string(intermediateCounter++) + "_" + GetUniqueTosaMappingID()
257  : outputName;
258 
259  operators.emplace_back(new TosaSerializationOperator(Op_RESHAPE,
260  Attribute_ReshapeAttribute,
261  &reshapeAttribute,
262  { tensors.back()->GetName() },
263  { outputNameReshape }));
264 
265  tensors.emplace_back(new TosaSerializationTensor(outputNameReshape,
266  outputShape,
267  inputType,
268  {}));
269 
270  // Conditional MUL
271  // Multiply previous tensor by constant of 1 / number of elements
272  if (!inputQuantised && reduceDescriptor->m_ReduceOperation == ReduceOperation::Mean)
273  {
274  // Constant
275  std::string constNameDivScale = "constant_" + GetUniqueTosaMappingID();
276  inputNames.emplace_back(constNameDivScale);
277 
278  operators.push_back(new TosaSerializationOperator(Op_CONST,
279  Attribute_NONE,
280  nullptr,
281  {},
282  { constNameDivScale }));
283 
284  float divScale = 1.0f / static_cast<float>(numElemsOnReducedAxis);
285 
286  std::vector<uint8_t> uint8DivScale;
287  switch (inputType)
288  {
289  case DType_FP32:
290  TosaSerializationHandler::ConvertF32toU8({divScale}, uint8DivScale);
291  break;
292  case DType_FP16:
293  TosaSerializationHandler::ConvertF16toU8({divScale}, uint8DivScale);
294  break;
295  default:
296  throw armnn::Exception("ConvertReduceOperator: Data type not supported");
297  }
298 
299  // Broadcast to match shapes
300  std::vector<int32_t> divConstantShape(outputShape.size(), 1);
301 
302  tensors.push_back(new TosaSerializationTensor(constNameDivScale,
303  divConstantShape,
304  inputType,
305  uint8DivScale));
306 
307  // MUL
308  TosaMulAttribute mulAttribute(0);
309 
310  operators.emplace_back(new TosaSerializationOperator(Op_MUL,
311  Attribute_MulAttribute,
312  &mulAttribute,
313  { constNameDivScale, outputNameReshape },
314  { outputName }));
315 
316  tensors.push_back(new TosaSerializationTensor(outputName,
317  outputShape,
318  inputType,
319  {}));
320  }
321 
322  return new TosaSerializationBasicBlock(blockName, // name
323  mainName, // region name
324  operators, // operators
325  tensors, // tensors
326  inputNames, // inputs
327  { outputName }); // outputs
328 }
TypesUtils.hpp
TosaRescaleOperatorUtils.hpp
GenerateUniqueOutputName
std::string GenerateUniqueOutputName(const Layer &layer, uint32_t layerSlot=0)
Definition: TosaOperatorUtils.hpp:120
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::ReduceDescriptor::m_ReduceOperation
ReduceOperation m_ReduceOperation
Specifies the reduction operation to execute.
Definition: Descriptors.hpp:1558
armnn::Layer
Definition: Layer.hpp:230
mainName
const std::string mainName
Definition: TosaOperatorUtils.hpp:19
ArmNNToDType
DType ArmNNToDType(const DataType &type)
Definition: TosaOperatorUtils.hpp:22
ReduceOperator.hpp
armnn::Exception
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:46
ComputeMultiplierAndShiftTosaScale32
void ComputeMultiplierAndShiftTosaScale32(double scale, int32_t &multiplier, int32_t &shift)
The following is taken from mlir/lib/Dialect/Tosa/Utils/QuantUtils.cpp in the LLVM project From a sca...
Definition: TosaRescaleOperatorUtils.hpp:65
ConvertReduceToTosaOperator
TosaSerializationBasicBlock * ConvertReduceToTosaOperator(const Layer *layer, const std::vector< const TensorInfo * > &inputs, const std::vector< const TensorInfo * > &outputs, const ReduceDescriptor *reduceDescriptor)
Definition: ReduceOperator.cpp:17
armnn::GetReduceOperationAsCString
constexpr char const * GetReduceOperationAsCString(ReduceOperation reduce_operation)
Definition: TypesUtils.hpp:171
armnn::ReduceDescriptor::m_vAxis
std::vector< uint32_t > m_vAxis
The indices of the dimensions to reduce.
Definition: Descriptors.hpp:1556
GetTosaTensorShape
std::vector< int32_t > GetTosaTensorShape(const TensorShape &shape)
Definition: TosaOperatorUtils.hpp:79
CreateRescaleTosaOperator
void CreateRescaleTosaOperator(const std::string &inputName, const std::string &outputName, const std::vector< int32_t > &multipliers, const std::vector< int32_t > &shifts, int32_t input_zp, int32_t output_zp, bool double_round, bool scale32, bool per_channel, TosaSerializationOperator **op)
Definition: TosaRescaleOperatorUtils.hpp:10
GenerateUniqueInputName
std::string GenerateUniqueInputName(const armnn::InputSlot &slot)
Definition: TosaOperatorUtils.hpp:109
armnn::ReduceDescriptor
A ReduceDescriptor for the REDUCE operators.
Definition: Descriptors.hpp:1538
GetUniqueTosaMappingID
std::string GetUniqueTosaMappingID()
Definition: TosaOperatorUtils.hpp:138