Skip to content

Commit c9a16ff

Browse files
[Solver] Move most of quantizer_config into the QuantizerConfig itself (#3559)
### Changes Most QuantizerConfig logic is moved to the QuantizerConfig class ### Reason for changes To separate logic of QuantizerConfig from the Solver to make it easier to process new QuantizerConfigs in future
1 parent 4b39526 commit c9a16ff

File tree

3 files changed

+69
-65
lines changed

3 files changed

+69
-65
lines changed

nncf/common/quantization/quantizer_propagation/graph.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
from nncf.common.quantization.quantizer_setup import QuantizationInsertionPointBase
4444
from nncf.common.quantization.quantizer_setup import QuantizationPointId
4545
from nncf.common.quantization.quantizer_setup import WeightQuantizationInsertionPoint
46-
from nncf.common.quantization.structs import QuantizationScheme as QuantizationMode
4746
from nncf.common.quantization.structs import QuantizerConfig
4847
from nncf.common.quantization.structs import UnifiedScaleType
4948
from nncf.common.scopes import should_consider_scope
@@ -1155,25 +1154,7 @@ def is_downstream_quantizer_redundant(
11551154
assert len(us_configs) == 1
11561155
ds_config = ds_configs[0]
11571156
us_config = us_configs[0]
1158-
is_redundant = True
1159-
is_redundant = is_redundant and (ds_config.num_bits == us_config.num_bits)
1160-
1161-
# Avoid asymmetric quantization if a symmetrically quantized tensor arrived
1162-
is_redundant = is_redundant and (
1163-
(ds_config.mode == us_config.mode)
1164-
or (ds_config.mode == QuantizationMode.ASYMMETRIC and us_config.mode == QuantizationMode.SYMMETRIC)
1165-
)
1166-
1167-
# Avoid per-channel quantization if a per-tensor-quantized tensor arrived
1168-
is_redundant = is_redundant and (
1169-
(ds_config.per_channel == us_config.per_channel)
1170-
or (ds_config.per_channel is True and us_config.per_channel is False)
1171-
)
1172-
1173-
# Strictly prohibit merging of config with different narrow_range params
1174-
is_redundant = is_redundant and (ds_config.narrow_range == us_config.narrow_range)
1175-
1176-
return is_redundant
1157+
return us_config.is_redundant_with_downstream_qconfig(ds_config)
11771158

11781159
def merge_traverse_fn(
11791160
curr_node_key: str, affecting_pq_and_prev_node_key: tuple[Optional[PropagatingQuantizer], str]

nncf/common/quantization/quantizer_propagation/solver.py

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -155,23 +155,9 @@ def finalize(
155155
else:
156156
final_qconfig = final_quantizer_setup.quantization_points[qp_id].qconfig
157157
if strict:
158-
159-
def is_final_qconfig_compatible_to_initial(initial_qconfig: QuantizerConfig) -> bool:
160-
return (
161-
final_qconfig.per_channel == initial_qconfig.per_channel
162-
and final_qconfig.mode == initial_qconfig.mode
163-
and final_qconfig.num_bits == initial_qconfig.num_bits
164-
and final_qconfig.narrow_range == initial_qconfig.narrow_range
165-
and (
166-
final_qconfig.signedness_to_force == initial_qconfig.signedness_to_force
167-
or initial_qconfig.signedness_to_force is None
168-
or final_qconfig.signedness_to_force is None
169-
)
170-
)
171-
172158
compatible_initial_qconfs = list(
173159
filter(
174-
is_final_qconfig_compatible_to_initial,
160+
final_qconfig.is_compatible_with,
175161
self.quantizer_setup.quantization_points[qp_id].possible_qconfigs,
176162
)
177163
)
@@ -1511,33 +1497,6 @@ def __disambiguate_config_list(
15111497
list. Quantization configs could not contain different narrow range parameters, so it does
15121498
not participate in __lt__ method of the QConfigComparator.
15131499
"""
1514-
1515-
class QConfigComparator:
1516-
def __init__(self, qconfig: QuantizerConfig):
1517-
self.qconfig = qconfig
1518-
1519-
def __lt__(self, other: "QConfigComparator") -> bool:
1520-
# Prefer higher bitwidths, per-tensor, symmetrical
1521-
if self.qconfig.num_bits > other.qconfig.num_bits:
1522-
return True
1523-
if self.qconfig.num_bits < other.qconfig.num_bits:
1524-
return False
1525-
if self.qconfig.per_channel is False and other.qconfig.per_channel is True:
1526-
return True
1527-
if self.qconfig.per_channel is True and other.qconfig.per_channel is False:
1528-
return False
1529-
if (
1530-
self.qconfig.mode is QuantizationMode.SYMMETRIC
1531-
and other.qconfig.mode is QuantizationMode.ASYMMETRIC
1532-
):
1533-
return True
1534-
if (
1535-
self.qconfig.mode is QuantizationMode.ASYMMETRIC
1536-
and other.qconfig.mode is QuantizationMode.SYMMETRIC
1537-
):
1538-
return False
1539-
return False
1540-
15411500
slices_to_sort = []
15421501

15431502
if len(qconfig_list_with_priority) > 1:
@@ -1554,12 +1513,11 @@ def __lt__(self, other: "QConfigComparator") -> bool:
15541513
if last_idx - curr_priority_start_idx > 0:
15551514
slices_to_sort.append(slice(curr_priority_start_idx, last_idx + 1))
15561515

1557-
list_to_sort = [QConfigComparator(x[0]) for x in qconfig_list_with_priority]
1516+
list_to_sort = [x[0] for x in qconfig_list_with_priority]
15581517
for slice_obj in slices_to_sort:
15591518
list_to_sort[slice_obj] = sorted(list_to_sort[slice_obj])
15601519

1561-
retval = [x.qconfig for x in list_to_sort]
1562-
return retval
1520+
return list_to_sort
15631521

15641522
def get_finished_propagating_quantizers(self) -> list[PropagatingQuantizer]:
15651523
"""

nncf/common/quantization/structs.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,71 @@ def __str__(self) -> str:
9898
def __hash__(self) -> int:
9999
return hash(str(self))
100100

101+
def __lt__(self, other: "QuantizerConfig") -> bool:
102+
# Prefer higher bitwidths, per-tensor, symmetrical
103+
if self.num_bits > other.num_bits:
104+
return True
105+
if self.num_bits < other.num_bits:
106+
return False
107+
if self.per_channel is False and other.per_channel is True:
108+
return True
109+
if self.per_channel is True and other.per_channel is False:
110+
return False
111+
if self.mode is QuantizationScheme.SYMMETRIC and other.mode is QuantizationScheme.ASYMMETRIC:
112+
return True
113+
if self.mode is QuantizationScheme.ASYMMETRIC and other.mode is QuantizationScheme.SYMMETRIC:
114+
return False
115+
return False
116+
117+
def is_redundant_with_downstream_qconfig(self, downstream_qconfig: "QuantizerConfig") -> bool:
118+
"""
119+
Returns True if the two quantizers placed immediately one after another are redundant and could be
120+
replaced with a single quantizer.
121+
122+
:param downstream_qconfig: QuantizerConfig of a quantizer placed immediately after the quantizer
123+
with the current QuantizerConfig.
124+
:return: True if the two quantizers placed immediately one after another are redundant and could be
125+
replaced with a single quantizer.
126+
"""
127+
is_redundant = downstream_qconfig.num_bits == self.num_bits
128+
129+
# Avoid asymmetric quantization if a symmetrically quantized tensor arrived
130+
is_redundant = is_redundant and (
131+
(downstream_qconfig.mode == self.mode)
132+
or (downstream_qconfig.mode == QuantizationScheme.ASYMMETRIC and self.mode == QuantizationScheme.SYMMETRIC)
133+
)
134+
135+
# Avoid per-channel quantization if a per-tensor-quantized tensor arrived
136+
is_redundant = is_redundant and (
137+
(downstream_qconfig.per_channel == self.per_channel)
138+
or (downstream_qconfig.per_channel is True and self.per_channel is False)
139+
)
140+
141+
# Strictly prohibit merging of config with different narrow_range params
142+
is_redundant = is_redundant and (downstream_qconfig.narrow_range == self.narrow_range)
143+
return is_redundant
144+
145+
def is_compatible_with(self, other: "QuantizerConfig") -> bool:
146+
"""
147+
Return True if the current QuantizerConfig and the other QuantizerConfig
148+
do not contradict each other.
149+
150+
:param other: The QuantizerConfig to compare with.
151+
:return: True if the current QuantizerConfig and the other QuantizerConfig
152+
do not contradict each other.
153+
"""
154+
return (
155+
self.per_channel == other.per_channel
156+
and self.mode == other.mode
157+
and self.num_bits == other.num_bits
158+
and self.narrow_range == other.narrow_range
159+
and (
160+
self.signedness_to_force == other.signedness_to_force
161+
or other.signedness_to_force is None
162+
or self.signedness_to_force is None
163+
)
164+
)
165+
101166
def is_valid_requantization_for(self, other: "QuantizerConfig") -> bool:
102167
"""
103168
Quantizer config A is a valid requantization for quantizer config B if A is more strict -

0 commit comments

Comments
 (0)