ONE - On-device Neural Engine
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
ConvolutionLayer.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018 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 "ConvolutionLayer.h"
18#include "OperationUtils.h"
20
21#include "../Tensor.h"
22#include "ir/Padding.h"
23#include <cker/operation/Conv.h>
24
26{
28 : _input(nullptr), _kernel(nullptr), _bias(nullptr), _output(nullptr),
29 _paddingType(ir::PaddingType::EXPLICIT), _paddingLeft(0), _paddingTop(0), _paddingRight(0),
30 _paddingBottom(0), _strideWidth(0), _strideHeight(0), _dilationWidthFactor(1),
31 _dilationHeightFactor(1), _activation(ir::Activation::NONE),
32 _conv_kernel(new nnfw::cker::Conv()), _prepare(false), _is_cachable_weights(false),
33 _is_hybrid(false)
34{
35 // DO NOTHING
36}
37
39
40void ConvolutionLayer::convFloat32()
41{
42 float output_activation_min = 0, output_activation_max = 0;
43 CalculateActivationRange(_activation, &output_activation_min, &output_activation_max);
44
45 nnfw::cker::ConvParams op_params;
49 op_params.stride_width = _strideWidth;
50 op_params.stride_height = _strideHeight;
53 op_params.float_activation_min = output_activation_min;
54 op_params.float_activation_max = output_activation_max;
55
57 kernel(op_params, getShape(_input), getBuffer<float>(_input), getShape(_kernel),
58 getBuffer<float>(_kernel), getShape(_bias), getBuffer<float>(_bias), getShape(_output),
59 getBuffer<float>(_output));
60}
61
62void ConvolutionLayer::convQ8uPerTensor()
63{
64 int32_t output_activation_min = 0;
65 int32_t output_activation_max = 0;
67 &output_activation_max);
68
69 double real_multiplier = 0.0;
70 int32_t output_multiplier = 0;
71 int32_t output_shift = 0;
73 QuantizeMultiplier(real_multiplier, &output_multiplier, &output_shift);
74
75 nnfw::cker::ConvParams op_params;
76 op_params.stride_width = _strideWidth;
77 op_params.stride_height = _strideHeight;
80 op_params.padding_type = getPaddingType(_paddingType);
83 op_params.input_offset = -_input->data_zero_point();
86 op_params.output_multiplier = output_multiplier;
87 op_params.output_shift = output_shift;
88 op_params.quantized_activation_min = output_activation_min;
89 op_params.quantized_activation_max = output_activation_max;
90 op_params.is_replaced_weights = true;
91
93 kernel(op_params, getShape(_input), getBuffer<uint8_t>(_input), getShape(_kernel),
94 getBuffer<uint8_t>(_kernel), getShape(_bias), getBuffer<int32_t>(_bias), getShape(_output),
95 getBuffer<uint8_t>(_output));
96}
97
98void ConvolutionLayer::convQ8uPerChannel()
99{
100 nnfw::cker::ConvParams op_params;
103 op_params.stride_width = _strideWidth;
104 op_params.stride_height = _strideHeight;
107 op_params.input_offset = -_input->data_zero_point();
109 int32_t output_activation_min = 0;
110 int32_t output_activation_max = 0;
112 &output_activation_max);
113 op_params.quantized_activation_min = output_activation_min;
114 op_params.quantized_activation_max = output_activation_max;
115 // NOTE: The following fields of ConvParams are not used:
116 // padding_type, weights_offset, output_{multiplier,shift}, float_activation_{min,max}
117
119 kernel(op_params, getShape(_input), getBuffer<uint8_t>(_input), getShape(_kernel),
120 getBuffer<uint8_t>(_kernel), _kernel->data_zero_points().data(), getShape(_bias),
121 getBuffer<int32_t>(_bias), getShape(_output), getBuffer<uint8_t>(_output));
122}
123
124void ConvolutionLayer::convQ8i()
125{
126 int32_t output_activation_min = 0;
127 int32_t output_activation_max = 0;
129 &output_activation_max);
130
131 nnfw::cker::ConvParams op_params;
132 op_params.input_offset = -_input->data_zero_point();
134 op_params.stride_height = _strideHeight;
135 op_params.stride_width = _strideWidth;
140 op_params.quantized_activation_min = output_activation_min;
141 op_params.quantized_activation_max = output_activation_max;
142
144 kernel(op_params, getShape(_input), reinterpret_cast<const int8_t *>(_input->buffer()),
145 getShape(_kernel), reinterpret_cast<const int8_t *>(_kernel->buffer()), getShape(_bias),
146 reinterpret_cast<const int32_t *>(_bias->buffer()), getShape(_output),
147 reinterpret_cast<int8_t *>(_output->buffer()));
148}
149
150void ConvolutionLayer::convQ8iHybridPerChannel()
151{
152 float output_activation_min = 0;
153 float output_activation_max = 0;
154 CalculateActivationRange(_activation, &output_activation_min, &output_activation_max);
155
156 const int batch_size = getShape(_input).Dims(0);
157 if (batch_size == 0)
158 throw std::runtime_error{"Convolution input batch_size = 0"};
159 auto input_shape = getShape(_input);
160 const int input_size = input_shape.FlatSize() / batch_size;
161
162 auto input_quantized_ptr = _hybrid_arena->input_quantized.data();
163 auto input_scaling_factors_ptr = _hybrid_arena->input_scaling_factors.data();
164 auto input_offsets_ptr = _hybrid_arena->input_offsets.data();
165 for (int b = 0; b < batch_size; ++b)
166 {
167 const int offset = b * input_size;
169 reinterpret_cast<const float *>(_input->buffer()) + offset, input_size,
170 input_quantized_ptr + offset, &input_scaling_factors_ptr[b], &input_offsets_ptr[b]);
171 }
172 nnfw::cker::ConvParams op_params;
176 op_params.stride_width = _strideWidth;
177 op_params.stride_height = _strideHeight;
180 op_params.float_activation_min = output_activation_min;
181 op_params.float_activation_max = output_activation_max;
182
183 const auto *filter_per_channel_scales = _kernel->data_scales().data();
185 op_params, input_scaling_factors_ptr, getShape(_input), input_quantized_ptr, getShape(_kernel),
186 reinterpret_cast<const int8_t *>(_kernel->buffer()), getShape(_bias),
187 reinterpret_cast<const float *>(_bias->buffer()), getShape(_output),
188 reinterpret_cast<float *>(_output->buffer()), filter_per_channel_scales, input_offsets_ptr);
189}
190
192 const IPortableTensor *bias, const ir::PaddingType paddingType,
193 const uint32_t paddingLeft, const uint32_t paddingRight,
194 const uint32_t paddingTop, const uint32_t paddingBottom,
195 const uint32_t strideWidth, const uint32_t strideHeight,
196 const uint32_t dilationWidthFactor,
197 const uint32_t dilationHeightFactor,
198 const ir::Activation activation, IPortableTensor *output,
199 bool is_cachable_weights)
200{
201 _input = input;
202 _kernel = kernel;
203 _bias = bias;
204 _paddingType = paddingType;
205 _paddingLeft = paddingLeft;
206 _paddingRight = paddingRight;
207 _paddingTop = paddingTop;
208 _paddingBottom = paddingBottom;
209 _strideWidth = strideWidth;
210 _strideHeight = strideHeight;
211 _dilationWidthFactor = dilationWidthFactor;
212 _dilationHeightFactor = dilationHeightFactor;
213 _activation = activation;
214 _output = output;
215 _is_cachable_weights = is_cachable_weights;
216 _is_hybrid = _input->data_type() == OperandType::FLOAT32 &&
217 _kernel->data_type() == OperandType::QUANT_INT8_SYMM;
218}
219
221{
222 prepare();
223 if (_input->is_dynamic() || _kernel->is_dynamic())
224 {
225 const auto ifm_shape = _input->getShape().asFeature();
226 const auto ofm_shape = _output->getShape().asFeature();
227 // Kernel format is [depth_out, kernel_height, kernel_width, depth_in].
228 const auto ker_shape = _kernel->getShape();
229 const auto ker_height = ker_shape.dim(1);
230 const auto ker_width = ker_shape.dim(2);
231
232 ir::Stride stride;
233 stride.vertical = _strideWidth;
234 stride.horizontal = _strideWidth;
235
236 ir::Padding param_padding;
237 param_padding.type = _paddingType;
238 param_padding.param.left = _paddingLeft;
239 param_padding.param.right = _paddingRight;
240 param_padding.param.top = _paddingTop;
241 param_padding.param.bottom = _paddingBottom;
242
243 const auto padding =
244 ir::calculatePadding(param_padding, ifm_shape, ofm_shape, stride, ker_width, ker_height,
246
247 _paddingLeft = padding.left;
248 _paddingRight = padding.right;
249 _paddingTop = padding.top;
250 _paddingBottom = padding.bottom;
251 }
252 if (_is_hybrid)
253 {
254 convQ8iHybridPerChannel();
255 }
256 else if (_input->data_type() == OperandType::FLOAT32)
257 {
258 convFloat32();
259 }
260 else if (_input->data_type() == OperandType::QUANT_UINT8_ASYMM)
261 {
262 const bool per_channel_quantized = _kernel->data_scales().size() > 1;
263 if (per_channel_quantized)
264 convQ8uPerChannel();
265 else
266 convQ8uPerTensor();
267 }
268 else if (_input->data_type() == OperandType::QUANT_INT8_ASYMM)
269 {
270 convQ8i();
271 }
272 else
273 {
274 throw std::runtime_error{"Conv: unsupported data type"};
275 }
276}
277
279{
280 if (_prepare)
281 return;
282
283 if (_is_hybrid)
284 {
285 // ensure weight is per-channel quantized.
286 int32_t kernel_output_channel = getShape(_kernel).Dims(0);
287 // zero_points comes from flatbuffer vector. Its size is within uint32_t range.
288 size_t kernel_zerop_cnt = _kernel->data_scales().size();
289 // promote to int64_t to compare int32_t and uint32_t
290 if ((int64_t)kernel_output_channel != (int64_t)kernel_zerop_cnt)
291 throw std::runtime_error{"Conv2D hybrid supports only per-channel quantized weight."};
292
293 // allocate memory for activation quantization.
294 // - quantized values (int8_t type and same shape of original input)
295 // - quantization params (= scale/zeropoint for each input)
296 auto input_shape = getShape(_input);
297 const int batch_size = input_shape.Dims(0);
298 const int input_size = input_shape.FlatSize() / batch_size;
299 _hybrid_arena = std::make_unique<nnfw::cker::ConvHybridTempArena>(batch_size, input_size);
300 _prepare = true;
301 return;
302 }
303
305 if (_input->data_type() == OperandType::FLOAT32 && _is_cachable_weights)
306 {
307 bool is_transposed = false;
308 kernel.prepareF32(getShape(_kernel), getBuffer<float>(_kernel), getPaddingType(_paddingType),
310
311 // Decrease reference of _kernel(weights) only when _kernel is constant
312 if (is_transposed)
313 {
314 auto kernel_tensor = dynamic_cast<const Tensor *>(_kernel);
315 if (kernel_tensor)
316 // TODO Remove const_cast
317 const_cast<Tensor *>(kernel_tensor)->decrease_ref();
318 }
319 }
320 else if (_input->data_type() == OperandType::QUANT_UINT8_ASYMM && _is_cachable_weights &&
322 {
323 const bool per_channel_quantized = _kernel->data_scales().size() > 1;
324 if (per_channel_quantized)
325 {
328 _kernel->data_scales().size(), getShape(_kernel).Dims(0),
330 }
331 else
332 {
336 }
337 }
338 else if (_input->data_type() == OperandType::QUANT_INT8_ASYMM)
339 {
341 {
344 _kernel->data_scales().size(), getShape(_kernel).Dims(0),
346 }
347 else
348 {
349 throw std::runtime_error{"Conv2D: Int8 dynamic weight is not supported"};
350 }
351 }
352 _prepare = true;
353}
354
355} // namespace onert::backend::cpu::ops
void Conv(const float *input_data, const Dims< 4 > &input_dims, const float *filter_data, const Dims< 4 > &filter_dims, const float *bias_data, const Dims< 4 > &bias_dims, int stride_width, int stride_height, int pad_width, int pad_height, float *output_data, const Dims< 4 > &output_dims, float *im2col_data, const Dims< 4 > &im2col_dims)
std::vector< int > & per_channel_output_shift()
Definition Conv.h:151
void prepareQ8uPerTensor(const Shape &input_shape, const Shape &kernel_shape, const Shape &output_shape, uint32_t stride_width, uint32_t stride_height, uint32_t dilation_width_factor, uint32_t dilation_height_factor)
Definition Conv.h:74
void prepareF32(const Shape &filter_shape, const float *filter_data, PaddingType padding_type, bool &is_replaced_weights, uint32_t dilationWidthFactor, uint32_t dilationHeightFactor)
Definition Conv.h:60
std::vector< int32_t > & per_channel_output_multiplier()
Definition Conv.h:150
int32_t Dims(int i) const
Definition Shape.h:110
A tensor class that is portable for other backends.
const std::vector< float > & data_scales() const override final
float data_scale() const override final
int32_t data_zero_point() const override final
const std::vector< int32_t > & data_zero_points() const override
ir::DataType data_type() const override final
ir::Shape getShape() const override final
Get ir::Shape of tensor.
bool is_dynamic() const override final
Return true if the tensor needs dynamic allocation, meaning that during compile-time the outpus shape...
virtual uint8_t * buffer() const =0
std::unique_ptr< nnfw::cker::Conv > _conv_kernel
void configure(const IPortableTensor *input, const IPortableTensor *kernel, const IPortableTensor *bias, ir::PaddingType _paddingType, const uint32_t paddingLeft, const uint32_t paddingRight, const uint32_t paddingTop, const uint32_t paddingBottom, const uint32_t strideWidth, const uint32_t strideHeight, const uint32_t dilationWidthFactor, const uint32_t dilationHeightFactor, const ir::Activation activation, IPortableTensor *output, bool is_cachable_weights)
std::unique_ptr< nnfw::cker::ConvHybridTempArena > _hybrid_arena
__global uchar * offset(const Image *img, int x, int y)
Definition helpers.h:540
void HybridConvPerChannel(const ConvParams &params, float *scaling_factors_ptr, const Shape &input_shape, const int8_t *input_data, const Shape &filter_shape, const int8_t *filter_data, const Shape &bias_shape, const float *bias_data, const Shape &output_shape, float *output_data, const float *per_channel_scale, const int32_t *input_offset)
Definition Conv.h:314
void PortableAsymmetricQuantizeFloats(const float *values, const int size, int8_t *quantized_values, float *scaling_factor, int32_t *offset)
Definition topk_v2.h:30
nnfw::cker::Shape getShape(const IPortableTensor *tensor)
void GetQuantizedConvolutionMultipliersAndShifts(float input_scale, float output_scale, const float *filter_scales, size_t filter_scales_size, int num_channels, std::vector< int32_t > &per_channel_output_multiplier, std::vector< int > &per_channel_output_shift)
void QuantizeMultiplier(double double_multiplier, int32_t *quantized_multiplier, int *shift)
nnfw::cker::PaddingType getPaddingType(ir::PaddingType ir_padding_type)
void CalculateActivationRangeQuantized(ir::Activation activation, const IPortableTensor *output, int32_t *act_min, int32_t *act_max)
void GetQuantizedConvolutionMultiplier(const IPortableTensor *input, const IPortableTensor *filter, const IPortableTensor *bias, const IPortableTensor *output, double *multiplier)
const ExplicitPadding calculatePadding(const Padding &padding, const FeatureShape &ifm_shape, const FeatureShape &ofm_shape, const Stride &stride, uint32_t kw, uint32_t kh, uint32_t dwf=1, uint32_t dhf=1)
Definition Padding.cc:131
void CalculateActivationRange(ir::Activation activation, T *activation_min, T *activation_max)
Definition Dims.h:26
int16_t stride_height
Definition Types.h:146
PaddingValues padding_values
Definition Types.h:143
float float_activation_max
Definition Types.h:161
int32_t output_multiplier
Definition Types.h:154
int32_t weights_offset
Definition Types.h:152
int32_t output_offset
Definition Types.h:153
int16_t dilation_width_factor
Definition Types.h:147
float float_activation_min
Definition Types.h:160
int32_t quantized_activation_max
Definition Types.h:158
PaddingType padding_type
Definition Types.h:142
int16_t dilation_height_factor
Definition Types.h:148
int32_t quantized_activation_min
Definition Types.h:157
PaddingType type
Definition Padding.h:59
ExplicitPadding param
Definition Padding.h:60