Skip to content

Commit

Permalink
Introduce an ignored subgraph (#2548)
Browse files Browse the repository at this point in the history
### Changes

Introduce an ability to define subgraph in ignored scope

### Reason for changes

The models get more complicated in terms of structure. Noticed many
times, that it's impossible to exclude some parts of the model from
quantization using the existing ignore scope functionality.

### Related tickets

Ref: 100999

### Tests

tests/common/test_ignored_scope.py
  • Loading branch information
andrey-churkin authored Mar 8, 2024
1 parent 7f0c73c commit de46194
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 1 deletion.
1 change: 1 addition & 0 deletions nncf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
)
from nncf.quantization.advanced_parameters import AdvancedQuantizationParameters as AdvancedQuantizationParameters
from nncf.scopes import IgnoredScope as IgnoredScope
from nncf.scopes import Subgraph as Subgraph
from nncf.version import __version__ as __version__

_SUPPORTED_FRAMEWORKS = ["torch", "tensorflow", "onnx", "openvino"]
Expand Down
53 changes: 52 additions & 1 deletion nncf/scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,42 @@
from nncf.common.utils.api_marker import api


@api(canonical_alias="nncf.Subgraph")
@dataclass
class Subgraph:
"""
Defines the ignored subgraph as follows: A subgraph comprises all nodes along
all simple paths in the graph from input to output nodes.
:param inputs: Input node names.
:type inputs: List[str]
:param outputs: Output node names.
:type outputs: List[str]
"""

inputs: List[str] = field(default_factory=list)
outputs: List[str] = field(default_factory=list)


def get_ignored_node_names_from_subgraph(graph: NNCFGraph, subgraph: Subgraph) -> List[str]:
"""
Returns all names that should be ignored according to given subgraph.
:param graph: Given NNCFGraph.
:param subgraph: Given subgraph instance.
:return: All names that should be ignored according to given subgraph.
"""
ignored_names = set()
for start_node_name in subgraph.inputs:
for end_node_name in subgraph.outputs:
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)

return list(sorted(ignored_names))


@api(canonical_alias="nncf.IgnoredScope")
@dataclass
class IgnoredScope:
Expand Down Expand Up @@ -61,6 +97,8 @@ class IgnoredScope:
:type patterns: List[str]
:param types: List of ignored operation types.
:type types: List[str]
:param subgraphs: List of ignored subgraphs.
:type subgraphs: List[Subgraph]
:param validate: If set to True, then a RuntimeError will be raised if any ignored scope does not match
in the model graph.
:type types: bool
Expand All @@ -69,6 +107,7 @@ class IgnoredScope:
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


Expand Down Expand Up @@ -153,4 +192,16 @@ def get_ignored_node_names_from_ignored_scope(
)
nncf_logger.info(f"{len(matched_by_types)} ignored nodes were found by types in the NNCFGraph")

return set(matched_by_names + matched_by_types + matched_by_patterns)
matched_by_subgraphs = []
if ignored_scope.subgraphs:
for subgraph in ignored_scope.subgraphs:
names_from_subgraph = get_ignored_node_names_from_subgraph(nncf_graph, subgraph)
if strict and not names_from_subgraph:
raise nncf.ValidationError(
f"Ignored subgraph with input names {subgraph.inputs} and output names {subgraph.outputs} "
"was not found in the NNCFGraph. " + error_msg
)

matched_by_subgraphs.extend(names_from_subgraph)

return set(matched_by_names + matched_by_types + matched_by_patterns + matched_by_subgraphs)
7 changes: 7 additions & 0 deletions tests/common/test_ignored_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from nncf.common.graph.operator_metatypes import InputNoopMetatype
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 tests.common.quantization.metatypes import Conv2dTestMetatype
from tests.common.quantization.metatypes import LinearTestMetatype
Expand Down Expand Up @@ -59,6 +60,10 @@ def __init__(self, conv_metatype, linear_metatype):
IgnoredScope(["/Conv_1_0", "/Linear_1_0"], [".*Marked.*"], [LINEAR_TYPE]),
["/Conv_1_0", "/Linear_1_0", "/Linear_2_0", "/Linear_3_0", "/Marked_Conv_3_0"],
),
(
IgnoredScope(subgraphs=[Subgraph(inputs=["/Linear_1_0"], outputs=["/Linear_3_0"])]),
["/Conv_2_0", "/Linear_1_0", "/Linear_2_0", "/Linear_3_0", "/Marked_Conv_3_0"],
),
]


Expand All @@ -73,6 +78,8 @@ def test_ignored_scopes(ignored_scope, ref_ignored_names):
IgnoredScope(["/Conv_0_0", "/Conv_1_0", "/Linear_1_0"]),
IgnoredScope(patterns=[".*Maarked.*"]),
IgnoredScope(types=["wrong_type"]),
IgnoredScope(subgraphs=[Subgraph(inputs=["/Linear_1_0"], outputs=["/Linear_1_0"])]),
IgnoredScope(subgraphs=[Subgraph(inputs=["/Linear_3_0"], outputs=["/Linear_1_0"])]),
]


Expand Down

0 comments on commit de46194

Please sign in to comment.