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
19#include "../KernelGenerator.h"
20#include "../Validator.h"
21
23
24namespace onert::backend::cpu
25{
26
27void Validator::visit(const ir::operation::BinaryArithmetic &) { _supported = true; }
28
29ops::ArithmeticType
31{
32 switch (arithmetic_type_ir)
33 {
42 default:
43 throw std::runtime_error("cpu KernelGenerator : Not supported operation yet");
44 }
45}
46
47void KernelGenerator::visit(const ir::operation::BinaryArithmetic &node)
48{
49 const auto ofm_index{node.getOutputs().at(0)};
50 const auto lhs_index{node.getInputs().at(ir::operation::BinaryArithmetic::Input::LHS)};
51 const auto rhs_index{node.getInputs().at(ir::operation::BinaryArithmetic::Input::RHS)};
52
53 const auto activation = node.param().activation;
54
55 auto ofm_tensor = _tensor_reg->getPortableTensor(ofm_index);
56 auto lhs_tensor = _tensor_reg->getPortableTensor(lhs_index);
57 auto rhs_tensor = _tensor_reg->getPortableTensor(rhs_index);
58
59 auto fn = std::make_unique<ops::BinaryArithmeticLayer>();
60
61 fn->configure(lhs_tensor, rhs_tensor, ofm_tensor, activation,
63
64 _return_fn = std::move(fn);
65}
66
67} // namespace onert::backend::cpu
68
70{
71
72namespace
73{
74
75template <nnfw::cker::BinaryArithmeticOpType arithmetic_type, typename T> struct Eval
76{
82
83 Eval(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output,
85 : _op_params(std::move(op_params)), _need_broadcast(false)
86 {
87 if (!output->is_dynamic())
88 updateCache(lhs, rhs, output);
89 }
90
91 void updateCache(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output)
92 {
96 _need_broadcast = nnfw::cker::ProcessBroadcastShapes(_lhs_shape, _rhs_shape, &_op_params);
97 }
98
99 void operator()(const IPortableTensor *lhs, const IPortableTensor *rhs, IPortableTensor *output)
100 {
101 // Assume dynamic tensors never become static and static ones never change shape since
102 // configure()
103 if (output->is_dynamic())
104 updateCache(lhs, rhs, output);
105 else
106 assert(_lhs_shape == getShape(lhs) && _rhs_shape == getShape(rhs) &&
107 _output_shape == getShape(output));
108 auto lhs_buffer = getBuffer<T>(lhs);
109 auto rhs_buffer = getBuffer<T>(rhs);
110 auto output_buffer = getBuffer<T>(output);
111 if (_need_broadcast)
112 {
113 nnfw::cker::BroadcastBinaryArithmeticOp<arithmetic_type>(
114 _op_params, _lhs_shape, lhs_buffer, _rhs_shape, rhs_buffer, _output_shape, output_buffer);
115 }
116 else
117 {
118 nnfw::cker::BinaryArithmeticOp<arithmetic_type>(
119 _op_params, _lhs_shape, lhs_buffer, _rhs_shape, rhs_buffer, _output_shape, output_buffer);
120 }
121 }
122};
123
124template <nnfw::cker::BinaryArithmeticOpType arithmetic_type>
125std::function<void(const IPortableTensor *, const IPortableTensor *, IPortableTensor *)>
126generateKernelGeneric(const IPortableTensor *lhs, const IPortableTensor *rhs,
127 IPortableTensor *output, const ir::Activation activation,
129{
130 switch (lhs->data_type())
131 {
132 case OperandType::FLOAT32:
133 {
134 float output_activation_min = 0, output_activation_max = 0;
135 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
136 op_params.float_activation_max = output_activation_max;
137 op_params.float_activation_min = output_activation_min;
138 return Eval<arithmetic_type, float>(lhs, rhs, output, op_params);
139 break;
140 }
141 case OperandType::INT32:
142 {
143 int32_t output_activation_min = 0, output_activation_max = 0;
144 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
145 op_params.quantized_activation_max = output_activation_max;
146 op_params.quantized_activation_min = output_activation_min;
147 return Eval<arithmetic_type, int32_t>(lhs, rhs, output, op_params);
148 break;
149 }
150 case OperandType::INT64:
151 {
152 int64_t output_activation_min = 0, output_activation_max = 0;
153 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
154 op_params.int64_activation_max = output_activation_max;
155 op_params.int64_activation_min = output_activation_min;
156 return Eval<arithmetic_type, int64_t>(lhs, rhs, output, op_params);
157 break;
158 }
159 case OperandType::BOOL8:
160 {
161 if (activation != ir::Activation::NONE)
162 throw std::runtime_error(
163 "BinaryArithmetic(generic): Fused activation is not supported with bool8 type");
164 int32_t output_activation_min = 0, output_activation_max = 0;
165 CalculateActivationRange(activation, &output_activation_min, &output_activation_max);
166 static_assert(sizeof(bool) == 1, "cpu backend supports bool type which is 1 byte");
167 return Eval<arithmetic_type, bool>(lhs, rhs, output, op_params);
168 break;
169 }
170 default:
171 throw std::runtime_error{"BinaryArithmetic(generic): Unsupported data type"};
172 }
173}
174
175void setAddOrSubQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
176 IPortableTensor *output, ir::Activation activation,
178{
179 int32_t output_activation_min, output_activation_max;
180 CalculateActivationRangeQuantized(activation, output, &output_activation_min,
181 &output_activation_max);
182 nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
183 op_params.quantized_activation_max = output_activation_max;
184 op_params.quantized_activation_min = output_activation_min;
185 // Parameters for scaled quantized computation
186 op_params.left_shift = 20;
187 // Zero-points of input and output tensors
188 op_params.input1_offset = -lhs->data_zero_point();
189 op_params.input2_offset = -rhs->data_zero_point();
190 op_params.output_offset = output->data_zero_point();
191
192 // Compute normalized scale for _lhs and _rhs values,
193 // and represent in 32-bit fixed point
194 const double norm_max_scale = 2 * std::max(lhs->data_scale(), rhs->data_scale());
195 const double real_lhs_scale = lhs->data_scale() / norm_max_scale;
196 const double real_rhs_scale = rhs->data_scale() / norm_max_scale;
197 // output scale is used to normalize final result, so we invert the scale here
198 const double real_output_scale =
199 norm_max_scale / (output->data_scale() * (1 << op_params.left_shift));
200
201 // Represent the scales as fixed int32_t multipliers, and int32_t shifts
202 QuantizeMultiplier(real_lhs_scale, &op_params.input1_multiplier, &op_params.input1_shift);
203 QuantizeMultiplier(real_rhs_scale, &op_params.input2_multiplier, &op_params.input2_shift);
204 QuantizeMultiplier(real_output_scale, &op_params.output_multiplier, &op_params.output_shift);
205}
206
207void setMulQuant8Params(const IPortableTensor *lhs, const IPortableTensor *rhs,
208 IPortableTensor *output, ir::Activation activation,
210{
211 int32_t output_activation_min, output_activation_max;
212 CalculateActivationRangeQuantized(activation, output, &output_activation_min,
213 &output_activation_max);
214 nnfw::cker::BinaryArithmeticOpParam &op_params = *params;
215
216 op_params.quantized_activation_max = output_activation_max;
217 op_params.quantized_activation_min = output_activation_min;
218 op_params.input1_offset = -lhs->data_zero_point();
219 op_params.input2_offset = -rhs->data_zero_point();
220 op_params.output_offset = output->data_zero_point();
221
222 double real_multiplier = lhs->data_scale() * rhs->data_scale() / output->data_scale();
223 QuantizeMultiplier(real_multiplier, &op_params.output_multiplier, &op_params.output_shift);
224}
225
226} // namespace
227
229 IPortableTensor *output, const ir::Activation activation,
230 const ArithmeticType arithmetic_type)
231{
232 assert(lhs != nullptr);
233 assert(rhs != nullptr);
234 assert(output != nullptr);
235
236 _lhs = lhs;
237 _rhs = rhs;
238 _output = output;
239
241 switch (arithmetic_type)
242 {
244 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
245 {
246 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
247 _kernel =
248 Eval<nnfw::cker::BinaryArithmeticOpType::ADD, uint8_t>(_lhs, _rhs, _output, op_params);
249 }
250 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
251 {
252 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
253 _kernel =
254 Eval<nnfw::cker::BinaryArithmeticOpType::ADD, int8_t>(_lhs, _rhs, _output, op_params);
255 }
256
257 else
258 {
259 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::ADD>(
260 _lhs, _rhs, _output, activation, op_params);
261 }
262 break;
264 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
265 {
266 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
267 op_params.input2_multiplier *= -1;
268 _kernel =
269 Eval<nnfw::cker::BinaryArithmeticOpType::SUB, uint8_t>(_lhs, _rhs, _output, op_params);
270 }
271 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
272 {
273 setAddOrSubQuant8Params(_lhs, _rhs, _output, activation, &op_params);
274 op_params.input2_multiplier *= -1;
275 _kernel =
276 Eval<nnfw::cker::BinaryArithmeticOpType::SUB, int8_t>(_lhs, _rhs, _output, op_params);
277 }
278
279 else
280 {
281 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::SUB>(
282 _lhs, _rhs, _output, activation, op_params);
283 }
284 break;
286 if (_lhs->data_type() == OperandType::QUANT_UINT8_ASYMM)
287 {
289 setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
290 _kernel =
291 Eval<nnfw::cker::BinaryArithmeticOpType::MUL, uint8_t>(_lhs, _rhs, _output, op_params);
292 }
293 else if (_lhs->data_type() == OperandType::QUANT_INT8_ASYMM)
294 {
296 setMulQuant8Params(_lhs, _rhs, _output, activation, &op_params);
297 _kernel =
298 Eval<nnfw::cker::BinaryArithmeticOpType::MUL, int8_t>(_lhs, _rhs, _output, op_params);
299 }
300 else
301 {
302 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::MUL>(
303 _lhs, _rhs, _output, activation, op_params);
304 }
305 break;
307 if (_lhs->data_type() == OperandType::FLOAT32)
308 {
309 _kernel = generateKernelGeneric<nnfw::cker::BinaryArithmeticOpType::DIV>(
310 _lhs, _rhs, _output, activation, op_params);
311 }
312 else
313 {
314 // TODO Support quantized type
315 // TODO Support integer type with zero check
316 throw std::runtime_error{
317 "BinaryArithmetic(Div): Div operation does not support non-float data types yet"};
318 }
319 break;
320 default:
321 throw std::runtime_error{"BinaryArithmetic: Unsupported BinaryArithmetic type"};
322 }
323}
324
326
327} // namespace onert::backend::cpu::ops
void ReplaceWith(int dimensions_count, const int32_t *dims_data)
Definition Shape.h:206
A tensor class that is portable for other backends.
ir::DataType data_type() const override final
std::unique_ptr< exec::IFunction > _return_fn
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)
const OperandIndex & at(IOIndex set_index) const
const OperandIndexSequence & getOutputs() const override
Definition Operation.h:54
OperandIndexSequence & getInputs()
Definition Operation.h:51
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)
ops::ArithmeticType convertArithmeticType(ir::operation::BinaryArithmetic::ArithmeticType arithmetic_type_ir)
void CalculateActivationRange(ir::Activation activation, T *activation_min, T *activation_max)