Imported Upstream version 1.25.0
[platform/core/ml/nnfw.git] / runtime / onert / backend / cpu / ops / BinaryArithmeticLayer.cc
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "BinaryArithmeticLayer.h"
18
19 #include <cker/operation/BinaryArithmeticOps.h>
20
21 namespace onert
22 {
23 namespace backend
24 {
25 namespace cpu
26 {
27 namespace ops
28 {
29
30 namespace
31 {
32
33 template <nnfw::cker::BinaryArithmeticOpType arithmetic_type, typename T> struct Eval
34 {
35   nnfw::cker::Shape _lhs_shape;
36   nnfw::cker::Shape _rhs_shape;
37   nnfw::cker::Shape _output_shape;
38   nnfw::cker::BinaryArithmeticOpParam _op_params;
39   bool _need_broadcast;
40
41   Eval(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output,
42        nnfw::cker::BinaryArithmeticOpParam op_params)
43     : _op_params(std::move(op_params)), _need_broadcast(false)
44   {
45     if (!output->is_dynamic())
46       updateCache(lhs, rhs, output);
47   }
48
49   void updateCache(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output)
50   {
51     _lhs_shape.ReplaceWith(getShape(lhs));
52     _rhs_shape.ReplaceWith(getShape(rhs));
53     _output_shape.ReplaceWith(getShape(output));
54     _need_broadcast = nnfw::cker::ProcessBroadcastShapes(_lhs_shape, _rhs_shape, &_op_params);
55   }
56
57   void operator()(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output)
58   {
59     // Assume dynamic tensors never become static and static ones never change shape since
60     // configure()
61     if (output->is_dynamic())
62       updateCache(lhs, rhs, output);
63     else
64       assert(_lhs_shape == getShape(lhs) && _rhs_shape == getShape(rhs) &&
65              _output_shape == getShape(output));
66     auto lhs_buffer = getBuffer<T>(lhs);
67     auto rhs_buffer = getBuffer<T>(rhs);
68     auto output_buffer = getBuffer<T>(output);
69     if (_need_broadcast)
70     {
71       nnfw::cker::BroadcastBinaryArithmeticOp<arithmetic_type>(
72         _op_params, _lhs_shape, lhs_buffer, _rhs_shape, rhs_buffer, _output_shape, output_buffer);
73     }
74     else
75     {
76       nnfw::cker::BinaryArithmeticOp<arithmetic_type>(
77         _op_params, _lhs_shape, lhs_buffer, _rhs_shape, rhs_buffer, _output_shape, output_buffer);
78     }
79   }
80 };
81
82 template <nnfw::cker::BinaryArithmeticOpType arithmetic_type>
83 std::function<void(const IPortableTensor *, const IPortableTensor *, IPortableTensor *)>
84 generateKernelGeneric(const IPortableTensor *lhs, const IPortableTensor *rhs,
85                       IPortableTensor *output, const ir::Activation activation,
86                       nnfw::cker::BinaryArithmeticOpParam &op_params)
87 {
88   switch (lhs->data_type())
89   {
90     case OperandType::FLOAT32:
91     {
92       float output_activation_min = 0, output_activation_max = 0;
93       CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
94       op_params.float_activation_max = output_activation_max;
95       op_params.float_activation_min = output_activation_min;
96       return Eval<arithmetic_type, float>(lhs, rhs, output, op_params);
97       break;
98     }
99     case OperandType::INT32:
100     {
101       int32_t output_activation_min = 0, output_activation_max = 0;
102       CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
103       op_params.quantized_activation_max = output_activation_max;
104       op_params.quantized_activation_min = output_activation_min;
105       return Eval<arithmetic_type, int32_t>(lhs, rhs, output, op_params);
106       break;
107     }
108     default:
109       throw std::runtime_error{"BinaryArithmetic(generic): Unsupported data type"};
110   }
111 }
112
113 void setAddOrSubQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
114                              IPortableTensor *output, ir::Activation activation,
115                              nnfw::cker::BinaryArithmeticOpParam *params)
116 {
117   int32_t output_activation_min, output_activation_max;
118   CalculateActivationRangeQuantized(activation, output, &output_activation_min,
119                                     &output_activation_max);
120   nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
121   op_params.quantized_activation_max = output_activation_max;
122   op_params.quantized_activation_min = output_activation_min;
123   // Parameters for scaled quantized computation
124   op_params.left_shift = 20;
125   // Zero-points of input and output tensors
126   op_params.input1_offset = -lhs->data_zero_point();
127   op_params.input2_offset = -rhs->data_zero_point();
128   op_params.output_offset = output->data_zero_point();
129
130   // Compute normalized scale for _lhs and _rhs values,
131   // and represent in 32-bit fixed point
132   const double norm_max_scale = 2 * std::max(lhs->data_scale(), rhs->data_scale());
133   const double real_lhs_scale = lhs->data_scale() / norm_max_scale;
134   const double real_rhs_scale = rhs->data_scale() / norm_max_scale;
135   // output scale is used to normalize final result, so we invert the scale here
136   const double real_output_scale =
137     norm_max_scale / (output->data_scale() * (1 << op_params.left_shift));
138
139   // Represent the scales as fixed int32_t multipliers, and int32_t shifts
140   QuantizeMultiplier(real_lhs_scale, &op_params.input1_multiplier, &op_params.input1_shift);
141   QuantizeMultiplier(real_rhs_scale, &op_params.input2_multiplier, &op_params.input2_shift);
142   QuantizeMultiplier(real_output_scale, &op_params.output_multiplier, &op_params.output_shift);
143 }
144
145 void setMulQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
146                         IPortableTensor *output, ir::Activation activation,
147                         nnfw::cker::BinaryArithmeticOpParam *params)
148 {
149   int32_t output_activation_min, output_activation_max;
150   CalculateActivationRangeQuantized(activation, output, &output_activation_min,
151                                     &output_activation_max);
152   nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
153
154   op_params.quantized_activation_max = output_activation_max;
155   op_params.quantized_activation_min = output_activation_min;
156   op_params.input1_offset = -lhs->data_zero_point();
157   op_params.input2_offset = -rhs->data_zero_point();
158   op_params.output_offset = output->data_zero_point();
159
160   double real_multiplier = lhs->data_scale() * rhs->data_scale() / output->data_scale();
161   QuantizeMultiplier(real_multiplier, &op_params.output_multiplier, &op_params.output_shift);
162 }
163
164 } // namespace
165
166 void BinaryArithmeticLayer::configure(const IPortableTensor *lhs, const IPortableTensor *rhs,
167                                       IPortableTensor *output, const ir::Activation activation,
168                                       const ArithmeticType arithmetic_type)
169 {
170   assert(lhs != nullptr);
171   assert(rhs != nullptr);
172   assert(output != nullptr);
173
174   _lhs = lhs;
175   _rhs = rhs;
176   _output = output;
177
178   nnfw::cker::BinaryArithmeticOpParam op_params;
179   switch (arithmetic_type)
180   {
181     case ArithmeticType::kAdd:
182       if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
183       {
184         setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
185         _kernel =
186           Eval<nnfw::cker::BinaryArithmeticOpType::ADD, uint8_t>(_lhs, _rhs, _output, op_params);
187       }
188       else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
189       {
190         setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
191         _kernel =
192           Eval<nnfw::cker::BinaryArithmeticOpType::ADD, int8_t>(_lhs, _rhs, _output, op_params);
193       }
194
195       else
196       {
197         _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::ADD>(
198           _lhs, _rhs, _output, activation, op_params);
199       }
200       break;
201     case ArithmeticType::kSub:
202       if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
203       {
204         setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
205         op_params.input2_multiplier *= -1;
206         _kernel =
207           Eval<nnfw::cker::BinaryArithmeticOpType::SUB, uint8_t>(_lhs, _rhs, _output, op_params);
208       }
209       else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
210       {
211         setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
212         op_params.input2_multiplier *= -1;
213         _kernel =
214           Eval<nnfw::cker::BinaryArithmeticOpType::SUB, int8_t>(_lhs, _rhs, _output, op_params);
215       }
216
217       else
218       {
219         _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::SUB>(
220           _lhs, _rhs, _output, activation, op_params);
221       }
222       break;
223     case ArithmeticType::kMul:
224       if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
225       {
226         nnfw::cker::BinaryArithmeticOpParam op_params;
227         setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
228         _kernel =
229           Eval<nnfw::cker::BinaryArithmeticOpType::MUL, uint8_t>(_lhs, _rhs, _output, op_params);
230       }
231       else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
232       {
233         nnfw::cker::BinaryArithmeticOpParam op_params;
234         setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
235         _kernel =
236           Eval<nnfw::cker::BinaryArithmeticOpType::MUL, int8_t>(_lhs, _rhs, _output, op_params);
237       }
238       else
239       {
240         _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::MUL>(
241           _lhs, _rhs, _output, activation, op_params);
242       }
243       break;
244     case ArithmeticType::kDiv:
245       if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
246       {
247         throw std::runtime_error{
248           "BinaryArithmetic(Div): Div operation does not support quantization"};
249       }
250       else if (_lhs->data_type() == OperandType::INT32)
251       {
252         throw std::runtime_error{"BinaryArithmetic(Div): Unsupported data type"};
253       }
254       else
255       {
256         _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::DIV>(
257           _lhs, _rhs, _output, activation, op_params);
258       }
259       break;
260     default:
261       throw std::runtime_error{"BinaryArithmetic: Unsupported BinaryArithmetic type"};
262   }
263 }
264
265 void BinaryArithmeticLayer::run() { _kernel(_lhs, _rhs, _output); }
266
267 } // namespace ops
268 } // namespace cpu
269 } // namespace backend
270 } // namespace onert