Files
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

274 lines
9.8 KiB
Python

#
# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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.
#
"""
This module implements classes to configure three supported quantization modes:
1. Full: quantize all layers with standard protocol based NVIDIA quantization scheme.
2. Full special: quantize few layers in a specific way and remaining with standard protocol based on NVIDIA
quantization scheme.
3. Partial: quantize ONLY few layers.
Each quantization mode can quantize all supported Keras layer classes or only subset of it.
"""
from abc import ABC
import tensorflow_quantization.global_config as global_config
import warnings
from typing import List, Dict
class BaseConfig(ABC):
"""
Base class from which four quantize config classes are derived.
Default quantization recipe is Nvidia's recommendation.
"""
def __new__(cls):
instance = super().__new__(cls)
# Add instance to global list
global_config.add_config_object(instance)
return instance
def __init__(self) -> None:
self.quantization_mode: str = "full"
self.layerwise_config: dict = {} # holds special layers information.
self.layer_classes_to_quantize: set = set()
self.config_class_id: int = 0
def __str__(self) -> str:
return (
" quantization_mode: {quant_mode} \n "
"layerwise_config: {layerwise_config} \n "
"specific_layer_class: {specific_layer_class} \n "
"config_class_id: {config_class_id} \n".format(
quant_mode=self.quantization_mode,
layerwise_config=self.layerwise_config,
specific_layer_class=self.specific_layer_class,
config_class_id=self.config_class_id,
)
)
@staticmethod
def _validate_layer_names(
user_passed_layer_names: List, model_layers: List
) -> None:
"""
Check whether user passed layer names exists in Keras model being quantized.
Args:
user_passed_layer_names (List): Layer names passed by user to treat specially.
model_layers (List): Keras model layers passed as a list.
Returns:
None
Raise:
Warning : when specific layer name is not found. Such layers are simply ignored.
"""
model_layer_name_set = set()
for l in model_layers:
model_layer_name_set.add(l.name)
for ul in user_passed_layer_names:
if ul not in model_layer_name_set:
warnings.warn(
"layer name {} is passed by user but could not find layer with this name in model.".format(
ul
)
)
def add_quantization_spec_object(
self, qspec: "QuantizationSpec", original_model_layers: List
) -> None:
"""
This method parses object of QuantizationSpec class and fill in `layerwise_config` dictionary
holding information about layers that need to be treated specially.
Specific layer classes that need to be treated specially are also here.
Args:
qspec (QuantizationSpec): object of QuantizationSpec class. If few layers or layer classes are to be treated
differently, LayerConfig class objects for that layer/layer class are created internally and
added to QuantizationSpec class.
original_model_layers (List): Keras model layers passed as a list.
Returns:
None
"""
for layer in qspec.layers:
if layer.is_keras_class:
self.add_special_layer_class(layer.name)
else:
layer_config_dict = {"qbool_list": [False, False]}
layer_config_dict["qbool_list"][0] = layer.quantize_input
layer_config_dict["qbool_list"][1] = layer.quantize_weight
if layer.quantization_index:
layer_config_dict["qindex_list"] = layer.quantization_index
self.add_special_layer(
layer_name=layer.name, config_dict=layer_config_dict
)
# Validate whether added layers exist in the model
self._validate_layer_names(
list(self.layerwise_config.keys()), original_model_layers
)
def add_special_layer(self, layer_name: str, config_dict: Dict) -> None:
"""
Add layer specific quantization information to quantize config object.
Args:
layer_name (str): layer name
config_dict (Dict): Layer specific quantization parameter dictionary in the
following format.
There are only two accepted keys `qbool_list` and `qindex_list`.
`qbool_list` is list of length two where each value is
[<True/False quantize inputs>, <True/False quantize weights>]
e.g.
To quantize inputs and weights, `qbool_list`=[True, True]
`qindex_list` is a list of specific indices to quaintize for layers such as Add, Concatenate
where more than two inputs are present.
Based on above information,
1. config_dict for weighted layer with name `dense_2`, to quantize inputs and weights will be
{'qbool_list':[True, True]} with laye_name=`dense_2`
2. config_dict for non weighted layer with name `add_3` to quantize input at index 1 will be
{'qbool_list':[True, False], 'qindex_list':[1]} with layer_name=`add_3`
Returns:
None
Raises:
Exception: When invalid keys are detected.
"""
self.layerwise_config[layer_name] = config_dict
def remove_layer(self, layer_name: str) -> None:
"""
Remove specific layer based on name from quantize config object.
Args:
layer_name (str): layer name
Returns:
None
"""
if layer_name in self.layerwise_config:
del self.layerwise_config[layer_name]
def remove_layers(self, layers_name: List) -> None:
"""
Bulk remove specific layers based on names from quantize config object.
Args:
layers_name (List): layers names, list of strings
Returns:
None
"""
for layer_name in layers_name:
self.remove_layer(layer_name=layer_name)
def get_layer_config(self) -> Dict:
"""
Return dictionary with information about layers to quantize for quantize
config object.
Args:
None
Returns:
Dict: a dictionary with layerwise configuration parameters.
"""
return self.layerwise_config
def is_empty(self) -> bool:
"""
Return True if no layer specific quantization information is available in quantize
config object.
Args:
None
Returns:
bool: True if no special layers are passed else return False
"""
return not self.layerwise_config
def clear_layer_config(self) -> None:
"""
Clear layer config information from quanize config object
Args:
None
Returns:
None
"""
self.layerwise_config.clear()
def add_special_layer_class(self, layer_class_name: str) -> None:
"""
Add class name to quantize config object so that only layers with specific class are quantized.
Args:
layer_class_name : String that represents keras class
Returns:
None
"""
self.layer_classes_to_quantize.add(layer_class_name)
def clean(self):
"""
Clean quantize config object from global space. Calling this is important to use `quantize_model` multiple times
within a single module.
Args:
None
Returns:
None
"""
global_config.remove_config_object()
class FullNetworkQuantization(BaseConfig):
"""
Quantize all layers based on NV scheme.
Nvidia recommended recipe for quantization is using Q/DQ only wth inputs/weights.
Q/DQ output support is just to compare engine performance/accuracy when other quantization
scheme is used.
NV: Add Q/DQ at input and weights
TF: Add Q/DQ at output and weights
This is config class with index `0` which is default.
"""
def __init__(self) -> None:
super().__init__()
self.config_class_id = 0
class FullNetworkSpecialQuantization(BaseConfig):
"""
Quantize few layers in specific way and remaining network in standard way based on NV scheme.
Layers are selected based on 'names' which can be via 'model.summary()' for functional
and sequential models.
Subclassed model layer information can be found using `KerasModelTraveller` class from utils.
This is config class with index 1.
"""
def __init__(self) -> None:
super().__init__()
self.config_class_id = 1
class PartialNetworkQuantization(BaseConfig):
"""
Quantize only specific layers and not the entire network.
Layers are selected based on name.
This is config class with index 2.
"""
def __init__(self) -> None:
super().__init__()
self.quantization_mode = "partial"
self.config_class_id = 2