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/common/scopes.py b/src/nncf/common/scopes.py index d2f9a07f4e6..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 IgnoredScope -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 | IgnoredScope | 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 graph: The model graph. - - :return : List of not matched scopes. - """ - if scope is None: - return [] - - if isinstance(scope, str): - patterns = [scope] - elif isinstance(scope, IgnoredScope): - 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: IgnoredScope | 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 IgnoredScope 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/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py b/src/nncf/experimental/torch/sparsify_activations/sparsify_activations_impl.py index d63087ceb1a..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,7 +27,8 @@ 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 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 +182,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) @@ -245,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/experimental/torch/sparsify_activations/target_scope.py b/src/nncf/experimental/torch/sparsify_activations/target_scope.py index efc98223dd4..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 get_difference_ignored_scope -from nncf.scopes import get_matched_ignored_scope_info +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. @@ -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..2ce5f973a88 100644 --- a/src/nncf/openvino/quantization/quantize_model.py +++ b/src/nncf/openvino/quantization/quantize_model.py @@ -55,7 +55,8 @@ 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 Scope +from nncf.scopes import validate_scope TTensor = TypeVar("TTensor") @@ -96,8 +97,8 @@ 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()) - ignored_scope = IgnoredScope( + validate_scope(ignored_scope, graphs.values()) + 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 34330dd5b39..0a59878da1c 100644 --- a/src/nncf/quantization/algorithms/min_max/algorithm.py +++ b/src/nncf/quantization/algorithms/min_max/algorithm.py @@ -69,7 +69,8 @@ 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 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 @@ -613,7 +614,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..d09e3907bcf 100644 --- a/src/nncf/quantization/algorithms/weight_compression/algorithm.py +++ b/src/nncf/quantization/algorithms/weight_compression/algorithm.py @@ -51,7 +51,8 @@ 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 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 @@ -122,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 @@ -506,9 +507,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 +857,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..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 @@ -36,38 +37,35 @@ 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)) -@api(canonical_alias="nncf.IgnoredScope") +@api(canonical_alias="nncf.Scope") @dataclass -class IgnoredScope: +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: @@ -75,37 +73,40 @@ class IgnoredScope: 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) @@ -115,26 +116,34 @@ class IgnoredScope: validate: bool = True -def get_difference_ignored_scope(ignored_scope_1: IgnoredScope, ignored_scope_2: IgnoredScope) -> IgnoredScope: +# 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. + """ + + +def get_difference_scope(scope_1: Scope, scope_2: Scope) -> Scope: """ - 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'. - :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, + 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)), + subgraphs=[subgraph for subgraph in scope_1.subgraphs if subgraph not in scope_2.subgraphs], + validate=scope_1.validate, ) -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. @@ -152,56 +161,54 @@ 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: Scope, nncf_graphs: list[NNCFGraph]) -> tuple[Scope, 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. 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( + matched_scope = Scope( 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 _info_matched_scope(matches: dict[str, set[str]]) -> None: """ Log matches. @@ -209,72 +216,65 @@ def _info_matched_ignored_scope(matches: dict[str, set[str]]) -> None: """ 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)} 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: Scope) -> 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. """ 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"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"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: Scope, matched_scope: Scope) -> 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: Scope, 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) 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: Scope, 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_deprecation_warnings.py b/tests/common/test_deprecation_warnings.py index 2cf0b34440e..2383a6d42d2 100644 --- a/tests/common/test_deprecation_warnings.py +++ b/tests/common/test_deprecation_warnings.py @@ -10,6 +10,7 @@ # 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 @@ -53,6 +54,12 @@ def test_warnings_are_shown_for_deprecated_class_instantiation(): DeprecatedClass() +def test_warnings_are_shown_for_deprecated_ignored_scope(): + 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(): 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" 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..0402692ca98 100644 --- a/tests/common/test_scopes.py +++ b/tests/common/test_scopes.py @@ -10,43 +10,16 @@ # 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 IgnoredScope +from nncf.scopes import Scope from nncf.scopes import Subgraph -from nncf.scopes import get_difference_ignored_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"]), - (IgnoredScope(names=["A", "B"]), []), - (IgnoredScope(names=["1", "2"]), ["1", "2"]), - (IgnoredScope(patterns=[r"\d"]), [r"{re}\d"]), - (IgnoredScope(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) +from nncf.scopes import get_difference_scope @pytest.mark.parametrize( "scope_1, scope_2, ref", ( ( - IgnoredScope( + Scope( names=["A_name", "B_name"], patterns=["A_pattern", "B_pattern"], types=["A_type", "B_type"], @@ -55,7 +28,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 +37,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"], @@ -74,4 +47,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 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)