Cross-Layer Equalization (CLE) with QuantSim
This notebook showcases a working code example of how to use AIMET to apply Cross-Layer Equalization (CLE) and use QuantSim. CLE is post-training quantization techniques that aims to improve quantized accuracy of a given model. CLE does not need any data samples. This technique help recover quantized accuracy when the model quantization is sensitive to parameter quantization as opposed to activation quantization.
To learn more about this techniques, please refer to the “Data-Free Quantization Through Weight Equalization and Bias Correction” paper from ICCV 2019 - https://arxiv.org/abs/1906.04721
Cross-Layer Equalization AIMET performs the following steps when running CLE: 1. Batch Norm Folding: Folds BN layers into Conv layers immediate before or after the Conv layers. 2. Cross-Layer Scaling: Given a set of consecutive Conv layers, equalizes the range of tensor values per-channel by scaling up/down per-channel weight tensor values of a layer and corresponding scaling down/up per-channel weight tensor values of the subsequent layer. 3. High Bias Folding: Cross-layer scaling may result in high bias parameter values for some layers. This technique folds some of the bias of a layer into the subsequent layer’s parameters.
Overall flow
This notebook covers the following 1. Instantiate the example evaluation and training pipeline 2. Load the FP32 model and evaluate the model to find the baseline FP32 accuracy 3. Create a quantization simulation model (with fake quantization ops inserted) and evaluate this simuation model to get a quantized accuracy score 4. Apply CLE, and evaluate the simulation model to get a post-finetuned quantized accuracy score 5. Exporting the simulation models encodings and how to take them to SNPE/QNN
What this notebook is not
This notebook is not designed to show state-of-the-art results. For example, it uses a relatively quantization-friendly model like Resnet50. Also, some optimization parameters are deliberately chosen to have the notebook execute more quickly.
Dataset
This notebook relies on the ImageNet dataset for the task of image classification. If you already have a version of the dataset readily available, please use that. Else, please download the dataset from appropriate location (e.g. https://image-net.org/challenges/LSVRC/2012/index.php#).
Note: To speed up the execution of this notebook, you may use a reduced subset of the ImageNet dataset. E.g. the entire ILSVRC2012 dataset has 1000 classes, 1000 training samples per class and 50 validation samples per class. But for the purpose of running this notebook, you could perhaps reduce the dataset to say 2 samples per class. This exercise is left upto the reader and is not necessary.
Edit the cell below and specify the directory where the downloaded ImageNet dataset is saved.
[ ]:
DATASET_DIR = "/path/to/dataset/dir/" # Please replace this with a real directory
1. Example evaluation and training pipeline
The following is an example training and validation loop for this image classification task.
Does AIMET have any limitations on how the training, validation pipeline is written? Not really. We will see later that AIMET will modify the user”s model to create a QuantizationSim model which is still a Keras model. This QuantizationSim model can be used in place of the original model when doing inference or training.
Does AIMET put any limitation on the interface of the evaluate() or train() methods? Not really. You should be able to use your existing evaluate and train routines as-is.
[ ]:
import tensorflow as tf
from Examples.common import image_net_config
from Examples.tensorflow.utils.keras.image_net_dataset import ImageNetDataset
from Examples.tensorflow.utils.keras.image_net_evaluator import ImageNetEvaluator
class ImageNetDataPipeline:
"""
Provides APIs for model evaluation and finetuning using ImageNet Dataset.
"""
@staticmethod
def get_val_dataset() -> tf.data.Dataset:
"""
Instantiates a validation dataloader for ImageNet dataset and returns it
:return: A tensorflow dataset
"""
data_loader = ImageNetDataset(DATASET_DIR,
image_size=image_net_config.dataset['image_size'],
batch_size=image_net_config.evaluation['batch_size'])
return data_loader
@staticmethod
def evaluate(model, iterations=None) -> float:
"""
Given a Keras model, evaluates its Top-1 accuracy on the validation dataset
:param model: The Keras model to be evaluated.
:param iterations: The number of iterations to run. If None, all the data will be used
:return: The accuracy for the sample with the maximum accuracy.
"""
evaluator = ImageNetEvaluator(DATASET_DIR,
image_size=image_net_config.dataset["image_size"],
batch_size=image_net_config.evaluation["batch_size"])
return evaluator.evaluate(model=model, iterations=iterations)
2. Load the model and evaluate to get a baseline FP32 accuracy score
For this example notebook, we are going to load a pretrained ResNet50 model from Keras. Similarly, you can load any pretrained Keras model instead.
[ ]:
from tensorflow.keras.applications.resnet50 import ResNet50
model = ResNet50(include_top=True,
weights="imagenet",
input_tensor=None,
input_shape=None,
pooling=None,
classes=1000)
Let’s determine the FP32 (floating point 32-bit) accuracy of this model using the evaluate() routine
[ ]:
ImageNetDataPipeline.evaluate(model=model, iterations=10)
3. Create a quantization simulation model and determine quantized accuracy
Fold Batch Normalization layers
Before we determine the simulated quantized accuracy using QuantizationSimModel, we will fold the BatchNormalization (BN) layers in the model. These layers get folded into adjacent Convolutional layers. The BN layers that cannot be folded are left as they are.
Why do we need to this? On quantized runtimes (like TFLite, SnapDragon Neural Processing SDK, etc.), it is a common practice to fold the BN layers. Doing so, results in an inferences/sec speedup since unnecessary computation is avoided. Now from a floating point compute perspective, a BN-folded model is mathematically equivalent to a model with BN layers from an inference perspective, and produces the same accuracy. However, folding the BN layers can increase the range of the tensor values for the weight parameters of the adjacent layers. And this can have a negative impact on the quantized accuracy of the model (especially when using INT8 or lower precision). So, we want to simulate that on-target behavior by doing BN folding here.
The following code calls AIMET to fold the BN layers of a given model. NOTE: During folding, a new model is returned. Please use the returned model for the rest of the pipeline.
[ ]:
from aimet_tensorflow.keras.batch_norm_fold import fold_all_batch_norms
_, model = fold_all_batch_norms(model)
Create Quantization Sim Model
Now we use AIMET to create a QuantizationSimModel. This basically means that AIMET will wrap the Keras layers to mimic a layer as quantized. A few of the parameters are explained here - quant_scheme: We set this to “QuantScheme.post_training_tf” - Supported options are ‘tf_enhanced’ or ‘tf’ or using Quant Scheme Enum QuantScheme.post_training_tf or QuantScheme.post_training_tf_enhanced - default_output_bw: Setting this to 8, essentially means that we are asking AIMET to perform all activation quantizations in the model using integer 8-bit precision - default_param_bw: Setting this to 8, essentially means that we are asking AIMET to perform all parameter quantizations in the model using integer 8-bit precision - num_batches: The number of batches used to evaluate the model while calculating the quantization encodings.Number of batches to use for computing encodings. Only 5 batches are used here to speed up the process. In addition, the number of images in these 5 batches should be sufficient for compute encodings - rounding_mode: The rounding mode used for quantization. There are two possible choices here - ‘nearest’ or ‘stochastic’ We will use “nearest.”
There are other parameters that are set to default values in this example. Please check the AIMET API documentation of QuantizationSimModel to see reference documentation for all the parameters.
The next cell sets up the quantizer, and quantizes the model. Note that the quantizer uses the same evaluate function as the one defined in our data pipeline when computing the new weights.
[ ]:
from aimet_common.defs import QuantScheme
from aimet_tensorflow.keras.quantsim import QuantizationSimModel
sim = QuantizationSimModel(model=model,
quant_scheme=QuantScheme.post_training_tf,
rounding_mode="nearest",
default_output_bw=8,
default_param_bw=8)
Compute Encodings
Even though AIMET has wrapped the layers to act as being ‘quantized’ but the model is not ready to be used yet. Before we can use the sim model for inference or training, we need to find appropriate scale/offset quantization parameters for each ‘quantizer’ layer. For activation quantization layers, we need to pass unlabeled data samples through the model to collect range statistics which will then let AIMET calculate appropriate scale/offset quantization parameters. This process is sometimes referred to as calibration. AIMET simply refers to it as ‘computing encodings’.
So we create a routine to pass unlabeled data samples through the model. This should be fairly simple - use the existing train or validation data loader to extract some samples and pass them to the model. We don’t need to compute any loss metric etc. So we can just ignore the model output for this purpose. A few pointers regarding the data samples
In practice, we need a very small percentage of the overall data samples for computing encodings. For example, the training dataset for ImageNet has 1M samples. For computing encodings we only need 500 or 1000 samples. It may be beneficial if the samples used for computing encoding are well distributed. It’s not necessary that all classes need to be covered etc. since we are only looking at the range of values at every layer activation. However, we definitely want to avoid an extreme scenario like all ‘dark’ or ‘light’ samples are used - e.g. only using pictures captured at night might not give ideal results. The following shows an example of a routine that passes unlabeled samples through the model for computing encodings. This routine can be written in many different ways, this is just an example.
[ ]:
from tensorflow.keras.utils import Progbar
from tensorflow.keras.applications.resnet import preprocess_input
def pass_calibration_data(sim_model, samples):
tf_dataset = ImageNetDataPipeline.get_val_dataset()
dataset = tf_dataset.dataset
batch_size = tf_dataset.batch_size
progbar = Progbar(samples)
batch_cntr = 0
for inputs, _ in dataset:
sim_model(preprocess_input(inputs))
batch_cntr += 1
progbar_stat_update = \
batch_cntr * batch_size if (batch_cntr * batch_size) < samples else samples
progbar.update(progbar_stat_update)
if (batch_cntr * batch_size) > samples:
break
Now we call AIMET to use the above routine to pass data through the model and then subsequently compute the quantization encodings. Encodings here refer to scale/offset quantization parameters.
[ ]:
sim.compute_encodings(forward_pass_callback=pass_calibration_data,
forward_pass_callback_args=1000)
Now, we can determine the simulated quantized accuracy of the equalized model. We again create a simulation model like before and evaluate to determine simulated quantized accuracy.
[ ]:
ImageNetDataPipeline.evaluate(sim.model)
4 Cross Layer Equalization
The next cell performs cross-layer equalization on the model. As noted before, the function folds batch norms, applies cross-layer scaling, and then folds high biases.
Note: Interestingly, CLE needs BN statistics for its procedure. If a BN folded model is provided, CLE will run the CLS (cross-layer scaling) optimization step but will skip the HBA (high-bias absorption) step. To avoid this, we simply load the original model again before running CLE.
[ ]:
from aimet_tensorflow.keras import cross_layer_equalization as aimet_cle
cle_applied_model = aimet_cle.equalize_model(model)
Now, we can determine the simulated quantized accuracy of the equalized model. We again create a simulation model like before and evaluate to determine simulated quantized accuracy.
[ ]:
sim = QuantizationSimModel(model=cle_applied_model,
quant_scheme=QuantScheme.post_training_tf,
rounding_mode="nearest",
default_output_bw=8,
default_param_bw=8)
sim.compute_encodings(forward_pass_callback=pass_calibration_data,
forward_pass_callback_args=1000)
ImageNetDataPipeline.evaluate(sim.model)
5 Exporting
Now the encodings for the QuantizationSimModel have been computed, they can be exported. Exporting can be done with the export function. This function will export the encodings in both a JSON and YAML file, a h5 model without wrappers, a Tensorflow 2 SavedModel, and a converted protobuff model from the h5 model. The converted protobuff model and the encodings exported can be then consumed by either SNPE/QNN.
Note: export()
takes a path to safe to, and a filename_prefix
[ ]:
import os
os.makedirs("./output/", exist_ok=True)
sim.export(path="./output", filename_prefix="resnet50_after_cle")
Summary
Hopefully this notebook was useful for you to understand how to use AIMET for performing Cross Layer Equalization (CLE).
Few additional resources - Refer to the AIMET API docs to know more details of the APIs and optional parameters - Refer to the other example notebooks to understand how to use AIMET post-training quantization techniques and QAT techniques