Source code for qkeras.qtools.quantized_operators.quantizer_impl

# Copyright 2019 Google LLC
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""atomic quantizer implementation."""


import abc
import math

import keras.ops.numpy as knp
import numpy as np
from keras import KerasTensor

from qkeras import quantizers

FLOATINGPOINT_BITS = 32


[docs] def get_np_value(val): if isinstance(val, np.ndarray): val = val.numpy() if isinstance(val, KerasTensor) and len(val) == 1: return val[0] else: return val else: return val
[docs] def get_exp(quantizer): """get max/min exp value for relu_po2 or quantized_po2.""" if quantizer.is_signed: non_sign_bits = quantizer.bits - 1 else: non_sign_bits = quantizer.bits min_exp = -(2 ** (non_sign_bits - 1)) max_exp_orig = 2 ** (non_sign_bits - 1) - 1 max_exp = max_exp_orig # max_value caps how many int_bits actually allowed if quantizer.max_val_po2 != -1: if quantizer.max_val_po2 <= 0: max_exp = 0 else: max_exp = math.ceil(knp.log2(quantizer.max_val_po2)) max_exp = min(max_exp, max_exp_orig) # if max_exp<0. no need to expand int_bits max_exp = max(0, max_exp) return (-min_exp, max_exp)
[docs] class IQuantizer(abc.ABC): """abstract class for quantizer.""" def __init__(self): self.mode = -1 self.bits = -1 self.int_bits = -1 self.is_signed = 0 self.is_floating_point = False self.max_val_po2 = -1 self.is_po2 = 0 self.name = None self.op_type = "quantizer"
[docs] class QuantizedBits(IQuantizer): """quantized bits. Attributes: mode: index of the current quantizer in MultiplierFactory.multiplier_impl_table bits: total bits int_bits: integer bits is_signed: if a signed number name: quantizer name """ def __init__(self): super().__init__() self.mode = 0 self.is_signed = 1 self.name = "quantized_bits"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.quantized_bits): self.mode = 0 self.bits = quantizer.bits self.int_bits = get_np_value(quantizer.integer) self.is_signed = quantizer.keep_negative
[docs] def convert_to_qkeras_quantizer( self, symmetric=1, alpha=None, use_stochastic_rounding=False, scale_axis=None, qnoise_factor=1.0, elements_per_scale=None, min_po2_exponent=None, max_po2_exponent=None, ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.quantized_bits( bits=self.bits, integer=self.int_bits, keep_negative=self.is_signed, symmetric=symmetric, alpha=alpha, use_stochastic_rounding=use_stochastic_rounding, scale_axis=scale_axis, qnoise_factor=qnoise_factor, elements_per_scale=elements_per_scale, min_po2_exponent=min_po2_exponent, max_po2_exponent=max_po2_exponent, )
[docs] class QuantizedTanh(QuantizedBits): """same as quantized bits.""" def __init__(self): super().__init__() self.name = "quantized_tanh"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.quantized_tanh): self.mode = 0 self.bits = quantizer.bits self.is_signed = 1
[docs] def convert_to_qkeras_quantizer( self, symmetric=False, use_stochastic_rounding=False ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.quantized_tanh( bits=self.bits, use_stochastic_rounding=use_stochastic_rounding, symmetric=symmetric, )
[docs] class QuantizedUlaw(QuantizedBits): """quantized ulaw type.""" # same as quantized bits def __init__(self): super().__init__() self.name = "quantized_ulaw"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.quantized_ulaw): self.mode = 0 self.bits = quantizer.bits self.int_bits = get_np_value(quantizer.integer) self.is_signed = 1
[docs] def convert_to_qkeras_quantizer(self, symmetric=0, u=255.0): """convert qtools quantizer to qkeras quantizer.""" return quantizers.quantized_ulaw( bits=self.bits, integer=self.int_bits, symmetric=symmetric, u=u )
[docs] class Binary(IQuantizer): """binary quantizer.""" def __init__(self, use_01=False): super().__init__() if use_01: self.mode = 4 self.is_signed = 0 else: self.mode = 3 self.is_signed = 1 self.bits = 1 self.int_bits = 1 self.use_01 = use_01 self.name = "binary"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.binary): if quantizer.use_01: self.mode = 4 self.is_signed = 0 else: self.mode = 3 self.is_signed = 1 self.use_01 = quantizer.use_01
[docs] def convert_to_qkeras_quantizer(self, alpha=None, use_stochastic_rounding=False): """convert qtools quantizer to qkeras quantizer.""" return quantizers.binary( use_01=self.use_01, alpha=alpha, use_stochastic_rounding=use_stochastic_rounding, )
[docs] class StochasticBinary(Binary): """stochastic binary quantizer.""" # same as binary(-1, 1) def __init__(self): super().__init__(use_01=False) self.name = "stochastic_binary"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.stochastic_binary): """convert qkeras quantizer to qtools quantizer.""" pass
[docs] def convert_to_qkeras_quantizer( self, alpha=None, temperature=6.0, use_real_sigmoid=True ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.stochastic_binary( alpha=alpha, temperature=temperature, use_real_sigmoid=use_real_sigmoid )
[docs] class Bernoulli(Binary): """bernoulli quantizer. same as binary(0, 1).""" def __init__(self): super().__init__(use_01=True) self.name = "bernoulli"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.bernoulli): pass
[docs] def convert_to_qkeras_quantizer( self, alpha=None, temperature=6.0, use_real_sigmoid=True ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.bernoulli( alpha=alpha, temperature=temperature, use_real_sigmoid=use_real_sigmoid )
[docs] class QuantizedRelu(IQuantizer): """quantized relu quantizer.""" def __init__(self): super().__init__() self.is_signed = 0 self.name = "quantized_relu"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.quantized_relu): """convert from qkeras quantizer.""" bits = quantizer.bits int_bits = get_np_value(quantizer.integer) if bits == 1 and int_bits == 1: mode = 4 else: mode = 0 self.mode = mode self.bits = bits self.int_bits = int_bits if hasattr(quantizer, "negative_slope") and quantizer.negative_slope != 0: self.is_signed = 1
[docs] def convert_to_qkeras_quantizer( self, use_sigmoid=0, negative_slope=0.0, use_stochastic_rounding=False, relu_upper_bound=None, is_quantized_clip=True, qnoise_factor=1.0, ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.quantized_relu( bits=self.bits, integer=self.int_bits, use_sigmoid=use_sigmoid, negative_slope=negative_slope, use_stochastic_rounding=use_stochastic_rounding, relu_upper_bound=relu_upper_bound, is_quantized_clip=is_quantized_clip, qnoise_factor=qnoise_factor, )
[docs] class Ternary(IQuantizer): """ternary(0, 1, -1).""" def __init__(self): super().__init__() self.mode = 2 self.bits = 2 self.int_bits = 2 self.is_signed = 1 self.name = "ternary"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.ternary): pass
[docs] def convert_to_qkeras_quantizer( self, alpha=None, threshold=None, use_stochastic_rounding=False, number_of_unrolls=5, ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.ternary( alpha=alpha, threshold=threshold, use_stochastic_rounding=use_stochastic_rounding, number_of_unrolls=number_of_unrolls, )
[docs] class StochasticTernary(Ternary): """stochastic ternary.""" def __init__(self): super().__init__() self.name = "stochastic_ternary" # same as ternary
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.stochastic_ternary): pass
[docs] def convert_to_qkeras_quantizer( self, alpha=None, threshold=None, temperature=8.0, use_real_sigmoid=True, number_of_unrolls=5, ): """convert qtools quantizer to qkeras quantizer.""" return quantizers.stochastic_ternary( alpha=alpha, threshold=threshold, temperature=temperature, use_real_sigmoid=use_real_sigmoid, number_of_unrolls=number_of_unrolls, )
[docs] class FloatingPoint(IQuantizer): """float32.""" def __init__(self, bits): super().__init__() self.mode = 5 self.bits = bits self.int_bits = -1 self.is_signed = 1 self.is_floating_point = True self.name = "floating_point"
[docs] def convert_qkeras_quantizer(self, bits): pass
[docs] def convert_to_qkeras_quantizer(self, bits): pass
[docs] class PowerOfTwo(IQuantizer): """po2.""" def __init__(self, is_signed=True): super().__init__() self.mode = 1 self.is_po2 = 1 self.is_signed = is_signed self.inference_value_counts = -1 if is_signed: self.name = "quantized_po2" else: self.name = "quantized_relu_po2"
[docs] def convert_qkeras_quantizer(self, quantizer): """convert qkeras quantizer to qtools quantizer.""" assert "po2" in quantizer.__class__.__name__ if quantizer.__class__.__name__ == "quantized_po2": self.is_signed = 1 self.name = "quantized_po2" elif quantizer.__class__.__name__ == "quantized_relu_po2": super().__init__() self.is_signed = 0 self.name = "quantized_relu_po2" bits = quantizer.bits max_val_po2 = quantizer.max_value if not max_val_po2: self.max_val_po2 = -1 else: self.max_val_po2 = max_val_po2 self.bits = bits self.int_bits = bits
[docs] def convert_to_qkeras_quantizer( self, negative_slope=0, use_stochastic_rounding=False, quadratic_approximation=False, ): """convert qtools quantizer to qkeras quantizer.""" if self.is_signed: # quantized_po2 return quantizers.quantized_po2( bits=self.bits, max_value=self.max_val_po2 if self.max_val_po2 >= 0 else None, use_stochastic_rounding=use_stochastic_rounding, quadratic_approximation=quadratic_approximation, ) else: # quantized_relu_po2 return quantizers.quantized_relu_po2( bits=self.bits, max_value=self.max_val_po2 if self.max_val_po2 >= 0 else None, negative_slope=negative_slope, use_stochastic_rounding=use_stochastic_rounding, quadratic_approximation=quadratic_approximation, )
[docs] def get_min_max_exp(self): return get_exp(self)
[docs] def quantizer_bits_calculator(self, val): """calculate how many bits needed.""" # calculate how many bits are required to represent a po2 value. # val can be +/- values, can be integer or franctional number. # needs to be dealt seperately. sign_bit = val < 0 # get rid of sign val = abs(val) if val == 0: # val of 0 is special case; qkeras uses mininmum # number to represent 0 non_sign_bits = self.bits - sign_bit else: exp_value = knp.log2(val) # exp_value should be integer if abs(knp.round(exp_value) - exp_value) > 0: raise ValueError(f"ERROR: {val} is not a po2 value!") exp_value = int(exp_value) # for n bits, the range of values it can represent is: # min_val = -2 ** (n - 1) # max_val = 2 ** (n - 1) - 1 if exp_value == 0: non_sign_bits = 1 elif exp_value > 0: # e.g., 16 needs 5 bits + 1 exp sign bit, # 15 needs 4 bits + 1 exp sign bit non_sign_bits = math.floor(knp.log2(exp_value)) + 1 + 1 else: # e.g., -16 needs 4 bits + 1 exp sign bit non_sign_bits = math.ceil(knp.log2(abs(exp_value))) + 1 return (sign_bit, non_sign_bits)
[docs] def update_quantizer(self, val, reset=False): """update quantizer bits according to the input value. Args: val: input value reset: True->disregard current quantizer bits and reset it according to the given value; False-> update the quantizer bits with given value. quantizer.bits = min(existing_bits, bits required by val) Returns: Update existing po2 quantizer bits by val. quantizer.bits = min(existing_bits, bits required by val) """ (sign_bit, non_sign_bits) = self.quantizer_bits_calculator(val) if reset: self.bits = sign_bit + non_sign_bits else: # avoid input value exceeding quantizer limit self.bits = min(self.bits, sign_bit + non_sign_bits) self.int_bits = self.bits self.max_val_po2 = min(val, self.max_val_po2) self.is_signed = sign_bit if sign_bit: self.name = "quantized_po2" else: self.name = "quantized_relu_po2"
[docs] def update_inference_values(self, weights): """find how many different values in weights in the po2 quantizer.""" inference_value_counts = len(set(weights.flatten())) self.inference_value_counts = inference_value_counts
[docs] class ReluPowerOfTwo(PowerOfTwo): """relu po2.""" def __init__(self): super().__init__() self.mode = 1 self.is_po2 = 1 self.is_signed = 0 self.name = "quantized_relu_po2"
[docs] def convert_qkeras_quantizer(self, quantizer: quantizers.quantized_relu_po2): self.bits = quantizer.bits self.int_bits = quantizer.bits if not quantizer.max_value: self.max_val_po2 = -1 else: self.max_val_po2 = quantizer.max_value
[docs] def convert_to_qkeras_quantizer( self, negative_slope=0, use_stochastic_rounding=False, quadratic_approximation=False, ): """convert qtools quantizer to qkeras quantizer.""" # quantized_relu_po2 return quantizers.quantized_relu_po2( bits=self.bits, max_value=self.max_val_po2 if self.max_val_po2 >= 0 else None, negative_slope=negative_slope, use_stochastic_rounding=use_stochastic_rounding, quadratic_approximation=quadratic_approximation, )