Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alignment of default settings from config file for Keras (Adaround/Quantsim) #1707

Merged
merged 8 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Docs/keras_code_examples/adaround.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def apply_adaround_example():

# Returns session with adarounded weights and their corresponding encodings
adarounded_model = Adaround.apply_adaround(model, params, path='./', filename_prefix='dummy',
default_param_bw=param_bw, default_quant_scheme=quant_scheme,
default_is_symmetric=False)
default_param_bw=param_bw, default_quant_scheme=quant_scheme)

# Create QuantSim using adarounded_session
sim = QuantizationSimModel(adarounded_model, quant_scheme, default_output_bw=output_bw, default_param_bw=param_bw)
Expand Down
3 changes: 1 addition & 2 deletions Examples/tensorflow/quantization/keras/adaround.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,7 @@
"\n",
"os.makedirs(\"./output/\", exist_ok=True)\n",
"ada_model = Adaround.apply_adaround(model, params, path=\"output\", filename_prefix=\"adaround\",\n",
" default_param_bw=8, default_quant_scheme=QuantScheme.post_training_tf,\n",
" default_is_symmetric=False)"
" default_param_bw=8, default_quant_scheme=QuantScheme.post_training_tf)"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@
},
{
"cell_type": "markdown",
"source": [
"For this example notebook, we are going to load a pretrained ResNet50 model from Keras. Similarly, you can load any pretrained Keras model instead."
],
"metadata": {
"collapsed": false
}
},
"source": [
"For this example notebook, we are going to load a pretrained ResNet50 model from Keras. Similarly, you can load any pretrained Keras model instead."
]
},
{
"cell_type": "code",
Expand Down Expand Up @@ -349,7 +349,6 @@
"os.makedirs(\"./output/\", exist_ok=True)\n",
"ada_model = Adaround.apply_adaround(model, params, path=\"output\", filename_prefix=\"adaround\",\n",
" default_param_bw=8, default_quant_scheme=QuantScheme.post_training_tf,\n",
" default_is_symmetric=False,\n",
" config_file=\"Examples/tensorflow/utils/keras/pcq_quantsim_config\") # NOTE: The same config file used in QuantSim is used here as well. Again, telling Adaround to enable PCQ.\n"
]
},
Expand Down
3 changes: 2 additions & 1 deletion Examples/tensorflow/utils/keras/pcq_quantsim_config
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{"defaults": {
"ops": {},
"params": {
"is_quantized": "True"
"is_quantized": "True",
"is_symmetric": "True"
},
"strict_symmetric": "True",
"unsigned_symmetric": "False",
Expand Down
8 changes: 3 additions & 5 deletions NightlyTests/tensorflow/test_adaround_keras.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@
# @@-COPYRIGHT-END-@@
# =============================================================================
""" Keras AdaRound Nightly Tests """

import pytest
pytestmark = pytest.mark.skip("Disable tests that requires eager execution")
import json
import logging
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import pytest
pytestmark = pytest.mark.skip("Disable tests that requires eager execution")
import numpy as np
import tensorflow as tf
from tensorflow.keras.applications.mobilenet import MobileNet
Expand Down Expand Up @@ -120,8 +119,7 @@ def dummy_forward_pass(model: tf.keras.Model, _):
quant_scheme = QuantScheme.post_training_tf_enhanced

adarounded_model = Adaround.apply_adaround(model, params, path='./', filename_prefix='dummy',
default_param_bw=param_bw, default_quant_scheme=quant_scheme,
default_is_symmetric=False)
default_param_bw=param_bw, default_quant_scheme=quant_scheme)

# Read exported param encodings JSON file
with open('./dummy.encodings') as json_file:
Expand Down
11 changes: 8 additions & 3 deletions TrainingExtensions/common/src/python/aimet_common/quantsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,22 @@ def gate_min_max(min_val: float, max_val: float) -> Tuple[float, float]:
return gated_min, gated_max


def calculate_delta_offset(min_val: Union[float, np.ndarray], max_val: Union[float, np.ndarray], bitwidth: int) -> \
Union[Tuple[float, float], Tuple[List, List]]:
def calculate_delta_offset(min_val: Union[float, np.ndarray], max_val: Union[float, np.ndarray], bitwidth: int,
use_symmetric_encodings: bool, use_strict_symmetric: bool) \
-> Union[Tuple[float, float], Tuple[List, List]]:
"""
calculates delta and offset given min and max.
:param min_val: min encoding value
:param max_val: max encoding value
:param bitwidth: bitwidth used for quantization
:return: delta and offset values computed
"""
num_steps = 2 ** bitwidth - 1
if use_symmetric_encodings and use_strict_symmetric:
num_steps -= 1

min_val, max_val = gate_min_max(min_val, max_val)
delta = (max_val - min_val) / (2 ** bitwidth - 1)
delta = (max_val - min_val) / num_steps

if isinstance(delta, np.ndarray):
offset = np.around(min_val/delta)
Expand Down
3 changes: 2 additions & 1 deletion TrainingExtensions/common/test/python/test_quantsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def test_offset_delta_compute(self):

expected_delta = (max - min) / (2 ** bitwidth - 1)
expected_offset = np.round(min / expected_delta)
delta, offset = calculate_delta_offset(min, max, bitwidth)
delta, offset = calculate_delta_offset(min, max, bitwidth,
use_strict_symmetric=False, use_symmetric_encodings=False)
self.assertTrue(expected_delta == delta)
self.assertTrue(expected_offset == offset)
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ def _apply_adaround_helper( # pylint: disable=too-many-locals
all_inp_data, all_out_data = act_sampler.sample_activation(op, hard_rounded_op, session,
session_hard_rounded_weight, starting_op_names,
params.num_batches)
is_symmetric = cls._get_is_symmetric_flag_for_op_param(configs, op.type, param_name="weight")
is_symmetric = cls.get_is_symmetric_flag_for_op_param(configs, op.type,
param_name="weight",
framework_to_onnx_type_dict=tf_op_type_to_onnx_type_dict)


# Find next following activation function
Expand Down Expand Up @@ -259,14 +261,13 @@ def get_config_dict_keys(config_file: str) -> Tuple[ConfigDictType, bool, bool,
:return: Config dictionary, strict symmetric flag, unsigned symmetric flag, enable per channel flag.
"""
configs = JsonConfigImporter.import_json_config_file(config_file)
# Strict_symmetric and unsigned_symmetric flags have default value False and True respectively
strict_symmetric = configs[ConfigDictKeys.DEFAULTS].get(ConfigDictKeys.STRICT_SYMMETRIC, False)
unisgned_symmetric = configs[ConfigDictKeys.DEFAULTS].get(ConfigDictKeys.UNSIGNED_SYMMETRIC, True)
unsigned_symmetric = configs[ConfigDictKeys.DEFAULTS].get(ConfigDictKeys.UNSIGNED_SYMMETRIC, False)

# Read per-channel quantization field. Default = False
per_channel_enabled = configs[ConfigDictKeys.DEFAULTS].get(ConfigDictKeys.PER_CHANNEL_QUANTIZATION, False)

return configs, strict_symmetric, unisgned_symmetric, per_channel_enabled
return configs, strict_symmetric, unsigned_symmetric, per_channel_enabled

@staticmethod
def _get_ordered_list_of_ops(graph: tf.Graph, input_op_names: List[str], output_op_names: List[str]) \
Expand Down Expand Up @@ -355,7 +356,8 @@ def _update_param_encodings_dict(encoding_dict: Dict, op: tf.Operation,
'is_symmetric': is_symmetric}]

@staticmethod
def _get_is_symmetric_flag_for_op_param(configs: ConfigDictType, tf_op_type: str, param_name: str):
def get_is_symmetric_flag_for_op_param(configs: ConfigDictType, tf_op_type: str, param_name: str,
framework_to_onnx_type_dict: dict) -> bool:
"""
NOTE: Checks config file in reverse order of specificity.

Expand All @@ -367,13 +369,14 @@ def _get_is_symmetric_flag_for_op_param(configs: ConfigDictType, tf_op_type: str
:param configs: Dictionary containing configs.
:param tf_op_type: TensorFlow operation type.
:param param_name: Parameter name.
:return: Is_symmetric flag for given op's param.
:param framework_to_onnx_type_dict: Dictionary mapping framework type to ONNX type.
:return: is_symmetric flag for given op's param.
"""
assert param_name in MAP_TF_PARAM_NAME_TO_QUANTSIM_NAME.keys(), "param name is invalid."

# third level of specificity which applies to specific op_type's parameters.
try:
onnx_type = tf_op_type_to_onnx_type_dict[tf_op_type]
onnx_type = framework_to_onnx_type_dict[tf_op_type]
return configs[ConfigDictKeys.OP_TYPE] \
[onnx_type] \
[ConfigDictKeys.PARAMS] \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
from aimet_tensorflow.keras.adaround.activation_sampler import ActivationSampler
from aimet_tensorflow.keras.adaround.adaround_wrapper import AdaroundWrapper
from aimet_tensorflow.keras.adaround.adaround_optimizer import AdaroundOptimizer
from aimet_tensorflow.keras.connectedgraph import ConnectedGraph
from aimet_tensorflow.keras.connectedgraph import ConnectedGraph, map_keras_types_to_onnx

_logger = AimetLogger.get_area_logger(AimetLogger.LogAreas.Quant)

Expand All @@ -69,7 +69,6 @@ class Adaround:
def apply_adaround(cls, model: tf.keras.Model, params: AdaroundParameters, path: str, filename_prefix: str,
default_param_bw: int = 4,
default_quant_scheme: QuantScheme = QuantScheme.post_training_tf_enhanced,
default_is_symmetric: bool = False,
config_file: str = None) -> tf.keras.Model:
"""
Returns model with optimized weight rounding of every op (Conv and Linear) and also saves the
Expand All @@ -83,14 +82,12 @@ def apply_adaround(cls, model: tf.keras.Model, params: AdaroundParameters, path:
:param default_param_bw: Default bitwidth (4-31) to use for quantizing layer parameters. Default 4
:param default_quant_scheme: Quantization scheme. Supported options are QuantScheme.post_training_tf or
QuantScheme.post_training_tf_enhanced. Default QuantScheme.post_training_tf_enhanced
:param default_is_symmetric: True if symmetric encodings is used, else asymmetric encodings.
Default False.
:param config_file: Configuration file for model quantizers
:return: Model with Adarounded weights
"""

# Get parameters from config file. To allow one central place for Adaround and Quantsim
_, strict_symmetric, unsigned_symmetric, per_channel_enabled = TfAdaround.get_config_dict_keys(config_file)
configs, strict_symmetric, unsigned_symmetric, per_channel_enabled = TfAdaround.get_config_dict_keys(config_file)

# Optimization Hyper parameters
opt_params = AdaroundHyperParameters(params.num_iterations, params.reg_param, params.beta_range,
Expand All @@ -110,7 +107,10 @@ def apply_adaround(cls, model: tf.keras.Model, params: AdaroundParameters, path:

progbar = Progbar(len(ordered_layer_indices))
for idx in ordered_layer_indices:
cls.adaround_layer(act_sampler, default_is_symmetric, strict_symmetric, unsigned_symmetric,
use_symmetric_encodings = TfAdaround.get_is_symmetric_flag_for_op_param(configs, model.layers[idx],
param_name='weight',
framework_to_onnx_type_dict=map_keras_types_to_onnx)
cls.adaround_layer(act_sampler, use_symmetric_encodings, strict_symmetric, unsigned_symmetric,
default_param_bw, default_quant_scheme, model, hard_rounded_model, soft_rounded_model,
idx, module_act_func_pair, opt_params, param_encodings, per_channel_enabled)
progbar.add(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class QuantizerSettings:
""" Class holding quantizer settings """

def __init__(self, bitwidth: int, round_mode: str, quant_scheme: Union[str, QuantScheme], is_symmetric: bool,
use_unsigned_symmetric: bool, use_strict_symmetric: bool):
use_unsigned_symmetric: bool, use_strict_symmetric: bool, enabled: bool = False):
self._bitwidth = bitwidth
self._round_mode = round_mode
if isinstance(quant_scheme, str):
Expand All @@ -72,6 +72,7 @@ def __init__(self, bitwidth: int, round_mode: str, quant_scheme: Union[str, Quan
self._is_symmetric = is_symmetric
self._use_unsigned_symmetric = use_unsigned_symmetric
self._use_strict_symmetric = use_strict_symmetric
self._enabled = enabled

@property
def quant_scheme(self):
Expand Down Expand Up @@ -123,6 +124,16 @@ def use_strict_symmetric(self, use_strict_symmetric: bool):
""" Use strict symmetric setter """
self._use_strict_symmetric = use_strict_symmetric

@property
def enabled(self):
""" Enabled getter """
return self._enabled

@enabled.setter
def enabled(self, enabled: bool):
""" Enabled setter """
self._enabled = enabled


class QcQuantizeWrapper(tf.keras.layers.Layer):
""" Wrapper for simulating quantization noise """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,9 @@ def encoding(self) -> Optional[libpymo.TfEncoding]:
# pylint: disable = protected-access
encodings.min = tf.keras.backend.get_value(self._encoding_min)
encodings.max = tf.keras.backend.get_value(self._encoding_max)
encodings.delta, encodings.offset = calculate_delta_offset(encodings.min, encodings.max, self.bitwidth)
encodings.delta, encodings.offset = calculate_delta_offset(encodings.min, encodings.max,
self.bitwidth, self.is_symmetric,
self.use_strict_symmetric)
encodings.bw = self.bitwidth
return encodings
return None
Expand Down Expand Up @@ -562,7 +564,8 @@ def encoding(self) -> Optional[List[libpymo.TfEncoding]]:
encodings.min = tf.keras.backend.get_value(self._encoding_min[i])
encodings.max = tf.keras.backend.get_value(self._encoding_max[i])
encodings.delta, encodings.offset = calculate_delta_offset(encodings.min, encodings.max,
self.bitwidth)
self.bitwidth, self.is_symmetric,
self.use_strict_symmetric[i])
encodings.bw = self.bitwidth
all_encodings[i] = encodings
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def export(self, path, filename_prefix, custom_objects=None):
try:
convert_h5_model_to_pb_model(f'{model_path}.h5', custom_objects=custom_objects)
except ValueError:
_logger.error("Could not convert h5 to frozen pb."
_logger.error("Could not convert h5 to frozen pb. "
"Please call export() again with custom_objects defined.")
raise
encodings_dict = self.get_encodings_dict()
Expand All @@ -396,7 +396,7 @@ def _compute_and_set_parameter_encodings(self, ops_with_invalid_encodings: List)
channel_slice = weight_tensor.reshape(*last_two_axes_combined_shape)
channel_slice = channel_slice.take(index, channel_slice.ndim - 1)
elif isinstance(quantizer_wrapper.original_layer, tf.keras.layers.Conv2DTranspose):
if len(weight_tensor) == 4:
if weight_tensor.ndim == 4:
channel_slice = weight_tensor.take(index, weight_tensor.ndim - 2)
else:
# For bias in Transpose layers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,16 @@ def _initialize_param_quantizers(layer: layers.Layer, param_config_dict: TreeLik
if weight.dtype in QUANT_ALLOWED_DTYPES:
weight_name = weight.name.split(":")[0]
param_type = "bias" if "bias" in weight_name else "weight"

# quant_settings is the global setting of the config file here. For params, if one of the settings is not
# specified, we will use the global setting (which may be specificed or defaulted).
if param_type in param_config_dict:
is_symmetric = param_config_dict[param_type][ConfigDictKeys.IS_SYMMETRIC].get(
SETTING, False)
SETTING, quant_settings.is_symmetric)
enabled = param_config_dict[param_type][ConfigDictKeys.IS_QUANTIZED].get(
SETTING, False)
SETTING, quant_settings.enabled)
else:
is_symmetric = param_config_dict[ConfigDictKeys.IS_SYMMETRIC].get(
SETTING, False)
enabled = param_config_dict[ConfigDictKeys.IS_QUANTIZED].get(
SETTING, False)
is_symmetric = quant_settings.is_symmetric
enabled = quant_settings.enabled

if per_channel_quantization_enabled and isinstance(layer,
keras_common_utils.per_channel_quantizeable_layers):
Expand All @@ -250,7 +249,6 @@ def _initialize_param_quantizers(layer: layers.Layer, param_config_dict: TreeLik
num_output_channels,
enabled))
else:

param_quantizers.append(
ParamPerTensorQuantizer(layer,
weight_name,
Expand Down Expand Up @@ -511,7 +509,9 @@ def _initialize_quantizers_by_layer(self, quant_scheme: Union[QuantScheme, str],
SETTING, False)
param_quant_settings = QuantizerSettings(default_param_bw, rounding_mode,
quant_scheme, param_is_symmetric,
use_unsigned_symmetric, use_strict_symmetric)
use_unsigned_symmetric, use_strict_symmetric,
enabled=param_config_dict[ConfigDictKeys.IS_QUANTIZED].get(
SETTING, False))

# Initialize Param Quantizers
self._layer_to_quantizers_dict[layer][PARAM_QUANTIZERS] = \
Expand Down
Loading