Line data Source code
1 : // SPDX-License-Identifier: Apache-2.0
2 : /**
3 : * @file quantizer.cpp
4 : * @date 10 December 2024
5 : * @brief This defines quantizers for different types of quantization schemes
6 : * @see https://github.com/nnstreamer/nntrainer
7 : * @author Donghyeon Jeong <dhyeon.jeong@samsung.com>
8 : * @bug No known bugs except for NYI items
9 : */
10 :
11 : #include <math.h>
12 : #include <quantizer.h>
13 : #include <tensor.h>
14 :
15 : namespace nntrainer {
16 :
17 : /**
18 : * @brief Helper function for clipping
19 : *
20 : * @tparam T data type
21 : * @param val value to clip
22 : * @param lower lower bound
23 : * @param upper upper bound
24 : * @return T cliped data
25 : */
26 : template <typename T> T clip(const T &val, const T &lower, const T &upper) {
27 5328 : return std::max(lower, std::min(val, upper));
28 : }
29 :
30 12 : void Quantizer::calculateMinMaxValue(Tdatatype qtype) {
31 : unsigned int N;
32 :
33 12 : if (qtype == Tdatatype::QINT16 || qtype == Tdatatype::UINT16) {
34 : N = 16;
35 : } else if (qtype == Tdatatype::QINT8 || qtype == Tdatatype::UINT8) {
36 : N = 8;
37 : } else if (qtype == Tdatatype::QINT4 || qtype == Tdatatype::UINT4) {
38 : N = 4;
39 : } else {
40 1 : throw std::invalid_argument("[Quantizer] Unsupported data type error.");
41 : }
42 :
43 : // define minimum and maximum valude representable by the type
44 22 : quant_max = (qtype == Tdatatype::UINT16 || qtype == Tdatatype::UINT8 ||
45 : qtype == Tdatatype::UINT4)
46 11 : ? static_cast<long>(std::pow(2, N) - 1)
47 9 : : static_cast<long>(std::pow(2, N - 1) - 1);
48 11 : quant_min = (qtype == Tdatatype::UINT16 || qtype == Tdatatype::UINT8 ||
49 : qtype == Tdatatype::UINT4)
50 11 : ? 0
51 9 : : static_cast<long>(-std::pow(2, N - 1));
52 11 : }
53 :
54 : /**
55 : * @brief PerTensorAffineQuantizer class
56 : */
57 0 : std::unique_ptr<Quantizer> PerTensorAffineQuantizer::create() {
58 0 : return std::make_unique<PerTensorAffineQuantizer>();
59 : }
60 :
61 4 : void PerTensorAffineQuantizer::calculateQParams(const Tensor &input,
62 : Tdatatype qtype) {
63 4 : float max_val = input.max_abs();
64 4 : scale = max_val / ((quant_max - quant_min) / 2.0f);
65 4 : scale = std::max(scale, std::numeric_limits<float>::epsilon());
66 :
67 4 : if (qtype == Tdatatype::UINT4) {
68 0 : zero_point =
69 0 : (unsigned int)(std::round(scale * input.minValue()) + std::pow(2, 3));
70 4 : } else if (qtype == Tdatatype::UINT8) {
71 0 : zero_point =
72 0 : (unsigned int)(std::round(scale * input.minValue()) + std::pow(2, 7));
73 4 : } else if (qtype == Tdatatype::UINT16) {
74 0 : zero_point =
75 0 : (unsigned int)(std::round(scale * input.minValue()) + std::pow(2, 15));
76 : } else {
77 4 : zero_point = 0;
78 : }
79 4 : }
80 :
81 5 : Tensor PerTensorAffineQuantizer::quantize(const Tensor &input,
82 : Tdatatype qtype) {
83 : // 1. Calculate quantization parameters
84 5 : calculateMinMaxValue(qtype);
85 4 : calculateQParams(input, qtype);
86 :
87 : // 2. Create output tensor with same dimension but different data type
88 4 : TensorDim dim = input.getDim();
89 : dim.setDataType(qtype);
90 4 : Tensor output(dim);
91 :
92 : // 3. perform quantization
93 4 : quantize(input, output, &scale, &zero_point);
94 :
95 3 : return output;
96 1 : }
97 :
98 11 : Tensor &PerTensorAffineQuantizer::quantize(const Tensor &input, Tensor &output,
99 : float *scales,
100 : unsigned int *zero_points) {
101 : // Currently only full precision floating point is supported. FP16 is NYI
102 12 : NNTR_THROW_IF(input.getDataType() != Tdatatype::FP32, std::invalid_argument)
103 : << "[Quantizer::quantize] Tensor data type is not floating point.";
104 :
105 : // Check if output tensor is valid
106 10 : NNTR_THROW_IF(output.empty(), std::invalid_argument)
107 : << "[Quantizer::quantize] Cannot quantize to an empty tensor.";
108 :
109 10 : NNTR_THROW_IF(output.getDataType() == Tdatatype::FP32, std::invalid_argument)
110 : << "[Quantizer::quantize] Cannot quantize to full precision floating "
111 : "point.";
112 :
113 10 : NNTR_THROW_IF(scales == nullptr || std::fpclassify(*scales) == FP_ZERO,
114 : std::invalid_argument)
115 : << "[Quantizer::quantize] Output scale factor is invalid.";
116 :
117 11 : NNTR_THROW_IF(input.size() != output.size(), std::invalid_argument)
118 : << "[Quantizer::quantize] Tensor size does not match.";
119 :
120 18 : if (output.getDataType() == Tdatatype::UINT4 ||
121 15 : output.getDataType() == Tdatatype::UINT8 ||
122 6 : output.getDataType() == Tdatatype::UINT16) {
123 6 : NNTR_THROW_IF(zero_points == nullptr, std::invalid_argument)
124 : << "[Quantizer::quantize] Output zero point is invalid.";
125 : }
126 :
127 7 : calculateMinMaxValue(output.getDataType());
128 :
129 : long int val;
130 :
131 : /// @todo this is a naive impl. need optimization
132 16 : for (unsigned int b = 0; b < output.batch(); ++b) {
133 24 : for (unsigned int c = 0; c < output.channel(); ++c) {
134 259 : for (unsigned int h = 0; h < output.height(); ++h) {
135 5572 : for (unsigned int w = 0; w < output.width(); ++w) {
136 5328 : val = std::lround(input.getValue(b, c, h, w) / *scales);
137 :
138 10656 : if (output.getDataType() == Tdatatype::UINT4 ||
139 10624 : output.getDataType() == Tdatatype::UINT8 ||
140 5296 : output.getDataType() == Tdatatype::UINT16) {
141 32 : val += *zero_points;
142 : }
143 :
144 5328 : output.setValue(b, c, h, w,
145 10656 : clip<float>(val, static_cast<float>(quant_min),
146 10656 : static_cast<float>(quant_max)));
147 : }
148 : }
149 : }
150 : }
151 7 : *output.getScale<float>() = *scales;
152 :
153 14 : if (output.getDataType() == Tdatatype::UINT4 ||
154 12 : output.getDataType() == Tdatatype::UINT8 ||
155 5 : output.getDataType() == Tdatatype::UINT16) {
156 2 : *output.getZeroPoint() = *zero_points;
157 : }
158 :
159 7 : return output;
160 : }
161 :
162 7 : Tensor PerTensorAffineQuantizer::dequantize(const Tensor &input,
163 : Tdatatype dtype) {
164 7 : Tensor output = input.clone(dtype);
165 14 : if (output.getDataType() == Tdatatype::UINT4 ||
166 12 : input.getDataType() == Tdatatype::UINT8 ||
167 5 : input.getDataType() == Tdatatype::UINT16) {
168 2 : output.subtract_i(static_cast<float>(*input.getZeroPoint()));
169 : }
170 :
171 6 : output.multiply_i(*input.getScale<float>());
172 :
173 6 : return output;
174 1 : }
175 :
176 0 : QScheme PerTensorAffineQuantizer::qscheme() const {
177 0 : return QScheme::PER_TENSOR_AFFINE;
178 : }
179 :
180 : /**
181 : * @brief PerChannelAffineQuantizer class
182 : */
183 0 : std::unique_ptr<Quantizer> PerChannelAffineQuantizer::create() {
184 0 : return std::make_unique<PerChannelAffineQuantizer>();
185 : }
186 :
187 0 : Tensor PerChannelAffineQuantizer::quantize(const Tensor &input,
188 : Tdatatype qtype) {
189 : /// @todo NYI
190 0 : return input;
191 : }
192 :
193 0 : Tensor &PerChannelAffineQuantizer::quantize(const Tensor &input, Tensor &output,
194 : float *scales,
195 : unsigned int *zero_points) {
196 : /// @todo NYI
197 0 : return output;
198 : }
199 :
200 0 : Tensor PerChannelAffineQuantizer::dequantize(const Tensor &input,
201 : Tdatatype dtype) {
202 : /// @todo NYI
203 0 : return input;
204 : }
205 :
206 0 : QScheme PerChannelAffineQuantizer::qscheme() const {
207 0 : return QScheme::PER_CHANNEL_AFFINE;
208 : }
209 :
210 : /**
211 : * @brief BinaryCodeBasedQuantizer class
212 : */
213 0 : std::unique_ptr<Quantizer> BinaryCodeBasedQuantizer::create() {
214 0 : return std::make_unique<BinaryCodeBasedQuantizer>();
215 : }
216 :
217 0 : Tensor BinaryCodeBasedQuantizer::quantize(const Tensor &input,
218 : Tdatatype qtype) {
219 : /// @todo NYI
220 0 : return input;
221 : }
222 :
223 0 : Tensor &BinaryCodeBasedQuantizer::quantize(const Tensor &input, Tensor &output,
224 : float *scales,
225 : unsigned int *zero_points) {
226 : /// @todo NYI
227 0 : return output;
228 : }
229 :
230 0 : Tensor BinaryCodeBasedQuantizer::dequantize(const Tensor &input,
231 : Tdatatype dtype) {
232 : /// @todo NYI
233 0 : return input;
234 : }
235 :
236 0 : QScheme BinaryCodeBasedQuantizer::qscheme() const {
237 0 : return QScheme::BINARY_CODE_BASED;
238 : }
239 :
240 : } // namespace nntrainer
|