ONE - On-device Neural Engine
Loading...
Searching...
No Matches
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
21namespace onert
22{
23namespace backend
24{
25namespace cpu
26{
27namespace ops
28{
29
30namespace
31{
32
33template <nnfw::cker::BinaryArithmeticOpType arithmetic_type, typename T> struct Eval
34{
40
41 Eval(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output,
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 {
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
82template <nnfw::cker::BinaryArithmeticOpType arithmetic_type>
83std::function<void(const IPortableTensor *, const IPortableTensor *, IPortableTensor *)>
84generateKernelGeneric(const IPortableTensor *lhs, const IPortableTensor *rhs,
85 IPortableTensor *output, const ir::Activation activation,
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 case OperandType::INT64:
109 {
110 int64_t output_activation_min = 0, output_activation_max = 0;
111 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
112 op_params.int64_activation_max = output_activation_max;
113 op_params.int64_activation_min = output_activation_min;
114 return Eval<arithmetic_type, int64_t>(lhs, rhs, output, op_params);
115 break;
116 }
117 case OperandType::BOOL8:
118 {
119 if (activation != ir::Activation::NONE)
120 throw std::runtime_error(
121 "BinaryArithmetic(generic): Fused activation is not supported with bool8 type");
122 int32_t output_activation_min = 0, output_activation_max = 0;
123 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
124 static_assert(sizeof(bool) == 1, "cpu backend supports bool type which is 1 byte");
125 return Eval<arithmetic_type, bool>(lhs, rhs, output, op_params);
126 break;
127 }
128 default:
129 throw std::runtime_error{"BinaryArithmetic(generic): Unsupported data type"};
130 }
131}
132
133void setAddOrSubQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
134 IPortableTensor *output, ir::Activation activation,
136{
137 int32_t output_activation_min, output_activation_max;
138 CalculateActivationRangeQuantized(activation, output, &output_activation_min,
139 &output_activation_max);
140 nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
141 op_params.quantized_activation_max = output_activation_max;
142 op_params.quantized_activation_min = output_activation_min;
143 // Parameters for scaled quantized computation
144 op_params.left_shift = 20;
145 // Zero-points of input and output tensors
146 op_params.input1_offset = -lhs->data_zero_point();
147 op_params.input2_offset = -rhs->data_zero_point();
148 op_params.output_offset = output->data_zero_point();
149
150 // Compute normalized scale for _lhs and _rhs values,
151 // and represent in 32-bit fixed point
152 const double norm_max_scale = 2 * std::max(lhs->data_scale(), rhs->data_scale());
153 const double real_lhs_scale = lhs->data_scale() / norm_max_scale;
154 const double real_rhs_scale = rhs->data_scale() / norm_max_scale;
155 // output scale is used to normalize final result, so we invert the scale here
156 const double real_output_scale =
157 norm_max_scale / (output->data_scale() * (1 << op_params.left_shift));
158
159 // Represent the scales as fixed int32_t multipliers, and int32_t shifts
160 QuantizeMultiplier(real_lhs_scale, &op_params.input1_multiplier, &op_params.input1_shift);
161 QuantizeMultiplier(real_rhs_scale, &op_params.input2_multiplier, &op_params.input2_shift);
162 QuantizeMultiplier(real_output_scale, &op_params.output_multiplier, &op_params.output_shift);
163}
164
165void setMulQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
166 IPortableTensor *output, ir::Activation activation,
168{
169 int32_t output_activation_min, output_activation_max;
170 CalculateActivationRangeQuantized(activation, output, &output_activation_min,
171 &output_activation_max);
172 nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
173
174 op_params.quantized_activation_max = output_activation_max;
175 op_params.quantized_activation_min = output_activation_min;
176 op_params.input1_offset = -lhs->data_zero_point();
177 op_params.input2_offset = -rhs->data_zero_point();
178 op_params.output_offset = output->data_zero_point();
179
180 double real_multiplier = lhs->data_scale() * rhs->data_scale() / output->data_scale();
181 QuantizeMultiplier(real_multiplier, &op_params.output_multiplier, &op_params.output_shift);
182}
183
184} // namespace
185
187 IPortableTensor *output, const ir::Activation activation,
188 const ArithmeticType arithmetic_type)
189{
190 assert(lhs != nullptr);
191 assert(rhs != nullptr);
192 assert(output != nullptr);
193
194 _lhs = lhs;
195 _rhs = rhs;
196 _output = output;
197
199 switch (arithmetic_type)
200 {
202 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
203 {
204 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
205 _kernel =
206 Eval<nnfw::cker::BinaryArithmeticOpType::ADD, uint8_t>(_lhs, _rhs, _output, op_params);
207 }
208 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
209 {
210 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
211 _kernel =
212 Eval<nnfw::cker::BinaryArithmeticOpType::ADD, int8_t>(_lhs, _rhs, _output, op_params);
213 }
214
215 else
216 {
217 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::ADD>(
218 _lhs, _rhs, _output, activation, op_params);
219 }
220 break;
222 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
223 {
224 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
225 op_params.input2_multiplier *= -1;
226 _kernel =
227 Eval<nnfw::cker::BinaryArithmeticOpType::SUB, uint8_t>(_lhs, _rhs, _output, op_params);
228 }
229 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
230 {
231 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
232 op_params.input2_multiplier *= -1;
233 _kernel =
234 Eval<nnfw::cker::BinaryArithmeticOpType::SUB, int8_t>(_lhs, _rhs, _output, op_params);
235 }
236
237 else
238 {
239 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::SUB>(
240 _lhs, _rhs, _output, activation, op_params);
241 }
242 break;
244 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
245 {
247 setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
248 _kernel =
249 Eval<nnfw::cker::BinaryArithmeticOpType::MUL, uint8_t>(_lhs, _rhs, _output, op_params);
250 }
251 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
252 {
254 setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
255 _kernel =
256 Eval<nnfw::cker::BinaryArithmeticOpType::MUL, int8_t>(_lhs, _rhs, _output, op_params);
257 }
258 else
259 {
260 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::MUL>(
261 _lhs, _rhs, _output, activation, op_params);
262 }
263 break;
265 if (_lhs->data_type() == OperandType::FLOAT32)
266 {
267 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::DIV>(
268 _lhs, _rhs, _output, activation, op_params);
269 }
270 else
271 {
272 // TODO Support quantized type
273 // TODO Support integer type with zero check
274 throw std::runtime_error{
275 "BinaryArithmetic(Div): Div operation does not support non-float data types yet"};
276 }
277 break;
278 default:
279 throw std::runtime_error{"BinaryArithmetic: Unsupported BinaryArithmetic type"};
280 }
281}
282
284
285} // namespace ops
286} // namespace cpu
287} // namespace backend
288} // namespace onert
void ReplaceWith(int dimensions_count, const int32_t *dims_data)
Definition Shape.h:130
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)