ONE - On-device Neural Engine
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
BinaryArithmeticLayer.cc
Go to the documentation of this file.
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
18
20
22{
23
24namespace
25{
26
27template <nnfw::cker::BinaryArithmeticOpType arithmetic_type, typename T> struct Eval
28{
34
35 Eval(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output,
37 : _op_params(std::move(op_params)), _need_broadcast(false)
38 {
39 if (!output->is_dynamic())
40 updateCache(lhs, rhs, output);
41 }
42
43 void updateCache(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output)
44 {
48 _need_broadcast = nnfw::cker::ProcessBroadcastShapes(_lhs_shape, _rhs_shape, &_op_params);
49 }
50
51 void operator()(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output)
52 {
53 // Assume dynamic tensors never become static and static ones never change shape since
54 // configure()
55 if (output->is_dynamic())
56 updateCache(lhs, rhs, output);
57 else
58 assert(_lhs_shape == getShape(lhs) && _rhs_shape == getShape(rhs) &&
59 _output_shape == getShape(output));
60 auto lhs_buffer = getBuffer<T>(lhs);
61 auto rhs_buffer = getBuffer<T>(rhs);
62 auto output_buffer = getBuffer<T>(output);
63 if (_need_broadcast)
64 {
65 nnfw::cker::BroadcastBinaryArithmeticOp<arithmetic_type>(
66 _op_params, _lhs_shape, lhs_buffer, _rhs_shape, rhs_buffer, _output_shape, output_buffer);
67 }
68 else
69 {
70 nnfw::cker::BinaryArithmeticOp<arithmetic_type>(
71 _op_params, _lhs_shape, lhs_buffer, _rhs_shape, rhs_buffer, _output_shape, output_buffer);
72 }
73 }
74};
75
76template <nnfw::cker::BinaryArithmeticOpType arithmetic_type>
77std::function<void(const IPortableTensor *, const IPortableTensor *, IPortableTensor *)>
78generateKernelGeneric(const IPortableTensor *lhs, const IPortableTensor *rhs,
79 IPortableTensor *output, const ir::Activation activation,
81{
82 switch (lhs->data_type())
83 {
84 case OperandType::FLOAT32:
85 {
86 float output_activation_min = 0, output_activation_max = 0;
87 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
88 op_params.float_activation_max = output_activation_max;
89 op_params.float_activation_min = output_activation_min;
90 return Eval<arithmetic_type, float>(lhs, rhs, output, op_params);
91 break;
92 }
93 case OperandType::INT32:
94 {
95 int32_t output_activation_min = 0, output_activation_max = 0;
96 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
97 op_params.quantized_activation_max = output_activation_max;
98 op_params.quantized_activation_min = output_activation_min;
99 return Eval<arithmetic_type, int32_t>(lhs, rhs, output, op_params);
100 break;
101 }
102 case OperandType::INT64:
103 {
104 int64_t output_activation_min = 0, output_activation_max = 0;
105 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
106 op_params.int64_activation_max = output_activation_max;
107 op_params.int64_activation_min = output_activation_min;
108 return Eval<arithmetic_type, int64_t>(lhs, rhs, output, op_params);
109 break;
110 }
111 case OperandType::BOOL8:
112 {
113 if (activation != ir::Activation::NONE)
114 throw std::runtime_error(
115 "BinaryArithmetic(generic): Fused activation is not supported with bool8 type");
116 int32_t output_activation_min = 0, output_activation_max = 0;
117 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
118 static_assert(sizeof(bool) == 1, "cpu backend supports bool type which is 1 byte");
119 return Eval<arithmetic_type, bool>(lhs, rhs, output, op_params);
120 break;
121 }
122 default:
123 throw std::runtime_error{"BinaryArithmetic(generic): Unsupported data type"};
124 }
125}
126
127void setAddOrSubQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
128 IPortableTensor *output, ir::Activation activation,
130{
131 int32_t output_activation_min, output_activation_max;
132 CalculateActivationRangeQuantized(activation, output, &output_activation_min,
133 &output_activation_max);
134 nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
135 op_params.quantized_activation_max = output_activation_max;
136 op_params.quantized_activation_min = output_activation_min;
137 // Parameters for scaled quantized computation
138 op_params.left_shift = 20;
139 // Zero-points of input and output tensors
140 op_params.input1_offset = -lhs->data_zero_point();
141 op_params.input2_offset = -rhs->data_zero_point();
142 op_params.output_offset = output->data_zero_point();
143
144 // Compute normalized scale for _lhs and _rhs values,
145 // and represent in 32-bit fixed point
146 const double norm_max_scale = 2 * std::max(lhs->data_scale(), rhs->data_scale());
147 const double real_lhs_scale = lhs->data_scale() / norm_max_scale;
148 const double real_rhs_scale = rhs->data_scale() / norm_max_scale;
149 // output scale is used to normalize final result, so we invert the scale here
150 const double real_output_scale =
151 norm_max_scale / (output->data_scale() * (1 << op_params.left_shift));
152
153 // Represent the scales as fixed int32_t multipliers, and int32_t shifts
154 QuantizeMultiplier(real_lhs_scale, &op_params.input1_multiplier, &op_params.input1_shift);
155 QuantizeMultiplier(real_rhs_scale, &op_params.input2_multiplier, &op_params.input2_shift);
156 QuantizeMultiplier(real_output_scale, &op_params.output_multiplier, &op_params.output_shift);
157}
158
159void setMulQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
160 IPortableTensor *output, ir::Activation activation,
162{
163 int32_t output_activation_min, output_activation_max;
164 CalculateActivationRangeQuantized(activation, output, &output_activation_min,
165 &output_activation_max);
166 nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
167
168 op_params.quantized_activation_max = output_activation_max;
169 op_params.quantized_activation_min = output_activation_min;
170 op_params.input1_offset = -lhs->data_zero_point();
171 op_params.input2_offset = -rhs->data_zero_point();
172 op_params.output_offset = output->data_zero_point();
173
174 double real_multiplier = lhs->data_scale() * rhs->data_scale() / output->data_scale();
175 QuantizeMultiplier(real_multiplier, &op_params.output_multiplier, &op_params.output_shift);
176}
177
178} // namespace
179
181 IPortableTensor *output, const ir::Activation activation,
182 const ArithmeticType arithmetic_type)
183{
184 assert(lhs != nullptr);
185 assert(rhs != nullptr);
186 assert(output != nullptr);
187
188 _lhs = lhs;
189 _rhs = rhs;
190 _output = output;
191
193 switch (arithmetic_type)
194 {
196 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
197 {
198 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
199 _kernel =
200 Eval<nnfw::cker::BinaryArithmeticOpType::ADD, uint8_t>(_lhs, _rhs, _output, op_params);
201 }
202 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
203 {
204 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
205 _kernel =
206 Eval<nnfw::cker::BinaryArithmeticOpType::ADD, int8_t>(_lhs, _rhs, _output, op_params);
207 }
208
209 else
210 {
211 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::ADD>(
212 _lhs, _rhs, _output, activation, op_params);
213 }
214 break;
216 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
217 {
218 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
219 op_params.input2_multiplier *= -1;
220 _kernel =
221 Eval<nnfw::cker::BinaryArithmeticOpType::SUB, uint8_t>(_lhs, _rhs, _output, op_params);
222 }
223 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
224 {
225 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
226 op_params.input2_multiplier *= -1;
227 _kernel =
228 Eval<nnfw::cker::BinaryArithmeticOpType::SUB, int8_t>(_lhs, _rhs, _output, op_params);
229 }
230
231 else
232 {
233 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::SUB>(
234 _lhs, _rhs, _output, activation, op_params);
235 }
236 break;
238 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
239 {
241 setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
242 _kernel =
243 Eval<nnfw::cker::BinaryArithmeticOpType::MUL, uint8_t>(_lhs, _rhs, _output, op_params);
244 }
245 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
246 {
248 setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
249 _kernel =
250 Eval<nnfw::cker::BinaryArithmeticOpType::MUL, int8_t>(_lhs, _rhs, _output, op_params);
251 }
252 else
253 {
254 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::MUL>(
255 _lhs, _rhs, _output, activation, op_params);
256 }
257 break;
259 if (_lhs->data_type() == OperandType::FLOAT32)
260 {
261 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::DIV>(
262 _lhs, _rhs, _output, activation, op_params);
263 }
264 else
265 {
266 // TODO Support quantized type
267 // TODO Support integer type with zero check
268 throw std::runtime_error{
269 "BinaryArithmetic(Div): Div operation does not support non-float data types yet"};
270 }
271 break;
272 default:
273 throw std::runtime_error{"BinaryArithmetic: Unsupported BinaryArithmetic type"};
274 }
275}
276
278
279} // namespace onert::backend::cpu::ops
void ReplaceWith(int dimensions_count, const int32_t *dims_data)
Definition Shape.h:203
A tensor class that is portable for other backends.
ir::DataType data_type() const override final
std::function< void(const IPortableTensor *, const IPortableTensor *, IPortableTensor *)> _kernel
void configure(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output, const ir::Activation activation, const ArithmeticType arithmetic_type)
nnfw::cker::Shape _output_shape
nnfw::cker::Shape _rhs_shape
nnfw::cker::BinaryArithmeticOpParam _op_params
nnfw::cker::Shape _lhs_shape
bool _need_broadcast
bool ProcessBroadcastShapes(const Shape &shape0, const Shape &shape1, BinaryArithmeticOpParam *params)
nnfw::cker::Shape getShape(const IPortableTensor *tensor)
void QuantizeMultiplier(double double_multiplier, int32_t *quantized_multiplier, int *shift)
void CalculateActivationRangeQuantized(ir::Activation activation, const IPortableTensor *output, int32_t *act_min, int32_t *act_max)
void CalculateActivationRange(ir::Activation activation, T *activation_min, T *activation_max)