LCOV - code coverage report
Current view: top level - nntrainer/tensor - quantizer.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 68.8 % 96 66
Test Date: 2025-12-14 20:38:17 Functions: 29.4 % 17 5

            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
        

Generated by: LCOV version 2.0-1