From 161078de46c60270a74ae84173785b40ad0a106e Mon Sep 17 00:00:00 2001 From: anzr299 Date: Thu, 24 Sep 2026 18:24:37 +0400 Subject: [PATCH 1/6] init --- .../sparsify_activations_impl.py | 6 +- .../sparsify_activations/target_scope.py | 8 +- .../openvino/quantization/quantize_model.py | 4 +- .../algorithms/min_max/algorithm.py | 4 +- .../weight_compression/algorithm.py | 8 +- src/nncf/scopes.py | 189 ++++++++++-------- .../function_hook/pruning/prune_model.py | 4 +- tests/common/test_ignored_scope.py | 6 +- tests/common/test_scopes.py | 4 +- 9 files changed, 126 insertions(+), 107 deletions(-) diff --git a/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py b/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py index d63087ceb1a..17da6016f03 100644 --- a/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py +++ b/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py @@ -27,7 +27,7 @@ from nncf.experimental.torch.sparsify_activations.target_scope import TargetScope from nncf.experimental.torch.sparsify_activations.target_scope import get_target_node_names_from_target_scope from nncf.scopes import IgnoredScope -from nncf.scopes import get_ignored_node_names_from_ignored_scope +from nncf.scopes import get_node_names_from_scope from nncf.torch.model_creation import is_wrapped_model from nncf.torch.model_creation import wrap_model @@ -181,9 +181,7 @@ def _get_target_sparsity_by_node(self, graph: NNCFGraph) -> dict[NNCFNode, float :return: A dictionary with nodes and the corresponding target sparsity level. """ supported_metatypes = self._backend_entity.supported_metatypes - ignored_names = get_ignored_node_names_from_ignored_scope( - self._ignored_scope, graph, strict=self._ignored_scope.validate - ) + ignored_names = get_node_names_from_scope(self._ignored_scope, graph, strict=self._ignored_scope.validate) target_sparsity_by_node = {} for scope, target_sparsity in self._target_sparsity_by_scope.items(): target_names = get_target_node_names_from_target_scope(scope, graph, strict=scope.validate) diff --git a/src/nncf/experimental/torch/sparsify_activations/target_scope.py b/src/nncf/experimental/torch/sparsify_activations/target_scope.py index efc98223dd4..103cbc70551 100644 --- a/src/nncf/experimental/torch/sparsify_activations/target_scope.py +++ b/src/nncf/experimental/torch/sparsify_activations/target_scope.py @@ -14,8 +14,8 @@ import nncf from nncf.common.graph.graph import NNCFGraph from nncf.scopes import IgnoredScope -from nncf.scopes import get_difference_ignored_scope -from nncf.scopes import get_matched_ignored_scope_info +from nncf.scopes import get_difference_scope +from nncf.scopes import get_matched_scope_info @dataclass @@ -84,7 +84,7 @@ def get_target_node_names_from_target_scope( :param strict: Whether target_scope must match at least one node or not. :return: NNCF node names from the given graph matched by target scope. """ - matched_target_scope, matches = get_matched_ignored_scope_info(target_scope, [nncf_graph]) + matched_target_scope, matches = get_matched_scope_info(target_scope, [nncf_graph]) if strict: _check_target_scope_strictly_matched(target_scope, matched_target_scope) return set().union(*matches.values()) @@ -97,7 +97,7 @@ def _check_target_scope_strictly_matched(target_scope: TargetScope, matched_targ :param target_scope: The given target scope. :param matched_target_scope: The actual target scope matched in a graph. """ - unmatched_scope = get_difference_ignored_scope(target_scope, matched_target_scope) + unmatched_scope = get_difference_scope(target_scope, matched_target_scope) error_messages = [] for match_type in ("names", "types", "patterns", "subgraphs"): unmatched_rules = getattr(unmatched_scope, match_type) diff --git a/src/nncf/openvino/quantization/quantize_model.py b/src/nncf/openvino/quantization/quantize_model.py index 19031fb1674..0ff7385af60 100644 --- a/src/nncf/openvino/quantization/quantize_model.py +++ b/src/nncf/openvino/quantization/quantize_model.py @@ -55,7 +55,7 @@ from nncf.quantization.statistics_caching import cache_weight_compression_statistics from nncf.quantization.statistics_caching import register_statistics_for_algorithm from nncf.scopes import IgnoredScope -from nncf.scopes import validate_ignored_scope +from nncf.scopes import validate_scope TTensor = TypeVar("TTensor") @@ -96,7 +96,7 @@ def _extract_all_subgraphs(model: ov.Model, current_id: str) -> None: main_model_graph_id = "main_model_graph" _extract_all_subgraphs(model, main_model_graph_id) if ignored_scope and ignored_scope.validate: - validate_ignored_scope(ignored_scope, graphs.values()) + validate_scope(ignored_scope, graphs.values()) ignored_scope = IgnoredScope( ignored_scope.names, ignored_scope.patterns, ignored_scope.types, ignored_scope.subgraphs, validate=False ) diff --git a/src/nncf/quantization/algorithms/min_max/algorithm.py b/src/nncf/quantization/algorithms/min_max/algorithm.py index 34330dd5b39..ebc9644c4c3 100644 --- a/src/nncf/quantization/algorithms/min_max/algorithm.py +++ b/src/nncf/quantization/algorithms/min_max/algorithm.py @@ -69,7 +69,7 @@ from nncf.quantization.range_estimator import RangeEstimatorParametersSet from nncf.quantization.range_estimator import StatisticsType from nncf.scopes import IgnoredScope -from nncf.scopes import get_ignored_node_names_from_ignored_scope +from nncf.scopes import get_node_names_from_scope TModel = TypeVar("TModel") @@ -613,7 +613,7 @@ def _get_ignored_names( :param ignored_patterns: Ignored patterns. :return: Ignored node names and ignore reason for quantization. """ - user_ignored_names = get_ignored_node_names_from_ignored_scope( + user_ignored_names = get_node_names_from_scope( self._ignored_scope, nncf_graph, strict=self._ignored_scope.validate ) autogenerated_ignored_names = get_node_names_matching_graph_pattern(inference_nncf_graph, ignored_patterns) diff --git a/src/nncf/quantization/algorithms/weight_compression/algorithm.py b/src/nncf/quantization/algorithms/weight_compression/algorithm.py index 9553127d84b..b0d9f5d41b1 100644 --- a/src/nncf/quantization/algorithms/weight_compression/algorithm.py +++ b/src/nncf/quantization/algorithms/weight_compression/algorithm.py @@ -51,7 +51,7 @@ from nncf.quantization.algorithms.weight_compression.weight_lowering import WeightCompressionConfig from nncf.quantization.algorithms.weight_compression.weight_lowering import get_reduction_channel_size from nncf.scopes import IgnoredScope -from nncf.scopes import get_ignored_node_names_from_ignored_scope +from nncf.scopes import get_node_names_from_scope from nncf.tensor import Tensor from nncf.tensor import functions as fns from nncf.tensor.definitions import TensorDataType @@ -506,9 +506,7 @@ def get_ignored_node_names(self, nncf_graph: NNCFGraph) -> set[str]: :param nncf_graph: The NNCF graph to analyze. :return: A set of node names to be ignored during weight compression. """ - ignored_names = get_ignored_node_names_from_ignored_scope( - self._ignored_scope, nncf_graph, strict=self._ignored_scope.validate - ) + ignored_names = get_node_names_from_scope(self._ignored_scope, nncf_graph, strict=self._ignored_scope.validate) autogenerated_ignored_names = get_node_names_matching_graph_pattern( nncf_graph, self._backend_entity.get_ignored_patterns() @@ -858,7 +856,7 @@ def _get_ignored_scope_weight_statistics(self, model: TModel, graph: NNCFGraph) :param graph: Model graph. :return: A list of weight sizes for the ignored nodes. """ - ignored_names = get_ignored_node_names_from_ignored_scope(self._ignored_scope, graph, strict=False) + ignored_names = get_node_names_from_scope(self._ignored_scope, graph, strict=False) weighted_metatypes = ( self._backend_entity.matmul_metatypes + self._backend_entity.embedding_metatypes diff --git a/src/nncf/scopes.py b/src/nncf/scopes.py index 93a6c34bb32..287abd92260 100644 --- a/src/nncf/scopes.py +++ b/src/nncf/scopes.py @@ -36,33 +36,53 @@ class Subgraph: outputs: list[str] = field(default_factory=list) -def get_ignored_node_names_from_subgraph(graph: NNCFGraph, subgraph: Subgraph) -> list[str]: +def get_node_names_from_subgraph(graph: NNCFGraph, subgraph: Subgraph) -> list[str]: """ - Returns all names that should be ignored according to given subgraph. + Returns all names that are matched by the given subgraph. :param graph: Given NNCFGraph. :param subgraph: Given subgraph instance. - :return: All names that should be ignored according to given subgraph. + :return: All names that are matched by the given subgraph. """ - ignored_names = set() + matched_names = set() for start_node_name in subgraph.inputs: for end_node_name in subgraph.outputs: if start_node_name == end_node_name: # For networkx<3.3 nx.get_all_simple_paths returns empty path for this case node = graph.get_node_by_name(start_node_name) - ignored_names.add(node.node_name) + matched_names.add(node.node_name) continue for path in graph.get_all_simple_paths(start_node_name, end_node_name): for node_key in path: node = graph.get_node_by_key(node_key) - ignored_names.add(node.node_name) + matched_names.add(node.node_name) - return list(sorted(ignored_names)) + return list(sorted(matched_names)) + + +@dataclass +class BaseScope: + """ + Defines a portion of a model by a set of matching rules against the model graph. + + :param names: List of node names. + :param patterns: List of regular expressions that define patterns for names of nodes. + :param types: List of operation types. + :param subgraphs: List of subgraphs. + :param validate: If set to True, then an error will be raised if any rule does not match + in the model graph. + """ + + names: list[str] = field(default_factory=list) + patterns: list[str] = field(default_factory=list) + types: list[str] = field(default_factory=list) + subgraphs: list[Subgraph] = field(default_factory=list) + validate: bool = True @api(canonical_alias="nncf.IgnoredScope") @dataclass -class IgnoredScope: +class IgnoredScope(BaseScope): r""" Provides an option to specify portions of model to be excluded from compression. @@ -108,27 +128,23 @@ class IgnoredScope: :type types: bool """ - names: list[str] = field(default_factory=list) - patterns: list[str] = field(default_factory=list) - types: list[str] = field(default_factory=list) - subgraphs: list[Subgraph] = field(default_factory=list) - validate: bool = True - -def get_difference_ignored_scope(ignored_scope_1: IgnoredScope, ignored_scope_2: IgnoredScope) -> IgnoredScope: +def get_difference_scope(scope_1: BaseScope, scope_2: BaseScope) -> BaseScope: """ - Returns ignored scope with rules from 'ignored_scope_1' not presented at 'ignored_scope_2' + Returns scope with rules from 'scope_1' not presented at 'scope_2'. + The returned scope has the same type as 'scope_1'. - :param ignored_scope_1: First ignored scope. - :param ignored_scope_2: Second ignored scope. - :return: Ignored scope. + :param scope_1: First scope. + :param scope_2: Second scope. + :return: Scope with the rules difference. """ - return IgnoredScope( - names=list(set(ignored_scope_1.names) - set(ignored_scope_2.names)), - patterns=list(set(ignored_scope_1.patterns) - set(ignored_scope_2.patterns)), - types=list(set(ignored_scope_1.types) - set(ignored_scope_2.types)), - subgraphs=[subgraph for subgraph in ignored_scope_1.subgraphs if subgraph not in ignored_scope_2.subgraphs], - validate=ignored_scope_1.validate, + cls = scope_1.__class__ + return cls( + names=list(set(scope_1.names) - set(scope_2.names)), + patterns=list(set(scope_1.patterns) - set(scope_2.patterns)), + types=list(set(scope_1.types) - set(scope_2.types)), + subgraphs=[subgraph for subgraph in scope_1.subgraphs if subgraph not in scope_2.subgraphs], + validate=scope_1.validate, ) @@ -152,129 +168,136 @@ def convert_ignored_scope_to_list(ignored_scope: IgnoredScope | None) -> list[st return results -def get_matched_ignored_scope_info( - ignored_scope: IgnoredScope, nncf_graphs: list[NNCFGraph] -) -> tuple[IgnoredScope, dict[str, set[str]]]: +def get_matched_scope_info(scope: BaseScope, nncf_graphs: list[NNCFGraph]) -> tuple[BaseScope, dict[str, set[str]]]: """ - Returns matched ignored scope for provided graphs along with all found matches. - The resulted ignored scope consist of all matched rules. + Returns matched scope for provided graphs along with all found matches. + The resulted scope consist of all matched rules and has the same type as the given scope. The found matches consist of a dictionary with a rule name as a key and matched node names as a value. - :param ignored_scope: Ignored scope instance. + :param scope: Scope instance. :param nncf_graphs: Graphs. - :returns: Matched ignored scope along with all matches. + :returns: Matched scope along with all matches. """ names, patterns, types, subgraphs_numbers = set(), set(), set(), set() # type: ignore matches = {"names": names, "patterns": set(), "types": set(), "subgraphs": set()} for graph in nncf_graphs: - if ignored_scope.names or ignored_scope.patterns: + if scope.names or scope.patterns: node_names = set(node.node_name for node in graph.nodes.values()) - for ignored_node_name in filter(lambda name: name in node_names, ignored_scope.names): - names.add(ignored_node_name) + for matched_node_name in filter(lambda name: name in node_names, scope.names): + names.add(matched_node_name) - for str_pattern in ignored_scope.patterns: + for str_pattern in scope.patterns: pattern = re.compile(str_pattern) pattern_matched_names = set(filter(pattern.match, node_names)) if pattern_matched_names: matches["patterns"].update(pattern_matched_names) patterns.add(str_pattern) - for node in graph.get_nodes_by_types(ignored_scope.types): + for node in graph.get_nodes_by_types(scope.types): matches["types"].add(node.node_name) types.add(node.node_type) - for i, subgraph in enumerate(ignored_scope.subgraphs): - names_from_subgraph = get_ignored_node_names_from_subgraph(graph, subgraph) + for i, subgraph in enumerate(scope.subgraphs): + names_from_subgraph = get_node_names_from_subgraph(graph, subgraph) if names_from_subgraph: matches["subgraphs"].update(names_from_subgraph) subgraphs_numbers.add(i) - matched_ignored_scope = IgnoredScope( + cls = scope.__class__ + matched_scope = cls( names=list(names), patterns=list(patterns), types=list(types), - subgraphs=[subgraph for i, subgraph in enumerate(ignored_scope.subgraphs) if i in subgraphs_numbers], - validate=ignored_scope.validate, + subgraphs=[subgraph for i, subgraph in enumerate(scope.subgraphs) if i in subgraphs_numbers], + validate=scope.validate, ) - return matched_ignored_scope, matches + return matched_scope, matches -def _info_matched_ignored_scope(matches: dict[str, set[str]]) -> None: +def _get_scope_name(scope: BaseScope) -> str: + """ + Returns the name of the given scope, used in the messages about the matched and unmatched nodes. + + :param scope: Scope. + :return: Name of the scope. + """ + return "Ignored" if isinstance(scope, IgnoredScope) else "Annotated" + + +def _info_matched_scope(matches: dict[str, set[str]], scope: BaseScope) -> None: """ Log matches. :param matches: Matches. + :param scope: Scope the matches are found by. """ + scope_name = _get_scope_name(scope) for rule_type, rules in matches.items(): if rules: - nncf_logger.info(f"{len(rules)} ignored nodes were found by {rule_type} in the NNCFGraph") + nncf_logger.info(f"{len(rules)} {scope_name} nodes were found by {rule_type} in the NNCFGraph") -def _error_unmatched_ignored_scope(unmatched_ignored_scope: IgnoredScope) -> str: +def _error_unmatched_scope(unmatched_scope: BaseScope) -> str: """ - Returns an error message for unmatched ignored scope. + Returns an error message for unmatched scope. - :param unmatched_ignored_scope: Unmatched ignored scope. + :param unmatched_scope: Unmatched scope. :return str: Error message. """ + scope_name = _get_scope_name(unmatched_scope) err_msg = "\n" - for ignored_type in ("names", "types", "patterns"): - unmatched_rules = getattr(unmatched_ignored_scope, ignored_type) + for rule_type in ("names", "types", "patterns"): + unmatched_rules = getattr(unmatched_scope, rule_type) if unmatched_rules: - err_msg += f"Ignored nodes that matches {ignored_type} {unmatched_rules} were not found in the NNCFGraph.\n" - for subgraph in unmatched_ignored_scope.subgraphs: + err_msg += ( + f"{scope_name} nodes that matches {rule_type} {unmatched_rules} were not found in the NNCFGraph.\n" + ) + for subgraph in unmatched_scope.subgraphs: err_msg += ( - f"Ignored nodes that matches subgraph with input names {subgraph.inputs} " + f"{scope_name} nodes that matches subgraph with input names {subgraph.inputs} " f"and output names {subgraph.outputs} were not found in the NNCFGraph.\n" ) return err_msg -def _check_ignored_scope_strictly_matched(ignored_scope: IgnoredScope, matched_ignored_scope: IgnoredScope) -> None: +def _check_scope_strictly_matched(scope: BaseScope, matched_scope: BaseScope) -> None: """ - Passes when ignored_scope and matched_ignored_scope are equal, otherwise - raises ValidationError. + Passes when scope and matched_scope are equal, otherwise - raises ValidationError. - :param ignored_scope: Ignored scope. - :param matched_ignored_scope: Matched ignored scope. + :param scope: Scope. + :param matched_scope: Matched scope. """ - unmatched_ignored_scope = get_difference_ignored_scope(ignored_scope, matched_ignored_scope) - if ( - unmatched_ignored_scope.names - or unmatched_ignored_scope.types - or unmatched_ignored_scope.patterns - or unmatched_ignored_scope.subgraphs - ): - raise nncf.ValidationError(_error_unmatched_ignored_scope(unmatched_ignored_scope)) - - -def get_ignored_node_names_from_ignored_scope( - ignored_scope: IgnoredScope, nncf_graph: NNCFGraph, strict: bool = True -) -> set[str]: + unmatched_scope = get_difference_scope(scope, matched_scope) + if unmatched_scope.names or unmatched_scope.types or unmatched_scope.patterns or unmatched_scope.subgraphs: + raise nncf.ValidationError(_error_unmatched_scope(unmatched_scope)) + + +def get_node_names_from_scope(scope: BaseScope, nncf_graph: NNCFGraph, strict: bool = True) -> set[str]: """ - Returns ignored names according to ignored scope and NNCFGraph. - If strict is True, raises nncf.ValidationError if any ignored rule was not matched. + Returns matched names according to the scope and NNCFGraph. + If strict is True, raises nncf.ValidationError if any rule was not matched. If strict is False, returns all possible matches. - :param ignored_scope: Ignored scope. + :param scope: Scope. :param nncf_graph: Graph. - :param strict: Whether all ignored_scopes must match at least one node or not. - :return: NNCF node names from given NNCFGraph specified in given ignored scope. + :param strict: Whether all scope rules must match at least one node or not. + :return: NNCF node names from given NNCFGraph specified in given scope. """ - matched_ignored_scope, matches = get_matched_ignored_scope_info(ignored_scope, [nncf_graph]) + matched_scope, matches = get_matched_scope_info(scope, [nncf_graph]) if strict: - _check_ignored_scope_strictly_matched(ignored_scope, matched_ignored_scope) - _info_matched_ignored_scope(matches) + _check_scope_strictly_matched(scope, matched_scope) + _info_matched_scope(matches, scope) return {name for match in matches.values() for name in match} -def validate_ignored_scope(ignored_scope: IgnoredScope, nncf_graphs: list[NNCFGraph]) -> None: +def validate_scope(scope: BaseScope, nncf_graphs: list[NNCFGraph]) -> None: """ - Passes whether all rules at 'ignored_scope' have matches at provided graphs, otherwise - raises ValidationError. + Passes whether all rules at 'scope' have matches at provided graphs, otherwise - raises ValidationError. - :param ignored_scope: Ignored scope. + :param scope: Scope. :param nncf_graphs: Graphs. """ - matched_ignored_scope, _ = get_matched_ignored_scope_info(ignored_scope, nncf_graphs) - _check_ignored_scope_strictly_matched(ignored_scope, matched_ignored_scope) + matched_scope, _ = get_matched_scope_info(scope, nncf_graphs) + _check_scope_strictly_matched(scope, matched_scope) diff --git a/src/nncf/torch/function_hook/pruning/prune_model.py b/src/nncf/torch/function_hook/pruning/prune_model.py index d04243e2390..d9bedff34eb 100644 --- a/src/nncf/torch/function_hook/pruning/prune_model.py +++ b/src/nncf/torch/function_hook/pruning/prune_model.py @@ -17,7 +17,7 @@ from nncf.common.logging import nncf_logger from nncf.parameters import PruneMode from nncf.scopes import IgnoredScope -from nncf.scopes import get_ignored_node_names_from_ignored_scope +from nncf.scopes import get_node_names_from_scope from nncf.torch.function_hook.nncf_graph.layer_attributes import PT2OpLayerAttributes from nncf.torch.function_hook.nncf_graph.nncf_graph_builder import build_nncf_graph from nncf.torch.function_hook.pruning.magnitude.algo import apply_magnitude_pruning @@ -89,7 +89,7 @@ def get_prunable_parameters(graph: PTNNCFGraph, ignored_scope: IgnoredScope | No """ ignored_names: set[str] = set() if ignored_scope is not None: - ignored_names = get_ignored_node_names_from_ignored_scope(ignored_scope, graph) + ignored_names = get_node_names_from_scope(ignored_scope, graph) nodes_with_weights = graph.get_nodes_by_metatypes(OPERATORS_WITH_WEIGHTS_METATYPES) parameters_to_sparsity: set[str] = set() diff --git a/tests/common/test_ignored_scope.py b/tests/common/test_ignored_scope.py index dcdb58e0c52..4117a93cc56 100644 --- a/tests/common/test_ignored_scope.py +++ b/tests/common/test_ignored_scope.py @@ -16,7 +16,7 @@ from nncf.common.graph.operator_metatypes import OutputNoopMetatype from nncf.scopes import IgnoredScope from nncf.scopes import Subgraph -from nncf.scopes import get_ignored_node_names_from_ignored_scope +from nncf.scopes import get_node_names_from_scope from tests.common.quantization.metatypes import Conv2dTestMetatype from tests.common.quantization.metatypes import LinearTestMetatype from tests.common.quantization.mock_graphs import NodeWithType @@ -74,7 +74,7 @@ def __init__(self, conv_metatype, linear_metatype): @pytest.mark.parametrize("ignored_scope,ref_ignored_names", IGNORED_SCOPES_TEST_DATA) def test_ignored_scopes(ignored_scope, ref_ignored_names): nncf_graph = NNCFGraphToTestIgnoredScope(CONV_TYPE, LINEAR_TYPE).nncf_graph - ignored_names = get_ignored_node_names_from_ignored_scope(ignored_scope, nncf_graph) + ignored_names = get_node_names_from_scope(ignored_scope, nncf_graph) assert sorted(ignored_names) == ref_ignored_names @@ -90,4 +90,4 @@ def test_ignored_scopes(ignored_scope, ref_ignored_names): def test_wrong_ignored_scopes(ignored_scope): nncf_graph = NNCFGraphToTestIgnoredScope(CONV_TYPE, LINEAR_TYPE).nncf_graph with pytest.raises(nncf.ValidationError): - get_ignored_node_names_from_ignored_scope(ignored_scope, nncf_graph) + get_node_names_from_scope(ignored_scope, nncf_graph) diff --git a/tests/common/test_scopes.py b/tests/common/test_scopes.py index 58167ac41ce..91ce47bb9dd 100644 --- a/tests/common/test_scopes.py +++ b/tests/common/test_scopes.py @@ -14,7 +14,7 @@ from nncf.common.scopes import get_not_matched_scopes from nncf.scopes import IgnoredScope from nncf.scopes import Subgraph -from nncf.scopes import get_difference_ignored_scope +from nncf.scopes import get_difference_scope @pytest.mark.parametrize( @@ -74,4 +74,4 @@ def test_get_not_matched_scopes(scope, ref): ), ) def test_ignored_scope_diff(scope_1, scope_2, ref): - assert get_difference_ignored_scope(scope_1, scope_2) == ref + assert get_difference_scope(scope_1, scope_2) == ref From 5b05f6909973f19cffd3ac844863846836652081 Mon Sep 17 00:00:00 2001 From: anzr299 Date: Tue, 29 Sep 2026 11:26:21 +0400 Subject: [PATCH 2/6] remove ignored scope completeley; rename basescope to scope --- src/nncf/__init__.py | 3 +- src/nncf/scopes.py | 119 +++++++----------- .../test_templates/test_ptq_params.py | 2 +- 3 files changed, 49 insertions(+), 75 deletions(-) diff --git a/src/nncf/__init__.py b/src/nncf/__init__.py index 475d7e836fc..38312e8d8e2 100644 --- a/src/nncf/__init__.py +++ b/src/nncf/__init__.py @@ -67,7 +67,8 @@ from nncf.quantization.advanced_parameters import AdvancedSmoothQuantParameters as AdvancedSmoothQuantParameters from nncf.quantization.advanced_parameters import GroupSizeFallbackMode as GroupSizeFallbackMode from nncf.quantization.advanced_parameters import OverflowFix as OverflowFix -from nncf.scopes import IgnoredScope as IgnoredScope +from nncf.scopes import IgnoredScope as IgnoredScope # Deprecated alias for nncf.Scope +from nncf.scopes import Scope as Scope from nncf.scopes import Subgraph as Subgraph from nncf.version import __version__ as __version__ diff --git a/src/nncf/scopes.py b/src/nncf/scopes.py index 287abd92260..606d1b0d9d4 100644 --- a/src/nncf/scopes.py +++ b/src/nncf/scopes.py @@ -60,34 +60,11 @@ def get_node_names_from_subgraph(graph: NNCFGraph, subgraph: Subgraph) -> list[s return list(sorted(matched_names)) +@api(canonical_alias="nncf.Scope") @dataclass -class BaseScope: - """ - Defines a portion of a model by a set of matching rules against the model graph. - - :param names: List of node names. - :param patterns: List of regular expressions that define patterns for names of nodes. - :param types: List of operation types. - :param subgraphs: List of subgraphs. - :param validate: If set to True, then an error will be raised if any rule does not match - in the model graph. - """ - - names: list[str] = field(default_factory=list) - patterns: list[str] = field(default_factory=list) - types: list[str] = field(default_factory=list) - subgraphs: list[Subgraph] = field(default_factory=list) - validate: bool = True - - -@api(canonical_alias="nncf.IgnoredScope") -@dataclass -class IgnoredScope(BaseScope): +class Scope: r""" - Provides an option to specify portions of model to be excluded from compression. - - The ignored scope defines model sub-graphs that should be excluded from the compression process such as - quantization, pruning and etc. + Defines a portion of a model by a set of matching rules against the model graph. Example: @@ -95,51 +72,63 @@ class IgnoredScope(BaseScope): import nncf - # Exclude by node name: + # Specify by node name: node_names = ['node_1', 'node_2', 'node_3'] - ignored_scope = nncf.IgnoredScope(names=node_names) + scope = nncf.Scope(names=node_names) - # Exclude using regular expressions: + # Specify using regular expressions: patterns = ['node_\\d'] - ignored_scope = nncf.IgnoredScope(patterns=patterns) + scope = nncf.Scope(patterns=patterns) - # Exclude by operation type: + # Specify by operation type: # OpenVINO opset https://docs.openvino.ai/latest/openvino_docs_ops_opset.html operation_types = ['Multiply', 'GroupConvolution', 'Interpolate'] - ignored_scope = nncf.IgnoredScope(types=operation_types) + scope = nncf.Scope(types=operation_types) # ONNX opset https://github.com/onnx/onnx/blob/main/docs/Operators.md operation_types = ['Mul', 'Conv', 'Resize'] - ignored_scope = nncf.IgnoredScope(types=operation_types) + scope = nncf.Scope(types=operation_types) + + # Specify by subgraph: + scope = nncf.Scope(subgraphs=[nncf.Subgraph(inputs=['node_1'], outputs=['node_3'])]) **Note:** Operation types must be specified according to the model framework. - :param names: List of ignored node names. + :param names: List of node names. :type names: List[str] - :param patterns: List of regular expressions that define patterns for names of ignored nodes. + :param patterns: List of regular expressions that define patterns for names of nodes. :type patterns: List[str] - :param types: List of ignored operation types. + :param types: List of operation types. :type types: List[str] - :param subgraphs: List of ignored subgraphs. + :param subgraphs: List of subgraphs. :type subgraphs: List[Subgraph] - :param validate: If set to True, then a RuntimeError will be raised if any ignored scope does not match + :param validate: If set to True, then a RuntimeError will be raised if any rule does not match in the model graph. - :type types: bool + :type validate: bool """ + names: list[str] = field(default_factory=list) + patterns: list[str] = field(default_factory=list) + types: list[str] = field(default_factory=list) + subgraphs: list[Subgraph] = field(default_factory=list) + validate: bool = True -def get_difference_scope(scope_1: BaseScope, scope_2: BaseScope) -> BaseScope: + +# Deprecated alias for :class:`nncf.Scope`, kept for backward compatibility. Use `nncf.Scope` instead. +# TODO(anzr299): Remove this alias in future properly. +IgnoredScope = Scope + + +def get_difference_scope(scope_1: Scope, scope_2: Scope) -> Scope: """ Returns scope with rules from 'scope_1' not presented at 'scope_2'. - The returned scope has the same type as 'scope_1'. :param scope_1: First scope. :param scope_2: Second scope. :return: Scope with the rules difference. """ - cls = scope_1.__class__ - return cls( + return Scope( names=list(set(scope_1.names) - set(scope_2.names)), patterns=list(set(scope_1.patterns) - set(scope_2.patterns)), types=list(set(scope_1.types) - set(scope_2.types)), @@ -148,9 +137,9 @@ def get_difference_scope(scope_1: BaseScope, scope_2: BaseScope) -> BaseScope: ) -def convert_ignored_scope_to_list(ignored_scope: IgnoredScope | None) -> list[str]: +def convert_ignored_scope_to_list(ignored_scope: Scope | None) -> list[str]: """ - Convert the contents of the `IgnoredScope` class to the legacy ignored + Convert the contents of the ignored `Scope` to the legacy ignored scope format. :param ignored_scope: The ignored scope. @@ -168,10 +157,10 @@ def convert_ignored_scope_to_list(ignored_scope: IgnoredScope | None) -> list[st return results -def get_matched_scope_info(scope: BaseScope, nncf_graphs: list[NNCFGraph]) -> tuple[BaseScope, dict[str, set[str]]]: +def get_matched_scope_info(scope: Scope, nncf_graphs: list[NNCFGraph]) -> tuple[Scope, dict[str, set[str]]]: """ Returns matched scope for provided graphs along with all found matches. - The resulted scope consist of all matched rules and has the same type as the given scope. + The resulted scope consist of all matched rules. The found matches consist of a dictionary with a rule name as a key and matched node names as a value. :param scope: Scope instance. @@ -205,8 +194,7 @@ def get_matched_scope_info(scope: BaseScope, nncf_graphs: list[NNCFGraph]) -> tu matches["subgraphs"].update(names_from_subgraph) subgraphs_numbers.add(i) - cls = scope.__class__ - matched_scope = cls( + matched_scope = Scope( names=list(names), patterns=list(patterns), types=list(types), @@ -216,53 +204,38 @@ def get_matched_scope_info(scope: BaseScope, nncf_graphs: list[NNCFGraph]) -> tu return matched_scope, matches -def _get_scope_name(scope: BaseScope) -> str: - """ - Returns the name of the given scope, used in the messages about the matched and unmatched nodes. - - :param scope: Scope. - :return: Name of the scope. - """ - return "Ignored" if isinstance(scope, IgnoredScope) else "Annotated" - - -def _info_matched_scope(matches: dict[str, set[str]], scope: BaseScope) -> None: +def _info_matched_scope(matches: dict[str, set[str]]) -> None: """ Log matches. :param matches: Matches. - :param scope: Scope the matches are found by. """ - scope_name = _get_scope_name(scope) for rule_type, rules in matches.items(): if rules: - nncf_logger.info(f"{len(rules)} {scope_name} nodes were found by {rule_type} in the NNCFGraph") + nncf_logger.info(f"{len(rules)} nodes were found by {rule_type} in the NNCFGraph") -def _error_unmatched_scope(unmatched_scope: BaseScope) -> str: +def _error_unmatched_scope(unmatched_scope: Scope) -> str: """ Returns an error message for unmatched scope. :param unmatched_scope: Unmatched scope. :return str: Error message. """ - scope_name = _get_scope_name(unmatched_scope) err_msg = "\n" for rule_type in ("names", "types", "patterns"): unmatched_rules = getattr(unmatched_scope, rule_type) if unmatched_rules: - err_msg += ( - f"{scope_name} nodes that matches {rule_type} {unmatched_rules} were not found in the NNCFGraph.\n" - ) + err_msg += f"Nodes that matches {rule_type} {unmatched_rules} were not found in the NNCFGraph.\n" for subgraph in unmatched_scope.subgraphs: err_msg += ( - f"{scope_name} nodes that matches subgraph with input names {subgraph.inputs} " + f"Nodes that matches subgraph with input names {subgraph.inputs} " f"and output names {subgraph.outputs} were not found in the NNCFGraph.\n" ) return err_msg -def _check_scope_strictly_matched(scope: BaseScope, matched_scope: BaseScope) -> None: +def _check_scope_strictly_matched(scope: Scope, matched_scope: Scope) -> None: """ Passes when scope and matched_scope are equal, otherwise - raises ValidationError. @@ -274,7 +247,7 @@ def _check_scope_strictly_matched(scope: BaseScope, matched_scope: BaseScope) -> raise nncf.ValidationError(_error_unmatched_scope(unmatched_scope)) -def get_node_names_from_scope(scope: BaseScope, nncf_graph: NNCFGraph, strict: bool = True) -> set[str]: +def get_node_names_from_scope(scope: Scope, nncf_graph: NNCFGraph, strict: bool = True) -> set[str]: """ Returns matched names according to the scope and NNCFGraph. If strict is True, raises nncf.ValidationError if any rule was not matched. @@ -288,11 +261,11 @@ def get_node_names_from_scope(scope: BaseScope, nncf_graph: NNCFGraph, strict: b matched_scope, matches = get_matched_scope_info(scope, [nncf_graph]) if strict: _check_scope_strictly_matched(scope, matched_scope) - _info_matched_scope(matches, scope) + _info_matched_scope(matches) return {name for match in matches.values() for name in match} -def validate_scope(scope: BaseScope, nncf_graphs: list[NNCFGraph]) -> None: +def validate_scope(scope: Scope, nncf_graphs: list[NNCFGraph]) -> None: """ Passes whether all rules at 'scope' have matches at provided graphs, otherwise - raises ValidationError. diff --git a/tests/cross_fw/test_templates/test_ptq_params.py b/tests/cross_fw/test_templates/test_ptq_params.py index 4993a562d1e..2230daae044 100644 --- a/tests/cross_fw/test_templates/test_ptq_params.py +++ b/tests/cross_fw/test_templates/test_ptq_params.py @@ -444,7 +444,7 @@ def test_validate_scope(self, test_params, validate_scopes): ) algo._backend_entity = self.get_algo_backend() if validate_scopes: - with pytest.raises(nncf.ValidationError, match="Ignored nodes that matches names"): + with pytest.raises(nncf.ValidationError, match="Nodes that matches names"): algo._get_ignored_names(nncf_graph, inference_nncf_graph, ignored_patterns) else: algo._get_ignored_names(nncf_graph, inference_nncf_graph, ignored_patterns) From 7c77c2e4d96dde69eb0b5240da3818ac6d43ed9f Mon Sep 17 00:00:00 2001 From: anzr299 Date: Tue, 29 Sep 2026 13:43:14 +0400 Subject: [PATCH 3/6] raise warning when using ignored scope --- src/nncf/common/scopes.py | 12 +++++----- .../weight_compression/algorithm.py | 7 ++++++ tests/common/test_deprecation_warnings.py | 24 +++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/nncf/common/scopes.py b/src/nncf/common/scopes.py index d2f9a07f4e6..2dc5f490d9e 100644 --- a/src/nncf/common/scopes.py +++ b/src/nncf/common/scopes.py @@ -18,7 +18,7 @@ from nncf.common.graph import NNCFNodeName from nncf.common.logging import nncf_logger from nncf.common.quantization.structs import QuantizerId -from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import convert_ignored_scope_to_list @@ -72,11 +72,11 @@ def should_consider_scope( ) -def get_not_matched_scopes(scope: list[str] | str | IgnoredScope | None, nodes: list[NNCFNode]) -> list[str]: +def get_not_matched_scopes(scope: list[str] | str | Scope | None, nodes: list[NNCFNode]) -> list[str]: """ Return list of scope that do not match node list. - :param scope: List of ignored/target scope or instance of IgnoredScope. + :param scope: List of ignored/target scope or instance of Scope. :param graph: The model graph. :return : List of not matched scopes. @@ -86,7 +86,7 @@ def get_not_matched_scopes(scope: list[str] | str | IgnoredScope | None, nodes: if isinstance(scope, str): patterns = [scope] - elif isinstance(scope, IgnoredScope): + elif isinstance(scope, Scope): patterns = convert_ignored_scope_to_list(scope) else: patterns = list(scope) @@ -104,7 +104,7 @@ def get_not_matched_scopes(scope: list[str] | str | IgnoredScope | None, nodes: def check_scopes_in_graph( graph: NNCFGraph, - ignored_scopes: IgnoredScope | list[str], + ignored_scopes: Scope | list[str], target_scopes: list[str] | None = None, validate_scopes: bool = True, ) -> None: @@ -112,7 +112,7 @@ def check_scopes_in_graph( Raise RuntimeError in case if ignored/target scope names do not match model graph. :param graph: The model graph. - :param ignored_scopes: The instance of IgnoredScope or a list of strings specifying a denylist + :param ignored_scopes: The instance of Scope or a list of strings specifying a denylist for the serializable_id. :param target_scopes: A list of strings specifying an allowlist for the serializable_id. :param validate_scopes: If set to True, then a RuntimeError will be raised if the names of the diff --git a/src/nncf/quantization/algorithms/weight_compression/algorithm.py b/src/nncf/quantization/algorithms/weight_compression/algorithm.py index b0d9f5d41b1..bcf004fee36 100644 --- a/src/nncf/quantization/algorithms/weight_compression/algorithm.py +++ b/src/nncf/quantization/algorithms/weight_compression/algorithm.py @@ -18,6 +18,7 @@ import nncf from nncf import Dataset +from nncf.common.deprecation import warning_deprecated from nncf.common.factory import StatisticsAggregatorFactory from nncf.common.graph.graph import NNCFGraph from nncf.common.graph.graph import NNCFNode @@ -157,6 +158,12 @@ def check_user_compression_configuration( """ Validates the user's weight compression configuration for correctness. """ + if isinstance(ignored_scope, IgnoredScope): + warning_deprecated( + "Usage of `nncf.IgnoredScope` is deprecated and will be removed in a future NNCF version. " + "Use `nncf.Scope` instead." + ) + if mode in INT8_MODES: if (ratio and ratio != 1) or (group_size and group_size != -1): msg = ( diff --git a/tests/common/test_deprecation_warnings.py b/tests/common/test_deprecation_warnings.py index 2cf0b34440e..ba053c13232 100644 --- a/tests/common/test_deprecation_warnings.py +++ b/tests/common/test_deprecation_warnings.py @@ -10,9 +10,12 @@ # limitations under the License. import pytest +import nncf from nncf.common.deprecation import _generate_deprecation_message from nncf.common.deprecation import deprecated from nncf.common.logging.logger import NNCFDeprecationWarning +from nncf.parameters import CompressWeightsMode +from nncf.quantization.algorithms.weight_compression.algorithm import check_user_compression_configuration EXAMPLE_MSG = "foo" START_VERSION = "1.2.3" @@ -53,6 +56,27 @@ def test_warnings_are_shown_for_deprecated_class_instantiation(): DeprecatedClass() +def test_warnings_are_shown_for_deprecated_ignored_scope(): + with pytest.warns(NNCFDeprecationWarning, match="`nncf.IgnoredScope` is deprecated"): + check_user_compression_configuration( + mode=CompressWeightsMode.INT8_ASYM, + subset_size=1, + dataset=None, + ratio=None, + group_size=None, + all_layers=None, + awq=None, + scale_estimation=None, + gptq=None, + lora_correction=None, + ignored_scope=nncf.IgnoredScope(names=["node_name"]), + sensitivity_metric=None, + backup_mode=None, + compression_format=None, + advanced_parameters=None, + ) + + def test_generate_deprecation_message(): ret = _generate_deprecation_message("foo", "text", "1.2.3", "4.5.6") assert ret == "Usage of foo is deprecated starting from NNCF v1.2.3 and will be removed in NNCF v4.5.6.\ntext" From 53e6f0a893ac2f401863be90ee5986a0a21b4a70 Mon Sep 17 00:00:00 2001 From: anzr299 Date: Tue, 29 Sep 2026 14:03:18 +0400 Subject: [PATCH 4/6] change ignored scope to be a child class of scope for now --- .../experimental/torch/sparsify_activations/target_scope.py | 4 ++-- src/nncf/scopes.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/nncf/experimental/torch/sparsify_activations/target_scope.py b/src/nncf/experimental/torch/sparsify_activations/target_scope.py index 103cbc70551..bddb56c37e3 100644 --- a/src/nncf/experimental/torch/sparsify_activations/target_scope.py +++ b/src/nncf/experimental/torch/sparsify_activations/target_scope.py @@ -13,13 +13,13 @@ import nncf from nncf.common.graph.graph import NNCFGraph -from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import get_difference_scope from nncf.scopes import get_matched_scope_info @dataclass -class TargetScope(IgnoredScope): +class TargetScope(Scope): r""" Specifies the target portions in a model graph. diff --git a/src/nncf/scopes.py b/src/nncf/scopes.py index 606d1b0d9d4..7f3a75514b6 100644 --- a/src/nncf/scopes.py +++ b/src/nncf/scopes.py @@ -115,9 +115,11 @@ class Scope: validate: bool = True -# Deprecated alias for :class:`nncf.Scope`, kept for backward compatibility. Use `nncf.Scope` instead. # TODO(anzr299): Remove this alias in future properly. -IgnoredScope = Scope +class IgnoredScope(Scope): + """ + Deprecated alias for :class:`nncf.Scope`, kept for backward compatibility. Use `nncf.Scope` instead. + """ def get_difference_scope(scope_1: Scope, scope_2: Scope) -> Scope: From 54ddb8e7e80a1f55c13ef07984c2b5e274885368 Mon Sep 17 00:00:00 2001 From: anzr299 Date: Tue, 29 Sep 2026 14:15:27 +0400 Subject: [PATCH 5/6] add deprecation decorator for ignored scope class. CHnage internal calls of ignored scope to scope so deprecation warning is not shown --- .../sparsify_activations_impl.py | 3 ++- .../openvino/quantization/quantize_model.py | 3 ++- src/nncf/openvino/rt_info.py | 8 +++---- .../algorithms/min_max/algorithm.py | 3 ++- .../weight_compression/algorithm.py | 10 ++------ src/nncf/scopes.py | 2 ++ tests/common/test_deprecation_warnings.py | 23 +++---------------- tests/common/test_scopes.py | 16 ++++++------- 8 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py b/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py index 17da6016f03..5bb29b267a4 100644 --- a/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py +++ b/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py @@ -27,6 +27,7 @@ from nncf.experimental.torch.sparsify_activations.target_scope import TargetScope from nncf.experimental.torch.sparsify_activations.target_scope import get_target_node_names_from_target_scope from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import get_node_names_from_scope from nncf.torch.model_creation import is_wrapped_model from nncf.torch.model_creation import wrap_model @@ -243,7 +244,7 @@ def sparsify_activations( raise ValueError(msg) if ignored_scope is None: - ignored_scope = IgnoredScope() + ignored_scope = Scope() backend = get_backend(model) if backend == BackendType.TORCH and not is_wrapped_model(model): diff --git a/src/nncf/openvino/quantization/quantize_model.py b/src/nncf/openvino/quantization/quantize_model.py index 0ff7385af60..2ce5f973a88 100644 --- a/src/nncf/openvino/quantization/quantize_model.py +++ b/src/nncf/openvino/quantization/quantize_model.py @@ -55,6 +55,7 @@ from nncf.quantization.statistics_caching import cache_weight_compression_statistics from nncf.quantization.statistics_caching import register_statistics_for_algorithm from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import validate_scope TTensor = TypeVar("TTensor") @@ -97,7 +98,7 @@ def _extract_all_subgraphs(model: ov.Model, current_id: str) -> None: _extract_all_subgraphs(model, main_model_graph_id) if ignored_scope and ignored_scope.validate: validate_scope(ignored_scope, graphs.values()) - ignored_scope = IgnoredScope( + ignored_scope = Scope( ignored_scope.names, ignored_scope.patterns, ignored_scope.types, ignored_scope.subgraphs, validate=False ) quantization_algorithm = PostTrainingQuantization( diff --git a/src/nncf/openvino/rt_info.py b/src/nncf/openvino/rt_info.py index 87582316bbb..7c4600c9058 100644 --- a/src/nncf/openvino/rt_info.py +++ b/src/nncf/openvino/rt_info.py @@ -16,14 +16,14 @@ import nncf from nncf.common.logging import nncf_logger -from nncf.scopes import IgnoredScope +from nncf.scopes import Scope def exclude_empty_fields(value: dict[str, Any]) -> dict[str, Any]: """ Remove keys where value is empty and key is `validate` - :param value: IgnoredScope instance, based on the quantization parameters + :param value: Scope instance, based on the quantization parameters """ value.pop("validate") keys = list(value.keys()) @@ -47,8 +47,8 @@ def dump_parameters( try: path = path if path else [] for key, value in parameters.items(): - # Special condition for composed fields like IgnoredScope - if isinstance(value, IgnoredScope): + # Special condition for composed fields like Scope + if isinstance(value, Scope): value = exclude_empty_fields(asdict(value)) if bool(value): dump_parameters(model, value, algo_name, [key]) diff --git a/src/nncf/quantization/algorithms/min_max/algorithm.py b/src/nncf/quantization/algorithms/min_max/algorithm.py index ebc9644c4c3..0a59878da1c 100644 --- a/src/nncf/quantization/algorithms/min_max/algorithm.py +++ b/src/nncf/quantization/algorithms/min_max/algorithm.py @@ -69,6 +69,7 @@ from nncf.quantization.range_estimator import RangeEstimatorParametersSet from nncf.quantization.range_estimator import StatisticsType from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import get_node_names_from_scope TModel = TypeVar("TModel") @@ -206,7 +207,7 @@ def __init__( self._activations_range_estimator_params = activations_range_estimator_params self._weights_range_estimator_params = weights_range_estimator_params self._preset = preset - self._ignored_scope = IgnoredScope() if ignored_scope is None else ignored_scope + self._ignored_scope = Scope() if ignored_scope is None else ignored_scope self.quantizer_propagation_rule = quantizer_propagation_rule # validate input parameter types diff --git a/src/nncf/quantization/algorithms/weight_compression/algorithm.py b/src/nncf/quantization/algorithms/weight_compression/algorithm.py index bcf004fee36..d09e3907bcf 100644 --- a/src/nncf/quantization/algorithms/weight_compression/algorithm.py +++ b/src/nncf/quantization/algorithms/weight_compression/algorithm.py @@ -18,7 +18,6 @@ import nncf from nncf import Dataset -from nncf.common.deprecation import warning_deprecated from nncf.common.factory import StatisticsAggregatorFactory from nncf.common.graph.graph import NNCFGraph from nncf.common.graph.graph import NNCFNode @@ -52,6 +51,7 @@ from nncf.quantization.algorithms.weight_compression.weight_lowering import WeightCompressionConfig from nncf.quantization.algorithms.weight_compression.weight_lowering import get_reduction_channel_size from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import get_node_names_from_scope from nncf.tensor import Tensor from nncf.tensor import functions as fns @@ -123,7 +123,7 @@ def get_weight_compression_configuration( "scale_estimation": scale_estimation or False, "gptq": gptq or False, "lora_correction": lora_correction or False, - "ignored_scope": ignored_scope or IgnoredScope(), + "ignored_scope": ignored_scope or Scope(), "sensitivity_metric": ( ( SensitivityMetric.WEIGHT_QUANTIZATION_ERROR @@ -158,12 +158,6 @@ def check_user_compression_configuration( """ Validates the user's weight compression configuration for correctness. """ - if isinstance(ignored_scope, IgnoredScope): - warning_deprecated( - "Usage of `nncf.IgnoredScope` is deprecated and will be removed in a future NNCF version. " - "Use `nncf.Scope` instead." - ) - if mode in INT8_MODES: if (ratio and ratio != 1) or (group_size and group_size != -1): msg = ( diff --git a/src/nncf/scopes.py b/src/nncf/scopes.py index 7f3a75514b6..85ed2f1fc4f 100644 --- a/src/nncf/scopes.py +++ b/src/nncf/scopes.py @@ -14,6 +14,7 @@ from dataclasses import field import nncf +from nncf.common.deprecation import deprecated from nncf.common.graph.graph import NNCFGraph from nncf.common.logging import nncf_logger from nncf.common.utils.api_marker import api @@ -116,6 +117,7 @@ class Scope: # TODO(anzr299): Remove this alias in future properly. +@deprecated(msg="Use `nncf.Scope` instead.") class IgnoredScope(Scope): """ Deprecated alias for :class:`nncf.Scope`, kept for backward compatibility. Use `nncf.Scope` instead. diff --git a/tests/common/test_deprecation_warnings.py b/tests/common/test_deprecation_warnings.py index ba053c13232..2383a6d42d2 100644 --- a/tests/common/test_deprecation_warnings.py +++ b/tests/common/test_deprecation_warnings.py @@ -14,8 +14,6 @@ from nncf.common.deprecation import _generate_deprecation_message from nncf.common.deprecation import deprecated from nncf.common.logging.logger import NNCFDeprecationWarning -from nncf.parameters import CompressWeightsMode -from nncf.quantization.algorithms.weight_compression.algorithm import check_user_compression_configuration EXAMPLE_MSG = "foo" START_VERSION = "1.2.3" @@ -57,24 +55,9 @@ def test_warnings_are_shown_for_deprecated_class_instantiation(): def test_warnings_are_shown_for_deprecated_ignored_scope(): - with pytest.warns(NNCFDeprecationWarning, match="`nncf.IgnoredScope` is deprecated"): - check_user_compression_configuration( - mode=CompressWeightsMode.INT8_ASYM, - subset_size=1, - dataset=None, - ratio=None, - group_size=None, - all_layers=None, - awq=None, - scale_estimation=None, - gptq=None, - lora_correction=None, - ignored_scope=nncf.IgnoredScope(names=["node_name"]), - sensitivity_metric=None, - backup_mode=None, - compression_format=None, - advanced_parameters=None, - ) + with pytest.warns(NNCFDeprecationWarning, match="Use `nncf.Scope` instead."): + ignored_scope = nncf.IgnoredScope(names=["node_name"]) + assert isinstance(ignored_scope, nncf.Scope) def test_generate_deprecation_message(): diff --git a/tests/common/test_scopes.py b/tests/common/test_scopes.py index 91ce47bb9dd..3057ec188b3 100644 --- a/tests/common/test_scopes.py +++ b/tests/common/test_scopes.py @@ -12,7 +12,7 @@ from nncf.common.graph import NNCFNode from nncf.common.scopes import get_not_matched_scopes -from nncf.scopes import IgnoredScope +from nncf.scopes import Scope from nncf.scopes import Subgraph from nncf.scopes import get_difference_scope @@ -27,10 +27,10 @@ ([r"{re}\d"], [r"{re}\d"]), ([r"{re}\w"], []), (["A", "B", "{re}.*", "1"], ["1"]), - (IgnoredScope(names=["A", "B"]), []), - (IgnoredScope(names=["1", "2"]), ["1", "2"]), - (IgnoredScope(patterns=[r"\d"]), [r"{re}\d"]), - (IgnoredScope(patterns=[r"\w"]), []), + (Scope(names=["A", "B"]), []), + (Scope(names=["1", "2"]), ["1", "2"]), + (Scope(patterns=[r"\d"]), [r"{re}\d"]), + (Scope(patterns=[r"\w"]), []), ], ) def test_get_not_matched_scopes(scope, ref): @@ -46,7 +46,7 @@ def test_get_not_matched_scopes(scope, ref): "scope_1, scope_2, ref", ( ( - IgnoredScope( + Scope( names=["A_name", "B_name"], patterns=["A_pattern", "B_pattern"], types=["A_type", "B_type"], @@ -55,7 +55,7 @@ def test_get_not_matched_scopes(scope, ref): Subgraph(inputs=["B_input"], outputs=["B_output"]), ], ), - IgnoredScope( + Scope( names=["B_name", "C_name"], patterns=["B_pattern", "C_pattern"], types=["B_type", "C_type"], @@ -64,7 +64,7 @@ def test_get_not_matched_scopes(scope, ref): Subgraph(inputs=["C_input"], outputs=["C_output"]), ], ), - IgnoredScope( + Scope( names=["A_name"], patterns=["A_pattern"], types=["A_type"], From 1e7c573552356c4068cb6a952e3994a4659fb188 Mon Sep 17 00:00:00 2001 From: anzr299 Date: Thu, 8 Oct 2026 16:29:02 +0400 Subject: [PATCH 6/6] remove unused functions --- src/nncf/common/scopes.py | 76 ------------------------------------- tests/common/test_scopes.py | 27 ------------- 2 files changed, 103 deletions(-) diff --git a/src/nncf/common/scopes.py b/src/nncf/common/scopes.py index 2dc5f490d9e..d844c0c3e34 100644 --- a/src/nncf/common/scopes.py +++ b/src/nncf/common/scopes.py @@ -12,14 +12,8 @@ import re from typing import Iterable -import nncf -from nncf.common.graph import NNCFGraph -from nncf.common.graph import NNCFNode from nncf.common.graph import NNCFNodeName -from nncf.common.logging import nncf_logger from nncf.common.quantization.structs import QuantizerId -from nncf.scopes import Scope -from nncf.scopes import convert_ignored_scope_to_list def matches_any(tested_str: str, strs_to_match_to: Iterable[str] | str | None) -> bool: @@ -70,73 +64,3 @@ def should_consider_scope( return (target_scopes is None or matches_any(string_id, target_scopes)) and not matches_any( string_id, ignored_scopes ) - - -def get_not_matched_scopes(scope: list[str] | str | Scope | None, nodes: list[NNCFNode]) -> list[str]: - """ - Return list of scope that do not match node list. - - :param scope: List of ignored/target scope or instance of Scope. - :param graph: The model graph. - - :return : List of not matched scopes. - """ - if scope is None: - return [] - - if isinstance(scope, str): - patterns = [scope] - elif isinstance(scope, Scope): - patterns = convert_ignored_scope_to_list(scope) - else: - patterns = list(scope) - - if not patterns: - return [] - - matched_patterns = set() - for node in nodes: - for pattern in patterns: - if matches_any(node.node_name, pattern): - matched_patterns.add(pattern) - return list(set(patterns) - matched_patterns) - - -def check_scopes_in_graph( - graph: NNCFGraph, - ignored_scopes: Scope | list[str], - target_scopes: list[str] | None = None, - validate_scopes: bool = True, -) -> None: - """ - Raise RuntimeError in case if ignored/target scope names do not match model graph. - - :param graph: The model graph. - :param ignored_scopes: The instance of Scope or a list of strings specifying a denylist - for the serializable_id. - :param target_scopes: A list of strings specifying an allowlist for the serializable_id. - :param validate_scopes: If set to True, then a RuntimeError will be raised if the names of the - ignored/target scopes do not match the names of the scopes in the model graph. - """ - node_list = graph.get_all_nodes() - not_matched_ignored_scopes = get_not_matched_scopes(ignored_scopes, node_list) - not_matched_target_scopes = get_not_matched_scopes(target_scopes, node_list) - - if not_matched_ignored_scopes or not_matched_target_scopes: - err_message = ( - "No match has been found among the model operations for the following ignored/target scope definitions:\n" - ) - if not_matched_ignored_scopes: - err_message += f" - ignored_scope: {not_matched_ignored_scopes}\n" - if not_matched_target_scopes: - err_message += f" - target_scope: {not_matched_target_scopes}\n" - - err_message += ( - "Refer to the original_graph.dot to discover the operations " - "in the model currently visible to NNCF and specify the ignored/target " - "scopes in terms of the names there." - ) - - if validate_scopes: - raise nncf.ValidationError(err_message) - nncf_logger.info(err_message) diff --git a/tests/common/test_scopes.py b/tests/common/test_scopes.py index 3057ec188b3..0402692ca98 100644 --- a/tests/common/test_scopes.py +++ b/tests/common/test_scopes.py @@ -10,38 +10,11 @@ # limitations under the License. import pytest -from nncf.common.graph import NNCFNode -from nncf.common.scopes import get_not_matched_scopes from nncf.scopes import Scope from nncf.scopes import Subgraph from nncf.scopes import get_difference_scope -@pytest.mark.parametrize( - "scope, ref", - [ - ("A", []), - ("1", ["1"]), - (["A", "B"], []), - (["1", "2"], ["1", "2"]), - ([r"{re}\d"], [r"{re}\d"]), - ([r"{re}\w"], []), - (["A", "B", "{re}.*", "1"], ["1"]), - (Scope(names=["A", "B"]), []), - (Scope(names=["1", "2"]), ["1", "2"]), - (Scope(patterns=[r"\d"]), [r"{re}\d"]), - (Scope(patterns=[r"\w"]), []), - ], -) -def test_get_not_matched_scopes(scope, ref): - node_lists = [ - NNCFNode({NNCFNode.ID_NODE_ATTR: 1, NNCFNode.NODE_NAME_ATTR: "A"}), - NNCFNode({NNCFNode.ID_NODE_ATTR: 2, NNCFNode.NODE_NAME_ATTR: "B"}), - ] - not_matched = get_not_matched_scopes(scope, node_lists) - assert not set(not_matched) - set(ref) - - @pytest.mark.parametrize( "scope_1, scope_2, ref", (