Imported Upstream version 1.8.0
[platform/core/ml/nnfw.git] / compiler / luci / pass / src / QuantizationUtils.cpp
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 "QuantizationUtils.h"
18
19 #include <luci/Log.h>
20
21 #include <iostream>
22 #include <cmath>
23
24 namespace luci
25 {
26
27 uint8_t fp32_to_uint8_cast(float f)
28 {
29   assert(std::numeric_limits<uint8_t>::min() <= f);
30   assert(f <= std::numeric_limits<uint8_t>::max());
31   return static_cast<uint8_t>(f);
32 }
33
34 void compute_sym_scale_zp(float min, float max, float &scaling_factor, int64_t &zp,
35                           float &nudged_min, float &nudged_max)
36 {
37   assert(min != max);
38
39   const int32_t kMaxScale = std::numeric_limits<int16_t>::max();
40   const int32_t kMinScale = -kMaxScale;
41   const double qmin_double = kMinScale;
42   const double qmax_double = kMaxScale;
43   const double rmin = std::fmin(0, min);
44   const double rmax = std::fmax(0, max);
45   double scale_factor_from_min_side{0};
46   double scale_factor_from_max_side{0};
47
48   if ((qmin_double * rmin) > 0)
49     scale_factor_from_min_side = rmin / qmin_double;
50
51   if ((qmax_double * rmax) > 0)
52     scale_factor_from_max_side = rmax / qmax_double;
53
54   scaling_factor = scale_factor_from_min_side > scale_factor_from_max_side
55                        ? scale_factor_from_min_side
56                        : scale_factor_from_max_side;
57   zp = 0;
58   nudged_min = static_cast<float>(qmin_double * scaling_factor);
59   nudged_max = static_cast<float>(qmax_double * scaling_factor);
60 }
61
62 void compute_asym_scale_zp(float min, float max, float &scaling_factor, int64_t &zp,
63                            float &nudged_min, float &nudged_max)
64 {
65   LOGGER(l);
66
67   assert(min <= max);
68   const int32_t kMinScale = 0;
69   const int32_t kMaxScale = 255;
70   const double qmin_double = kMinScale;
71   const double qmax_double = kMaxScale;
72   const double rmin = std::fmin(0, min);
73   const double rmax = std::fmax(0, max);
74
75   double scale = (rmax - rmin) / (qmax_double - qmin_double);
76   double zero_point_double = 0;
77   uint8_t nudged_zero_point = 0;
78   if (scale == 0)
79   {
80     WARN(l) << "The minimum and maximum values are the same." << std::endl;
81     if (min >= 0 && max >= 0)
82       zero_point_double = kMinScale;
83     else
84       zero_point_double = kMaxScale;
85   }
86   else
87     zero_point_double = qmin_double - rmin / scale;
88   if (min >= 0)
89   {
90     assert(min >= 0 && max >= 0);
91     nudged_zero_point = kMinScale;
92     scale = max / (qmax_double - qmin_double);
93     if (min > 0 && max > 0)
94       WARN(l) << "The minimum and maximum values are all positive." << std::endl;
95   }
96   else if (max < 0)
97   {
98     assert(min < 0 && max < 0);
99     nudged_zero_point = kMaxScale;
100     scale = -min / (qmax_double - qmin_double);
101     WARN(l) << "The minimum and maximum values are all negative." << std::endl;
102   }
103   else
104   {
105     assert(min < 0 && max >= 0);
106     nudged_zero_point = fp32_to_uint8_cast(std::round(zero_point_double));
107   }
108
109   // protect scale from being very low due to overflow
110   if (scale < 1e-5)
111   {
112     scale = 1e-5;
113     nudged_zero_point = fp32_to_uint8_cast(std::round(qmin_double - rmin / scale));
114   }
115
116   nudged_min = static_cast<float>((qmin_double - nudged_zero_point) * scale);
117   nudged_max = static_cast<float>((qmax_double - nudged_zero_point) * scale);
118
119   scaling_factor = scale;
120   zp = nudged_zero_point;
121 }
122
123 bool get_channel_dim_index(CircleConst *node, loco::TensorShape &dimension, int &channel_dim_index)
124 {
125   auto succs = loco::succs(node);
126   if (succs.size() != 1) // assume weights is used by only one node
127     return false;
128
129   for (auto out : succs)
130   {
131     auto conv = dynamic_cast<CircleConv2D *>(out);
132     auto dw_conv = dynamic_cast<CircleDepthwiseConv2D *>(out);
133     auto tw_conv = dynamic_cast<CircleTransposeConv *>(out);
134     auto fc = dynamic_cast<CircleFullyConnected *>(out);
135
136     // Refer to https://github.com/Samsung/ONE/pull/2448.
137     if ((conv != nullptr && conv->filter() == node) ||
138         (tw_conv != nullptr && tw_conv->filter() == node)) // OHWI
139     {
140       assert(node->rank() == 4);
141       dimension.dim(0).set(node->dim(0).value());
142       dimension.dim(1).set(node->dim(1).value());
143       dimension.dim(2).set(node->dim(2).value());
144       dimension.dim(3).set(node->dim(3).value());
145       channel_dim_index = 0; // Set channel_dim_index based on "O"
146       return true;
147     }
148     else if (dw_conv != nullptr && dw_conv->filter() == node) // IHWC
149     {
150       assert(node->rank() == 4);
151       dimension.dim(0).set(node->dim(0).value());
152       dimension.dim(1).set(node->dim(1).value());
153       dimension.dim(2).set(node->dim(2).value());
154       dimension.dim(3).set(node->dim(3).value());
155       channel_dim_index = 3; // Set channel_dim_index based on "C"
156       return true;
157     }
158     else if (fc != nullptr && fc->weights() == node) // OI
159     {
160       assert(node->rank() == 2);
161       dimension.dim(0).set(node->dim(0).value());
162       dimension.dim(1).set(1); // Set FC layer like CONV
163       dimension.dim(2).set(1);
164       dimension.dim(3).set(node->dim(1).value());
165       channel_dim_index = 0; // Set channel_dim_index based on "O"
166       return true;
167     }
168     else
169     {
170       // node does not support channle-wise quantization
171       assert(false);
172     }
173   }
174
175   return false;
176 }
177
178 uint32_t cal_offset(loco::TensorShape &dimension, uint32_t *indices)
179 {
180   return indices[0] * dimension.dim(1).value() * dimension.dim(2).value() *
181              dimension.dim(3).value() +
182          indices[1] * dimension.dim(2).value() * dimension.dim(3).value() +
183          indices[2] * dimension.dim(3).value() + indices[3];
184 }
185
186 } // namespace luci