ai-edge-quantizer-nightly 0.0.1.dev20250115__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_edge_quantizer/__init__.py +19 -0
- ai_edge_quantizer/algorithm_manager.py +167 -0
- ai_edge_quantizer/algorithm_manager_api.py +271 -0
- ai_edge_quantizer/algorithm_manager_api_test.py +210 -0
- ai_edge_quantizer/algorithms/__init__.py +15 -0
- ai_edge_quantizer/algorithms/nonlinear_quantize/__init__.py +15 -0
- ai_edge_quantizer/algorithms/nonlinear_quantize/float_casting.py +273 -0
- ai_edge_quantizer/algorithms/nonlinear_quantize/float_casting_test.py +664 -0
- ai_edge_quantizer/algorithms/uniform_quantize/__init__.py +15 -0
- ai_edge_quantizer/algorithms/uniform_quantize/naive_min_max_quantize.py +666 -0
- ai_edge_quantizer/algorithms/uniform_quantize/naive_min_max_quantize_test.py +184 -0
- ai_edge_quantizer/algorithms/uniform_quantize/uniform_quantize_tensor.py +371 -0
- ai_edge_quantizer/algorithms/uniform_quantize/uniform_quantize_tensor_test.py +357 -0
- ai_edge_quantizer/algorithms/utils/__init__.py +15 -0
- ai_edge_quantizer/algorithms/utils/min_max_quantize_utils.py +1067 -0
- ai_edge_quantizer/algorithms/utils/min_max_quantize_utils_test.py +512 -0
- ai_edge_quantizer/calibrator.py +288 -0
- ai_edge_quantizer/calibrator_test.py +297 -0
- ai_edge_quantizer/conftest.py +22 -0
- ai_edge_quantizer/default_policy.py +310 -0
- ai_edge_quantizer/model_modifier.py +176 -0
- ai_edge_quantizer/model_modifier_test.py +130 -0
- ai_edge_quantizer/model_validator.py +357 -0
- ai_edge_quantizer/model_validator_test.py +354 -0
- ai_edge_quantizer/params_generator.py +361 -0
- ai_edge_quantizer/params_generator_test.py +1041 -0
- ai_edge_quantizer/qtyping.py +483 -0
- ai_edge_quantizer/quantizer.py +372 -0
- ai_edge_quantizer/quantizer_test.py +532 -0
- ai_edge_quantizer/recipe.py +67 -0
- ai_edge_quantizer/recipe_manager.py +245 -0
- ai_edge_quantizer/recipe_manager_test.py +815 -0
- ai_edge_quantizer/recipe_test.py +97 -0
- ai_edge_quantizer/transformation_instruction_generator.py +584 -0
- ai_edge_quantizer/transformation_instruction_generator_test.py +1082 -0
- ai_edge_quantizer/transformation_performer.py +278 -0
- ai_edge_quantizer/transformation_performer_test.py +344 -0
- ai_edge_quantizer/transformations/__init__.py +15 -0
- ai_edge_quantizer/transformations/dequant_insert.py +87 -0
- ai_edge_quantizer/transformations/dequant_insert_test.py +304 -0
- ai_edge_quantizer/transformations/emulated_subchannel.py +363 -0
- ai_edge_quantizer/transformations/emulated_subchannel_test.py +212 -0
- ai_edge_quantizer/transformations/quant_insert.py +100 -0
- ai_edge_quantizer/transformations/quant_insert_test.py +284 -0
- ai_edge_quantizer/transformations/quantize_tensor.py +156 -0
- ai_edge_quantizer/transformations/quantize_tensor_test.py +227 -0
- ai_edge_quantizer/transformations/transformation_utils.py +132 -0
- ai_edge_quantizer/transformations/transformation_utils_test.py +162 -0
- ai_edge_quantizer/utils/__init__.py +15 -0
- ai_edge_quantizer/utils/calibration_utils.py +86 -0
- ai_edge_quantizer/utils/calibration_utils_test.py +77 -0
- ai_edge_quantizer/utils/test_utils.py +107 -0
- ai_edge_quantizer/utils/tfl_flatbuffer_utils.py +317 -0
- ai_edge_quantizer/utils/tfl_flatbuffer_utils_test.py +200 -0
- ai_edge_quantizer/utils/tfl_interpreter_utils.py +312 -0
- ai_edge_quantizer/utils/tfl_interpreter_utils_test.py +332 -0
- ai_edge_quantizer/utils/validation_utils.py +125 -0
- ai_edge_quantizer/utils/validation_utils_test.py +87 -0
- ai_edge_quantizer_nightly-0.0.1.dev20250115.dist-info/LICENSE +201 -0
- ai_edge_quantizer_nightly-0.0.1.dev20250115.dist-info/METADATA +32 -0
- ai_edge_quantizer_nightly-0.0.1.dev20250115.dist-info/RECORD +63 -0
- ai_edge_quantizer_nightly-0.0.1.dev20250115.dist-info/WHEEL +5 -0
- ai_edge_quantizer_nightly-0.0.1.dev20250115.dist-info/top_level.txt +1 -0
@@ -0,0 +1,245 @@
|
|
1
|
+
# Copyright 2024 The AI Edge Quantizer Authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# ==============================================================================
|
15
|
+
|
16
|
+
"""Manages model quantization recipe (configuration) for the quantizer."""
|
17
|
+
|
18
|
+
import collections
|
19
|
+
import dataclasses
|
20
|
+
import re
|
21
|
+
from typing import Any, Optional
|
22
|
+
from absl import logging
|
23
|
+
from ai_edge_quantizer import algorithm_manager
|
24
|
+
from ai_edge_quantizer import qtyping
|
25
|
+
|
26
|
+
# A collection of quantization configuration.
|
27
|
+
# Key: scope regex.
|
28
|
+
# Value: list of OpQuantizationRecipe in dictionary format.
|
29
|
+
ModelQuantizationRecipe = list[dict[str, Any]]
|
30
|
+
# Expose algorithm names to users.
|
31
|
+
AlgorithmName = algorithm_manager.AlgorithmName
|
32
|
+
|
33
|
+
_TFLOpName = qtyping.TFLOperationName
|
34
|
+
_OpQuantizationConfig = qtyping.OpQuantizationConfig
|
35
|
+
_TensorQuantizationConfig = qtyping.TensorQuantizationConfig
|
36
|
+
|
37
|
+
|
38
|
+
@dataclasses.dataclass
|
39
|
+
class OpQuantizationRecipe:
|
40
|
+
"""Dataclass for quantization configuration under a scope.
|
41
|
+
|
42
|
+
This class is the main entry point to a recipe schema. There could be a single
|
43
|
+
instance of this associated with a model (with `regex=.*` if the full model is
|
44
|
+
to be quantized with the same spec), or multiple instances targeting different
|
45
|
+
`regex` or `operation`.
|
46
|
+
|
47
|
+
Attributes:
|
48
|
+
regex: Regular expression for scope name matching. Any op that matches
|
49
|
+
`regex` will be quantized according to this instance. The narrowest scope
|
50
|
+
would be the full output tensor name of an op. The widest scope would be
|
51
|
+
'.*' which applies to the full model.
|
52
|
+
operation: Target TFL operation. * for any supported TFLite operation.
|
53
|
+
algorithm_key: Algorithm key to be applied. This can be any one of the
|
54
|
+
strings as enumerated in `AlgorithmName`.
|
55
|
+
op_config: Quantization configuration to be applied for the op.
|
56
|
+
"""
|
57
|
+
|
58
|
+
regex: str
|
59
|
+
operation: _TFLOpName
|
60
|
+
algorithm_key: str
|
61
|
+
op_config: _OpQuantizationConfig = dataclasses.field(
|
62
|
+
default_factory=_OpQuantizationConfig
|
63
|
+
)
|
64
|
+
|
65
|
+
|
66
|
+
class RecipeManager:
|
67
|
+
"""Sets the quantization recipe for target model.
|
68
|
+
|
69
|
+
This class is internal to Quantizer to help manage loading recipes and
|
70
|
+
resolving conflicts between recipes input by the user.
|
71
|
+
"""
|
72
|
+
|
73
|
+
def __init__(self):
|
74
|
+
"""Scope name config.
|
75
|
+
|
76
|
+
Key: scope regex. ".*" for all scopes.
|
77
|
+
Value: list of operator quantization settings under the scope.
|
78
|
+
The priority between rules are determined by the order they entered: later
|
79
|
+
one has higher priority.
|
80
|
+
"""
|
81
|
+
self._scope_configs: collections.OrderedDict[
|
82
|
+
str, list[OpQuantizationRecipe]
|
83
|
+
] = collections.OrderedDict()
|
84
|
+
|
85
|
+
# TODO: b/335254997 - Check if an op quantization config is supported.
|
86
|
+
def add_quantization_config(
|
87
|
+
self,
|
88
|
+
regex: str,
|
89
|
+
operation_name: _TFLOpName,
|
90
|
+
op_config: Optional[_OpQuantizationConfig] = None,
|
91
|
+
algorithm_key: str = algorithm_manager.AlgorithmName.MIN_MAX_UNIFORM_QUANT,
|
92
|
+
) -> None:
|
93
|
+
"""Adds a quantization configuration.
|
94
|
+
|
95
|
+
Conflict arises when we are trying to set an operation under a certain regex
|
96
|
+
which is already existed in the config dictionary. Under such circumstance,
|
97
|
+
the new config is used to replace the previous one.
|
98
|
+
|
99
|
+
We also have special treatment for _TFLOperationKey.ALL. If the new config
|
100
|
+
is on _TFLOperationKey.ALL and there are existing op configs inside the same
|
101
|
+
scope, we clear the previous configs and use _TFLOperationKey.ALL.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
regex: Regular expression for layer name matching.
|
105
|
+
operation_name: Target TFLite operation. * for all supported TFLite
|
106
|
+
operation.
|
107
|
+
op_config: Quantization configuration which will be used to update the
|
108
|
+
default configuration. None or empty dict means the default
|
109
|
+
configuration will be used.
|
110
|
+
algorithm_key: Algorithm key to be applied.
|
111
|
+
"""
|
112
|
+
if op_config is None:
|
113
|
+
op_config = _OpQuantizationConfig()
|
114
|
+
|
115
|
+
config = OpQuantizationRecipe(
|
116
|
+
regex, operation_name, algorithm_key, op_config
|
117
|
+
)
|
118
|
+
# Special care if trying to set all ops to some config.
|
119
|
+
if config.operation == _TFLOpName.ALL_SUPPORTED:
|
120
|
+
self._scope_configs[regex] = [config]
|
121
|
+
return
|
122
|
+
|
123
|
+
if algorithm_key != AlgorithmName.NO_QUANTIZE:
|
124
|
+
algorithm_manager.check_op_quantization_config(
|
125
|
+
algorithm_key, operation_name, op_config
|
126
|
+
)
|
127
|
+
|
128
|
+
if regex not in self._scope_configs:
|
129
|
+
self._scope_configs[regex] = [config]
|
130
|
+
else:
|
131
|
+
# Reiterate configs to avoid duplication on op settings.
|
132
|
+
configs = []
|
133
|
+
is_new_op = True
|
134
|
+
for existing_config in self._scope_configs[regex]:
|
135
|
+
if existing_config.operation == config.operation:
|
136
|
+
is_new_op = False
|
137
|
+
op_config = config
|
138
|
+
logging.warning(
|
139
|
+
'Overwrite operation %s config under scope_regex %s with %s.',
|
140
|
+
existing_config.operation,
|
141
|
+
regex,
|
142
|
+
config,
|
143
|
+
)
|
144
|
+
else:
|
145
|
+
op_config = existing_config
|
146
|
+
configs.append(op_config)
|
147
|
+
if is_new_op:
|
148
|
+
configs.append(config)
|
149
|
+
self._scope_configs[regex] = configs
|
150
|
+
|
151
|
+
def get_quantization_configs(
|
152
|
+
self,
|
153
|
+
target_op_name: _TFLOpName,
|
154
|
+
scope_name: str,
|
155
|
+
) -> tuple[str, _OpQuantizationConfig]:
|
156
|
+
"""Gets the algorithm key and quantization configuration for an op.
|
157
|
+
|
158
|
+
We respect the latest valid config and fall back to no quantization.
|
159
|
+
Specifically, we search the quantization configuration in the order of the
|
160
|
+
scope configs. If there are two or more matching settings, the latest one
|
161
|
+
will be used.
|
162
|
+
|
163
|
+
|
164
|
+
Args:
|
165
|
+
target_op_name: Target TFLite operation. * for all supported TFLite
|
166
|
+
operation.
|
167
|
+
scope_name: Name of the target scope.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
A tuple of quantization algorithm, and quantization configuration.
|
171
|
+
"""
|
172
|
+
result_key, result_config = (
|
173
|
+
AlgorithmName.NO_QUANTIZE,
|
174
|
+
_OpQuantizationConfig(),
|
175
|
+
)
|
176
|
+
for scope_regex, recipes in self._scope_configs.items():
|
177
|
+
if re.search(scope_regex, scope_name):
|
178
|
+
for recipe in recipes:
|
179
|
+
if (
|
180
|
+
recipe.operation != _TFLOpName.ALL_SUPPORTED
|
181
|
+
and recipe.operation != target_op_name
|
182
|
+
):
|
183
|
+
continue
|
184
|
+
selected_recipe = recipe
|
185
|
+
if selected_recipe.algorithm_key != AlgorithmName.NO_QUANTIZE:
|
186
|
+
# The selected recipe must contain a supported config.
|
187
|
+
try:
|
188
|
+
algorithm_manager.check_op_quantization_config(
|
189
|
+
recipe.algorithm_key, target_op_name, recipe.op_config
|
190
|
+
)
|
191
|
+
except ValueError:
|
192
|
+
continue # Skip the recipe if it is not supported.
|
193
|
+
result_config = selected_recipe.op_config
|
194
|
+
result_key = selected_recipe.algorithm_key
|
195
|
+
|
196
|
+
return result_key, result_config
|
197
|
+
|
198
|
+
def get_quantization_recipe(self) -> ModelQuantizationRecipe:
|
199
|
+
"""Gets the full quantization recipe from the manager.
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
A list of quantization configs in the recipe.
|
203
|
+
"""
|
204
|
+
ret = []
|
205
|
+
for _, scope_config in self._scope_configs.items():
|
206
|
+
for quant_config in scope_config:
|
207
|
+
config = dict()
|
208
|
+
config['regex'] = quant_config.regex
|
209
|
+
config['operation'] = quant_config.operation
|
210
|
+
config['algorithm_key'] = quant_config.algorithm_key
|
211
|
+
config['op_config'] = quant_config.op_config.to_dict()
|
212
|
+
ret.append(config)
|
213
|
+
return ret
|
214
|
+
|
215
|
+
def load_quantization_recipe(
|
216
|
+
self, quantization_recipe: ModelQuantizationRecipe
|
217
|
+
) -> None:
|
218
|
+
"""Loads the quantization recipe to the manager.
|
219
|
+
|
220
|
+
Args:
|
221
|
+
quantization_recipe: A configuration dictionary which is generated by
|
222
|
+
get_full_config.
|
223
|
+
"""
|
224
|
+
self._scope_configs = collections.OrderedDict()
|
225
|
+
for config in quantization_recipe:
|
226
|
+
self.add_quantization_config(
|
227
|
+
config['regex'],
|
228
|
+
config['operation'],
|
229
|
+
_OpQuantizationConfig.from_dict(config['op_config'])
|
230
|
+
if config['algorithm_key'] != AlgorithmName.NO_QUANTIZE
|
231
|
+
else None,
|
232
|
+
config['algorithm_key'],
|
233
|
+
)
|
234
|
+
|
235
|
+
def need_calibration(self) -> bool:
|
236
|
+
"""Check if the recipe requires calibration."""
|
237
|
+
# At the moment, only SRQ requires calibration.
|
238
|
+
for op_quant_config in self.get_quantization_recipe():
|
239
|
+
if (
|
240
|
+
op_quant_config['op_config']['compute_precision']
|
241
|
+
== qtyping.ComputePrecision.INTEGER
|
242
|
+
and 'activation_tensor_config' in op_quant_config['op_config']
|
243
|
+
):
|
244
|
+
return True
|
245
|
+
return False
|