def _is_module_prunable(self, graph: NNCFGraph, node: NNCFNode) -> PruningAnalysisDecision: """ Check whether we should prune module corresponding to provided node according to algorithm parameters. :param graph: Graph to work with. :param node: Node to check. :return: Pruning analysis decision. """ stop_propagation_ops = self._stop_propagation_op_metatype.get_all_op_aliases( ) types_to_track = self._prune_operations_types + stop_propagation_ops input_non_pruned_nodes = get_first_nodes_of_type(graph, types_to_track) node_name = node.node_name if not should_consider_scope(node_name, self._ignored_scopes, self._target_scopes): return PruningAnalysisDecision(False, PruningAnalysisReason.IGNORED_SCOPE) if not self._prune_first and node in input_non_pruned_nodes: return PruningAnalysisDecision(False, PruningAnalysisReason.FIRST_CONV) if is_grouped_conv(node) and not is_prunable_depthwise_conv(node): return PruningAnalysisDecision(False, PruningAnalysisReason.GROUP_CONV) if not self._prune_downsample_convs and is_conv_with_downsampling( node): return PruningAnalysisDecision( False, PruningAnalysisReason.DOWNSAMPLE_CONV) return PruningAnalysisDecision(True)
def mask_propagation(cls, node: NNCFNode, graph: NNCFGraph): input_masks = get_input_masks(node, graph) output_mask = node.data.get('output_mask', None) if is_grouped_conv(node): output_mask = None if is_depthwise_conv(node): output_mask = input_masks[0] node.data['output_mask'] = output_mask
def mask_propagation(cls, node: NNCFNode, graph: NNCFGraph): input_masks = get_input_masks(node, graph) output_mask = node.data.get('output_mask', None) # In case of group convs we can't prune by output filters if is_grouped_conv(node): output_mask = None if is_depthwise_conv(node): output_mask = input_masks[0] node.data['output_mask'] = output_mask
def mask_propagation( cls, node: NNCFNode, graph: NNCFGraph, tensor_processor: Type[NNCFPruningBaseTensorProcessor]) -> None: input_masks = get_input_masks(node, graph) output_mask = node.data.get('output_mask', None) if is_grouped_conv(node): output_mask = None if is_prunable_depthwise_conv(node): output_mask = input_masks[0] node.data['output_mask'] = output_mask
def accept_pruned_input(cls, node: NNCFNode): accept_pruned_input = True if is_grouped_conv(node): if not is_depthwise_conv(node): accept_pruned_input = False return accept_pruned_input
def symbolic_mask_propagation(self, prunable_layers_types: List[str], can_prune_after_analysis: Dict[int, PruningAnalysisDecision]) \ -> Dict[int, PruningAnalysisDecision]: """ Check all nodes that were marked as prunable after the model analysis and compatibility check vs. pruning algo have a correct correspondent closing node on each path from self to outputs; the check entails verifying that every convolution prunable by the output channel dimension has a corresponding convolution that is prunable by its input channel dimension (output channel dimension equal to closing convolution input channel dimension) in every path from self to outputs. If the check fails, the entire groups containing such nodes will be marked as unprunable. If convolution symbolic mask mixes with other symbolic masks (by elementwise operation, for example) and mixing masks can't be mixed, all mask producers participated in this mixing will be marked as unprunable. If convolution output channel dimension reducing directly affect an output of the model - it will be marked as unprunable as well. :param prunable_layers_types: Types of operations with prunable filters. :param can_prune_after_analysis: Dict of node indices vs the decision made by previous steps; the decision is true only for the nodes that do not conflict with mask propagation and are supported by the NNCF pruning algorithm. :return: Dict of node indices vs the decision made by symbolic mask propagation algorithm. """ can_be_closing_convs = { node.node_id for node in self._graph.get_all_nodes() if node.node_type in prunable_layers_types and not is_grouped_conv(node) } can_prune_by_dim = {k: None for k in can_be_closing_convs} for node in self._graph.topological_sort(): if node.node_id in can_be_closing_convs and can_prune_after_analysis[ node.node_id]: # Set output mask node.data['output_mask'] = SymbolicMask( node.layer_attributes.out_channels, [node.node_id]) # Propagate masks cls = self.get_meta_operation_by_type_name(node.node_type) cls.mask_propagation(node, self._graph, SymbolicMaskProcessor) if node.node_id in can_be_closing_convs: # Check input mask producers out channel dimension input_masks = get_input_masks(node, self._graph) if any(input_masks): assert len(input_masks) == 1 input_mask = input_masks[0] for producer in input_mask.mask_producers: previously_dims_equal = True if can_prune_by_dim[producer] is None \ else can_prune_by_dim[producer] is_dims_equal = node.layer_attributes.in_channels == input_mask.shape[ 0] decision = previously_dims_equal and is_dims_equal can_prune_by_dim[producer] = PruningAnalysisDecision( decision, PruningAnalysisReason.DIMENSION_MISMATCH) # Remove all convolutions with masks # that were propagated to output node for out_node in self._graph.get_output_nodes(): for input_mask in get_input_masks(out_node, self._graph): if input_mask: for producer in input_mask.mask_producers: can_prune_by_dim[producer] = PruningAnalysisDecision( False, PruningAnalysisReason.LAST_CONV) # Update decision for nodes which # have no closing convolution convs_without_closing_conv = {} for k, v in can_prune_by_dim.items(): if v is None: convs_without_closing_conv[k] = \ PruningAnalysisDecision(False, PruningAnalysisReason.CLOSING_CONV_MISSING) can_prune_by_dim.update(convs_without_closing_conv) # Clean nodes masks for node in self._graph.get_all_nodes(): node.data['output_mask'] = None return can_prune_by_dim
def accept_pruned_input(cls, node: NNCFNode) -> bool: if is_grouped_conv(node) and not is_prunable_depthwise_conv(node): return False return True