ArmNN
 25.02
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ElementwiseBinaryOperator.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2022-2025 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
8 
9 TosaSerializationBasicBlock* ConvertElementwiseBinaryToTosaOperator(const Layer* layer,
10  const LayerType type,
11  const std::vector<const TensorInfo*>& inputs,
12  const std::vector<const TensorInfo*>& outputs,
13  const ElementwiseBinaryDescriptor* descriptor)
14 {
15  std::string input0Name = std::string("input_0");
16  std::string input1Name = std::string("input_1");
17  std::string outputName = std::string("output0_");
18  std::string input0ElemenwiseBinaryName = std::string("intermediate0_") + GetUniqueTosaMappingID();
19  std::string input1ElemenwiseBinaryName = std::string("intermediate0_") + GetUniqueTosaMappingID();
20  std::string blockName;
21 
22  // If a layer is present then the block will be used for execution, so input and output names need to be determined
23  // using the previous and following layers so the graph is connected correctly. For validation this doesn't matter.
24  if(layer != nullptr)
25  {
26  input0Name = GenerateUniqueInputName(layer->GetInputSlot(0));
27  input1Name = GenerateUniqueInputName(layer->GetInputSlot(1));
28  outputName = GenerateUniqueOutputName(*layer);
29  }
30 
31  TosaSerializationOperator* op = nullptr;
32 
33  std::vector<TosaSerializationTensor*> tensors;
34  std::vector<TosaSerializationOperator*> operators;
35  DType inputDType0 = ArmNNToDType(inputs[0]->GetDataType());
36  DType inputDType1 = ArmNNToDType(inputs[1]->GetDataType());
37  DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType());
38  bool isInputInt8 = (inputDType0 == DType_INT8);
39 
40  // Only add input tensors if connected layer is an input layer.
41  // As intermediate or constant tensors will be created separately.
42  // There also can't be duplicate tensor.
43  if(input0Name.find("input_") != std::string::npos)
44  {
45  std::vector<int32_t> inputShape0 = GetTosaTensorShape(inputs[0]->GetShape());
46  tensors.push_back(new TosaSerializationTensor(input0Name, inputShape0, inputDType0, {}));
47  }
48  if(input1Name.find("input_") != std::string::npos)
49  {
50  std::vector<int32_t> inputShape1 = GetTosaTensorShape(inputs[1]->GetShape());
51  tensors.push_back(new TosaSerializationTensor(input1Name, inputShape1, inputDType1, {}));
52  }
53 
54  std::vector<int32_t> outputShape0 = GetTosaTensorShape(outputs[0]->GetShape());
55 
56  // Assign an output name and add to tensors based on the input type
57  // An int8 input for all ops will require the output to be rescaled from int32 to int8
58  std::string outputElemenwiseBinaryName;
59  if (isInputInt8)
60  {
61  outputElemenwiseBinaryName = std::string("intermediate0_") + GetUniqueTosaMappingID();
62  tensors.push_back(new TosaSerializationTensor(outputElemenwiseBinaryName, outputShape0, DType_INT32, {}));
63  }
64  else
65  {
66  tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {}));
67  }
68 
69  // Add supports DType_INT32 input only, so a rescale is required when input is DType_INT8
70  // MUL op is the only exception which has TOSA int8 support
71  bool isMulDesc = descriptor ? descriptor->m_Operation == BinaryOperation::Mul : false;
72  bool isMulOp = (type == LayerType::Multiplication) || isMulDesc ? true : false;
73  if (isInputInt8 && !isMulOp)
74  {
75  TosaSerializationOperator* rescaleOp0 = nullptr;
76  CreateRescaleTosaOperator(input0Name,
77  input0ElemenwiseBinaryName,
78  inputs[0]->GetQuantizationScale() / outputs[0]->GetQuantizationScale(),
79  inputs[0]->GetQuantizationOffset(),
80  0,
81  false,
82  false,
83  true,
84  true,
85  &rescaleOp0);
86  tensors.push_back(new TosaSerializationTensor(input0ElemenwiseBinaryName,
87  GetTosaTensorShape(inputs[0]->GetShape()),
88  DType_INT32,
89  {}));
90  operators.push_back(rescaleOp0);
91 
92  TosaSerializationOperator* rescaleOp1 = nullptr;
93  CreateRescaleTosaOperator(input1Name,
94  input1ElemenwiseBinaryName,
95  inputs[1]->GetQuantizationScale() / outputs[0]->GetQuantizationScale(),
96  inputs[1]->GetQuantizationOffset(),
97  0,
98  false,
99  false,
100  true,
101  true,
102  &rescaleOp1);
103  tensors.push_back(new TosaSerializationTensor(input1ElemenwiseBinaryName,
104  GetTosaTensorShape(inputs[1]->GetShape()),
105  DType_INT32,
106  {}));
107  operators.push_back(rescaleOp1);
108  }
109 
110  std::string& elementwiseInput0Str = isInputInt8 ? input0ElemenwiseBinaryName : input0Name;
111  std::string& elementwiseInput1Str = isInputInt8 ? input1ElemenwiseBinaryName : input1Name;
112  std::string& elementwiseOutputStr = isInputInt8 ? outputElemenwiseBinaryName : outputName;
113 
114  switch(type)
115  {
116  case LayerType::Addition:
117  {
118  op = new TosaSerializationOperator(Op_ADD,
119  Attribute_NONE,
120  nullptr,
121  {input0Name, input1Name},
122  {outputName});
123  blockName = std::string("Op_ADD_block_") + GetUniqueTosaMappingID();
124  break;
125  }
126  case LayerType::ElementwiseBinary:
127  {
128  switch (descriptor->m_Operation)
129  {
130  case BinaryOperation::Add:
131  {
132  op = new TosaSerializationOperator(Op_ADD,
133  Attribute_NONE,
134  nullptr,
135  {elementwiseInput0Str, elementwiseInput1Str},
136  {elementwiseOutputStr});
137  blockName = std::string("Op_ADD_block_") + GetUniqueTosaMappingID();
138  break;
139  }
140  case BinaryOperation::Maximum:
141  {
142  op = new TosaSerializationOperator(Op_MAXIMUM,
143  Attribute_NONE,
144  nullptr,
145  {elementwiseInput0Str, elementwiseInput1Str},
146  {elementwiseOutputStr});
147  blockName = std::string("Op_MAXIMUM_block_") + GetUniqueTosaMappingID();
148  break;
149  }
150  case BinaryOperation::Mul:
151  {
152  int8_t shift = 0;
153  TosaMulAttribute mulAttribute(shift);
154 
155  // Mul supports input DType_INT8 so will not require a rescale before the op.
156  // i.e "input0Name" is used for the input and not intermediate "elementwiseInput0Str"
157  op = new TosaSerializationOperator(Op_MUL,
158  Attribute_MulAttribute,
159  &mulAttribute,
160  {input0Name, input1Name},
161  {elementwiseOutputStr});
162  blockName = std::string("Op_MUL_block_") + GetUniqueTosaMappingID();
163  break;
164  }
165  case BinaryOperation::Sub:
166  {
167  op = new TosaSerializationOperator(Op_SUB,
168  Attribute_NONE,
169  nullptr,
170  {elementwiseInput0Str, elementwiseInput1Str},
171  {elementwiseOutputStr});
172  blockName = std::string("Op_SUB_block_") + GetUniqueTosaMappingID();
173  break;
174  }
175  case BinaryOperation::SqDiff:
176  {
177  throw Exception("TOSA mappings of Squared Difference operator "
178  "implemented under ConvertSquaredDifferenceToTosaOperator().");
179  }
180  default:
181  throw Exception("ConvertElementwiseBinaryToTosaOperator: Unsupported layer type.");
182  }
183  break;
184  }
185  case LayerType::Multiplication:
186  {
187  int32_t shift = 0;
188  TosaMulAttribute mulAttribute(shift);
189  op = new TosaSerializationOperator(Op_MUL,
190  Attribute_MulAttribute,
191  &mulAttribute,
192  {input0Name, input1Name},
193  {outputName});
194  blockName = std::string("Op_MUL_block_") + GetUniqueTosaMappingID();
195  break;
196  }
197  case LayerType::Subtraction:
198  {
199  op = new TosaSerializationOperator(Op_SUB,
200  Attribute_NONE,
201  nullptr,
202  {input0Name, input1Name},
203  {outputName});
204  blockName = std::string("Op_SUB_block_") + GetUniqueTosaMappingID();
205  break;
206  }
207  default:
208  throw Exception("ConvertElementwiseBinaryToTosaOperator: Unsupported layer type.");
209  }
210 
211  operators.push_back(op);
212 
213  // All ElementwiseBinary operators require a rescale of output
214  // from DType_INT32 to DType_INT8 when the input is DType_INT8
215  if (inputDType0 == DType_INT8)
216  {
217  // double output_rescale_scale = in_lhs_scale * in_rhs_scale / output_scale;
218  float input0QScale = inputs[0]->IsQuantized()?inputs[0]->GetQuantizationScale():1.0f;
219  float input1QScale = inputs[1]->IsQuantized()?inputs[1]->GetQuantizationScale():1.0f;
220  float outputQScale = outputs[0]->IsQuantized()?outputs[0]->GetQuantizationScale():1.0f;
221  double combinedQScale = input0QScale * input1QScale / outputQScale;
222 
223  TosaSerializationOperator* rescaleOp = nullptr;
224  CreateRescaleTosaOperator(outputElemenwiseBinaryName,
225  outputName,
226  combinedQScale,
227  0,
228  outputs[0]->GetQuantizationOffset(),
229  false,
230  false,
231  true,
232  true,
233  &rescaleOp);
234  tensors.push_back(new TosaSerializationTensor(outputName,
235  GetTosaTensorShape(outputs[0]->GetShape()),
236  DType_INT8,
237  {}));
238  operators.push_back(rescaleOp);
239  }
240 
241  return new TosaSerializationBasicBlock(blockName, // name
242  mainName, // region name
243  {operators}, // operators
244  tensors, // tensors
245  {input0Name, input1Name}, // inputs
246  {outputName}); // outputs
247 }
248 
249 TosaSerializationBasicBlock* ConvertSquaredDifferenceToTosaOperator(const Layer* layer,
250  const LayerType,
251  const std::vector<const TensorInfo*>& inputs,
252  const std::vector<const TensorInfo*>& outputs,
253  const ElementwiseBinaryDescriptor* descriptor)
254 {
255  if (descriptor->m_Operation != BinaryOperation::SqDiff)
256  {
257  throw Exception("ElementwiseBinaryDescriptor operation must be SqDiff"
258  "in ConvertSquaredDifferenceToTosaOperator().");
259  }
260 
261  std::string input0Name = std::string("input_0");
262  std::string input1Name = std::string("input_1");
263  std::string outputName = std::string("output0_");
264  std::string interElemenwiseBinaryName = std::string("intermediate0_") + GetUniqueTosaMappingID();
265  std::string blockName = std::string("Op_SQDIFF_block_") + GetUniqueTosaMappingID();
266 
267  // If a layer is present then the block will be used for execution, so input and output names need to be determined
268  // using the previous and following layers so the graph is connected correctly. For validation this doesn't matter.
269  if (layer != nullptr)
270  {
271  if (layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer().GetType() == LayerType::Reshape ||
272  layer->GetInputSlot(1).GetConnectedOutputSlot()->GetOwningLayer().GetType() == LayerType::Reshape)
273  {
274  interElemenwiseBinaryName = std::string("intermediate1_") + GetUniqueTosaMappingID();
275  }
276 
277  input0Name = GenerateUniqueInputName(layer->GetInputSlot(0));
278  input1Name = GenerateUniqueInputName(layer->GetInputSlot(1));
279  outputName = GenerateUniqueOutputName(*layer);
280  }
281 
282  std::vector<TosaSerializationTensor*> tensors {};
283  std::vector<TosaSerializationOperator*> operators {};
284  DType inputDType0 = ArmNNToDType(inputs[0]->GetDataType());
285  DType inputDType1 = ArmNNToDType(inputs[1]->GetDataType());
286  DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType());
287  bool isInputInt8 = (inputDType0 == DType_INT8);
288 
289  // Only add input tensors if connected layer is an input layer.
290  // As intermediate or constant tensors will be created separately.
291  // There also can't be duplicate tensor.
292  if(input0Name.find("input_") != std::string::npos)
293  {
294  std::vector<int32_t> inputShape0 = GetTosaTensorShape(inputs[0]->GetShape());
295  tensors.push_back(new TosaSerializationTensor(input0Name, inputShape0, inputDType0, {}));
296  }
297  if(input1Name.find("input_") != std::string::npos)
298  {
299  std::vector<int32_t> inputShape1 = GetTosaTensorShape(inputs[1]->GetShape());
300  tensors.push_back(new TosaSerializationTensor(input1Name, inputShape1, inputDType1, {}));
301  }
302 
303  std::vector<int32_t> outputShape0 = GetTosaTensorShape(outputs[0]->GetShape());
304 
305  if (inputDType0 == DType_FP32 ||
306  inputDType0 == DType_FP16 ||
307  inputDType0 == DType_INT32)
308  {
309  operators.push_back(new TosaSerializationOperator(
310  Op_SUB,
311  Attribute_NONE,
312  nullptr,
313  {input0Name, input1Name},
314  {interElemenwiseBinaryName}));
315  tensors.push_back(new TosaSerializationTensor(interElemenwiseBinaryName,
316  outputShape0,
317  outputDType0,
318  {}));
319 
320  int8_t shift = 0;
321  TosaMulAttribute mulAttribute(shift);
322 
323  operators.push_back(new TosaSerializationOperator(
324  Op_MUL,
325  Attribute_MulAttribute,
326  &mulAttribute,
327  {interElemenwiseBinaryName, interElemenwiseBinaryName},
328  {outputName}));
329  }
330  else if (isInputInt8)
331  {
332  std::string rescale0Output0Name = std::string("intermediate0_") + GetUniqueTosaMappingID();
333  std::string rescale0Output1Name = std::string("intermediate1_") + GetUniqueTosaMappingID();
334  std::string rescale1Output0Name = std::string("intermediate2_") + GetUniqueTosaMappingID();
335  std::string rescale1Output1Name = std::string("intermediate3_") + GetUniqueTosaMappingID();
336  std::string mulOutputName = std::string("intermediate4_") + GetUniqueTosaMappingID();
337  interElemenwiseBinaryName = std::string("intermediate5_") + GetUniqueTosaMappingID();
338 
339  // We need to make sure the inputs are rescaled correctly
340  // Following the behaviour defined here lite/kernels/squared_difference.cc
341  double in_x_scale = inputs[0]->GetQuantizationScale();
342  double in_y_scale = inputs[1]->GetQuantizationScale();
343  double result_scale = outputs[0]->GetQuantizationScale();
344  double twice_max_input_scale = 2.0 * std::max(in_x_scale, in_y_scale);
345  const int32_t LEFT_SHIFT = 7;
346  double x_rescale_scale = in_x_scale / twice_max_input_scale;
347  double y_rescale_scale = in_y_scale / twice_max_input_scale;
348  double output_rescale_scale =
349  (twice_max_input_scale * twice_max_input_scale) /
350  ((static_cast<double>(1 << LEFT_SHIFT * 2)) * result_scale);
351 
352  TosaSerializationOperator* xShiftOp = nullptr;
353  CreateRescaleTosaOperator(input0Name,
354  rescale0Output0Name,
355  (1 << LEFT_SHIFT),
356  inputs[0]->GetQuantizationOffset(),
357  0,
358  false,
359  false,
360  true,
361  true,
362  &xShiftOp);
363  operators.push_back(xShiftOp);
364  tensors.push_back(new TosaSerializationTensor(rescale0Output0Name,
365  GetTosaTensorShape(inputs[0]->GetShape()),
366  DType_INT32,
367  {}));
368 
369  TosaSerializationOperator* yShiftOp = nullptr;
370  CreateRescaleTosaOperator(input1Name,
371  rescale0Output1Name,
372  (1 << LEFT_SHIFT),
373  inputs[1]->GetQuantizationOffset(),
374  0,
375  false,
376  false,
377  true,
378  true,
379  &yShiftOp);
380  operators.push_back(yShiftOp);
381  tensors.push_back(new TosaSerializationTensor(rescale0Output1Name,
382  GetTosaTensorShape(inputs[1]->GetShape()),
383  DType_INT32,
384  {}));
385 
386  TosaSerializationOperator* xScaledOp = nullptr;
387  CreateRescaleTosaOperator(rescale0Output0Name,
388  rescale1Output0Name, //change
389  x_rescale_scale,
390  0,
391  0,
392  false,
393  false,
394  true,
395  true,
396  &xScaledOp);
397  operators.push_back(xScaledOp);
398  tensors.push_back(new TosaSerializationTensor(rescale1Output0Name,
399  GetTosaTensorShape(inputs[0]->GetShape()),
400  DType_INT32,
401  {}));
402 
403  TosaSerializationOperator* yScaledOp = nullptr;
404  CreateRescaleTosaOperator(rescale0Output1Name,
405  rescale1Output1Name, //change
406  y_rescale_scale,
407  0,
408  0,
409  false,
410  false,
411  true,
412  true,
413  &yScaledOp);
414  operators.push_back(yScaledOp);
415  tensors.push_back(new TosaSerializationTensor(rescale1Output1Name,
416  GetTosaTensorShape(inputs[1]->GetShape()),
417  DType_INT32,
418  {}));
419 
420 
421 
422  operators.push_back(new TosaSerializationOperator(
423  Op_SUB,
424  Attribute_NONE,
425  nullptr,
426  {rescale1Output0Name, rescale1Output1Name},
427  {interElemenwiseBinaryName}));
428  tensors.push_back(new TosaSerializationTensor(interElemenwiseBinaryName,
429  GetTosaTensorShape(outputs[0]->GetShape()),
430  DType_INT32,
431  {}));
432  int8_t shift = 0;
433  TosaMulAttribute mulAttribute(shift);
434 
435  operators.push_back(new TosaSerializationOperator(
436  Op_MUL,
437  Attribute_MulAttribute,
438  &mulAttribute,
439  {interElemenwiseBinaryName, interElemenwiseBinaryName},
440  {mulOutputName}));
441  tensors.push_back(new TosaSerializationTensor(mulOutputName,
442  GetTosaTensorShape(outputs[0]->GetShape()),
443  DType_INT32,
444  {}));
445 
446 
447  TosaSerializationOperator* rescaleOutputOp = nullptr;
448  CreateRescaleTosaOperator(mulOutputName,
449  outputName,
450  output_rescale_scale,
451  0,
452  outputs[0]->GetQuantizationOffset(),
453  false,
454  false,
455  true,
456  true,
457  &rescaleOutputOp);
458  operators.push_back(rescaleOutputOp);
459  }
460  else
461  {
462  throw Exception("TOSA spec only supports INT8, INT32, FP16 and FP32 datatypes for SqDiff.");
463  }
464 
465  tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {}));
466 
467  return new TosaSerializationBasicBlock(blockName, // name
468  mainName, // region name
469  {operators}, // operators
470  tensors, // tensors
471  {input0Name, input1Name}, // inputs
472  {outputName}); // outputs
473 }
474 
TosaSerializationBasicBlock * ConvertElementwiseBinaryToTosaOperator(const Layer *layer, const LayerType type, const std::vector< const TensorInfo * > &inputs, const std::vector< const TensorInfo * > &outputs, const ElementwiseBinaryDescriptor *descriptor)
TosaSerializationBasicBlock * ConvertSquaredDifferenceToTosaOperator(const Layer *layer, const LayerType, const std::vector< const TensorInfo * > &inputs, const std::vector< const TensorInfo * > &outputs, const ElementwiseBinaryDescriptor *descriptor)
std::string GenerateUniqueOutputName(const Layer &layer, uint32_t layerSlot=0)
const std::string mainName
DType ArmNNToDType(const DataType &type)
std::vector< int32_t > GetTosaTensorShape(const TensorShape &shape)
std::string GenerateUniqueInputName(const armnn::InputSlot &slot)
std::string GetUniqueTosaMappingID()
void CreateRescaleTosaOperator(const std::string &inputName, const std::string &outputName, double scale, int32_t input_zp, int32_t output_zp, bool input_unsigned, bool output_unsigned, bool double_round, bool scale32, TosaSerializationOperator **op)
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:47
const OutputSlot * GetConnectedOutputSlot() const
Definition: Layer.hpp:56
const InputSlot & GetInputSlot(unsigned int index) const override
Get a const input slot handle by slot index.
Definition: Layer.hpp:337
LayerType GetType() const override
Returns the armnn::LayerType of this layer.
Definition: Layer.hpp:286
Layer & GetOwningLayer() const
Definition: Layer.hpp:132
LayerType
When adding a new layer, adapt also the LastLayer enum value in the enum class LayerType below.
Definition: Types.hpp:494
A ElementwiseBinaryDescriptor for the ElementwiseBinaryLayer.
BinaryOperation m_Operation
Specifies the elementwiseBinary operation to execute.