Exemple #1
0
def find_next_nodes_not_of_types(graph: NNCFGraph, nncf_node: NNCFNode,
                                 types: List[str]) -> List[NNCFNode]:
    """
    Traverse nodes in the graph from nncf node to find first nodes that aren't of type from types list.
    First nodes with some condition mean nodes:
        - for which this condition is true
        - reachable from `nncf_node` such that on the path from `nncf_node` to
          this nodes there are no other nodes with fulfilled condition

    :param graph: Graph to work with.
    :param nncf_node: NNCFNode to start search.
    :param types: List of types.
    :return: List of next nodes for nncf_node of type not from types list.
    """
    visited = {node_id: False for node_id in graph.get_all_node_ids()}
    partial_traverse_function = partial(traverse_function,
                                        type_check_fn=lambda x: x not in types,
                                        visited=visited)
    nncf_nodes = [nncf_node]
    if nncf_node.node_type not in types:
        nncf_nodes = graph.get_next_nodes(nncf_node)

    next_nodes = []
    for node in nncf_nodes:
        next_nodes.extend(graph.traverse_graph(node,
                                               partial_traverse_function))
    return next_nodes
Exemple #2
0
def get_next_nodes_of_types(graph: NNCFGraph, nncf_node: NNCFNode,
                            types: List[str]) -> List[NNCFNode]:
    """
    Looking for nodes with type from types list from `nncf_node` such that there is path
    from `nncf_node` to this node and on this path no node has one of types type.

    :param graph: Graph to work with.
    :param nncf_node: NNCFNode to start search.
    :param types: List of types to find.
    :return: List of next nodes of nncf_node with type from types list.
    """
    sources_types = types
    visited = {node_id: False for node_id in graph.get_all_node_ids()}
    partial_traverse_function = partial(
        traverse_function,
        type_check_fn=lambda x: x in sources_types,
        visited=visited)
    nncf_nodes = [nncf_node]
    if nncf_node.node_type in sources_types:
        nncf_nodes = graph.get_next_nodes(nncf_node)

    next_nodes = []
    for node in nncf_nodes:
        next_nodes.extend(graph.traverse_graph(node,
                                               partial_traverse_function))
    return next_nodes
Exemple #3
0
def get_sources_of_node(nncf_node: NNCFNode, graph: NNCFGraph,
                        sources_types: List[str]) -> List[NNCFNode]:
    """
    Source is a node of source such that there is path from this node to `nncf_node` and on this path
    no node has one of `sources_types` type.

    :param sources_types: List of sources types.
    :param nncf_node: NNCFNode to get sources.
    :param graph: NNCF graph to work with.
    :return: List of all sources nodes.
    """
    visited = {node_id: False for node_id in graph.get_all_node_ids()}
    partial_traverse_function = partial(
        traverse_function,
        type_check_fn=lambda x: x in sources_types,
        visited=visited)
    nncf_nodes = [nncf_node]
    if nncf_node.node_type in sources_types:
        nncf_nodes = graph.get_previous_nodes(nncf_node)

    source_nodes = []
    for node in nncf_nodes:
        source_nodes.extend(
            graph.traverse_graph(node, partial_traverse_function, False))
    return source_nodes
Exemple #4
0
    def _get_input_preprocessing_nodes(
            self, nncf_graph: NNCFGraph,
            model: tf.keras.Model) -> List[NNCFNode]:
        retval = []

        def traverse_fn(
            node: NNCFNode, preprocessing_nodes: List[NNCFNode]
        ) -> Tuple[bool, List[NNCFNode]]:
            is_finished = True
            successors = nncf_graph.get_next_nodes(node)
            if len(successors) == 1:
                successor = next(iter(successors))
                # It is necessary to determine the number of input nodes from the model
                # in order to correctly count the duplicated edges
                original_name, _ = get_original_name_and_instance_idx(
                    successor.node_name)
                layer = model.get_layer(name=original_name)
                num_previous_nodes = len(layer.input) if isinstance(
                    layer.input, list) else 1
                if successor.metatype in ELEMENTWISE_LAYER_METATYPES and num_previous_nodes == 1:
                    preprocessing_nodes.append(successor)
                    is_finished = False
            return is_finished, preprocessing_nodes

        for nncf_node in nncf_graph.get_input_nodes():
            preprocessing_nodes_for_this_input = nncf_graph.traverse_graph(
                nncf_node, traverse_fn)
            retval += preprocessing_nodes_for_this_input

        return retval
Exemple #5
0
    def _get_quantized_nodes_for_output(
            self,
            nncf_graph: NNCFGraph,
            insertion_points: List[str],
            node_key: str,
            quantized_nodes_for_output: List[NNCFNode] = None
    ) -> List[NNCFNode]:
        nncf_node = nncf_graph.get_node_by_key(node_key)
        if quantized_nodes_for_output is None:
            if node_key in insertion_points:
                return [nncf_node]
            quantized_nodes_for_output = []

        for predecessor in nncf_graph.get_previous_nodes(nncf_node):
            pred_node_key = nncf_graph.get_node_key_by_id(predecessor.node_id)
            if len(nncf_graph.get_next_nodes(predecessor)) > 1:
                logger.warning(
                    'Removing of FakeQuantize after layer {} '
                    'with multiple outputs is not fully supported'.format(
                        predecessor.node_name))
            if predecessor.metatype in LAYER_METATYPES_AGNOSTIC_TO_DATA_PRECISION:
                self._get_quantized_nodes_for_output(
                    nncf_graph, insertion_points, pred_node_key,
                    quantized_nodes_for_output)
            elif nncf_graph.get_node_key_by_id(
                    predecessor.node_id) in insertion_points:
                quantized_nodes_for_output.append(predecessor)
        return quantized_nodes_for_output
Exemple #6
0
    def _get_default_pre_hook_ip_list(
            nncf_graph: NNCFGraph) -> List[PreHookInsertionPoint]:
        # Pre-hook all input ports of all nodes
        allowed_pre_hook_insertion_points = []
        for nncf_node in nncf_graph.get_all_nodes():
            pred_nodes = nncf_graph.get_previous_nodes(nncf_node)

            for pred_node in pred_nodes:
                input_edge = nncf_graph.get_edge(pred_node, nncf_node)
                allowed_pre_hook_insertion_points.append(
                    PreHookInsertionPoint(nncf_node.node_name,
                                          input_edge.input_port_id))
        return allowed_pre_hook_insertion_points
Exemple #7
0
def init_output_masks_in_graph(graph: NNCFGraph, nodes: List):
    """
    Initialize masks in groph for mask propagation algorithm

    :param graph: NNCFNetwork
    :param nodes: list with pruned nodes
    """
    for node in graph.get_all_nodes():
        node.data.pop('input_masks', None)
        node.data.pop('output_mask', None)

    for minfo in nodes:
        mask = minfo.operand.binary_filter_pruning_mask
        nncf_node = graph.get_node_by_id(minfo.nncf_node_id)
        nncf_node.data['output_mask'] = PTNNCFTensor(mask)
Exemple #8
0
def get_cluster_next_nodes(
        graph: NNCFGraph,
        pruned_groups_info: Clusterization[PrunedLayerInfoBase],
        prunable_types: List[str]) -> Dict[int, List[NNCFNodeName]]:
    """
    Finds nodes of `prunable_types` types that receive the output of a pruned cluster as input.

    :param graph: NNCFGraph.
    :param pruned_groups_info: `Clusterization` of pruning groups.
    :param prunable_types: Types of nodes that will be returned.
    :return Dictionary of next node names by cluster {cluster_id: [node_name]}.
    """
    next_nodes = {}
    for cluster in pruned_groups_info.get_all_clusters():
        next_nodes_cluster = set()
        cluster_nodes = set()
        for pruned_layer_info in cluster.elements:
            nncf_cluster_node = graph.get_node_by_id(
                pruned_layer_info.nncf_node_id)
            cluster_nodes.add(nncf_cluster_node.node_name)
            curr_next_nodes = get_next_nodes_of_types(graph, nncf_cluster_node,
                                                      prunable_types)

            next_nodes_idxs = [n.node_name for n in curr_next_nodes]
            next_nodes_cluster = next_nodes_cluster.union(next_nodes_idxs)
        next_nodes[cluster.id] = list(next_nodes_cluster - cluster_nodes)
    return next_nodes
Exemple #9
0
    def _get_quantizable_weighted_layer_nodes(
            self, nncf_graph: NNCFGraph) -> List[QuantizableWeightedLayerNode]:
        nodes_with_weights = []
        for node in nncf_graph.get_all_nodes():
            metatype = node.metatype
            if metatype in OUTPUT_NOOP_METATYPES:
                continue

            if not (metatype in QUANTIZATION_LAYER_METATYPES
                    and should_consider_scope(
                        node.node_name,
                        ignored_scopes=self.ignored_scopes_per_group[
                            QuantizerGroup.WEIGHTS],
                        target_scopes=None)):
                continue

            assert issubclass(metatype, TFLayerWithWeightsMetatype)
            nodes_with_weights.append(node)
        scope_overrides_dict = self._get_algo_specific_config_section().get(
            'scope_overrides', {})
        weighted_node_and_qconf_lists = assign_qconfig_lists_to_modules(
            nodes_with_weights,
            self.DEFAULT_QCONFIG,
            self.global_quantizer_constraints[QuantizerGroup.WEIGHTS],
            scope_overrides_dict,
            hw_config=self.hw_config)
        return [
            QuantizableWeightedLayerNode(node, qconf_list)
            for node, qconf_list in weighted_node_and_qconf_lists.items()
        ]
Exemple #10
0
    def generate_output_mask(
        cls, node: NNCFNode, graph: NNCFGraph,
        tensor_processor: Type[NNCFPruningBaseTensorProcessor]
    ) -> Optional[NNCFTensor]:
        """
        Generate output mask from input masks with all None replaced by identity masks.
        If all input masks is None return None.

        :param node: Node to determine it's sources.
        :param graph: NNCF graph to work with.
        :param tensor_processor: Interface with tensor processing methods.
        :return: Filled input masks.
        """
        input_edges = graph.get_input_edges(node)
        previous_nodes = [edge.from_node for edge in input_edges]
        input_masks = [
            input_node.data['output_mask'] for input_node in previous_nodes
        ]

        not_empty_masks = [mask for mask in input_masks
                           if mask is not None]  # type: List[NNCFTensor]
        if not not_empty_masks:
            return None

        first_non_empty_mask = not_empty_masks[0]
        device = first_non_empty_mask.device
        filled_input_masks = []
        for i, mask in enumerate(input_masks):
            if mask is None:
                concat_axis = node.layer_attributes.axis
                concat_dim = input_edges[i].tensor_shape[concat_axis]
                mask = tensor_processor.ones(concat_dim, device)
            filled_input_masks.append(mask)
        result_mask = tensor_processor.concatenate(filled_input_masks, 0)
        return result_mask
Exemple #11
0
def get_bn_node_for_conv(graph: NNCFGraph,
                         conv_node: NNCFNode) -> Optional[NNCFNode]:
    successors = graph.get_next_nodes(conv_node)
    for succ in successors:
        if succ.node_type == 'batch_norm':
            return succ
    return None
Exemple #12
0
 def _get_default_post_hook_ip_list(
         nncf_graph: NNCFGraph) -> List[PostHookInsertionPoint]:
     # Post-hook all nodes, post hook applies to the entire op output
     allowed_post_hook_insertion_points = []
     for nncf_node in nncf_graph.get_all_nodes():
         allowed_post_hook_insertion_points.append(
             PostHookInsertionPoint(nncf_node.node_name))
     return allowed_post_hook_insertion_points
Exemple #13
0
 def _get_related_batchnorms(self, layer_name: str, group: Cluster, graph: NNCFGraph) -> List[NNCFNode]:
     """
     Returns List of batchnorm elements related to the layer.
     Note: Single node per layer for shared bactchnorm layers
     """
     layer_nodes = [node_ for node_ in group.elements
                    if node_.layer_name == layer_name]
     bn_nodes = []
     bn_layer_names = []
     for layer_node in layer_nodes:
         for next_node in graph.get_next_nodes(layer_node):
             for bn_node in graph.traverse_graph(next_node, self._get_bn_for_node):
                 bn_layer_name = get_layer_identifier(bn_node)
                 if bn_layer_name not in bn_layer_names:
                     bn_layer_names.append(bn_layer_name)
                     bn_nodes.append(bn_node)
     return bn_nodes
Exemple #14
0
 def _get_custom_layer_node_names(
         self, nncf_graph: NNCFGraph,
         converter: TFModelConverter) -> List[NNCFNodeName]:
     retval = []
     for node in nncf_graph.get_all_nodes():
         metatype = node.metatype
         if metatype in OUTPUT_NOOP_METATYPES:
             continue
         is_custom, _ = converter.get_layer_info_for_node(node.node_name)
         if is_custom:
             retval.append(node.node_name)
     return retval
Exemple #15
0
def get_first_nodes_of_type(graph: NNCFGraph, op_types: List[str]) -> List[NNCFNode]:
    """
    Looking for first node in graph with type in `op_types`.
    First == layer with type in `op_types`, that there is a path from the input such that there are no other
    operations with type in `op_types` on it.

    :param op_types: Types of modules to track.
    :param graph: Graph to work with.
    :return: List of all first nodes with type in `op_types`.
    """
    graph_roots = graph.get_input_nodes()  # NNCFNodes here

    visited = {node_id: False for node_id in graph.get_all_node_ids()}
    partial_traverse_function = partial(traverse_function,
                                        type_check_fn=lambda x: x in op_types,
                                        visited=visited)

    first_nodes_of_type = []
    for root in graph_roots:
        first_nodes_of_type.extend(graph.traverse_graph(root, partial_traverse_function))
    return first_nodes_of_type
Exemple #16
0
def cluster_special_ops(graph: NNCFGraph, special_types: List[str],
                        identity_types: List[str]) -> Clusterization[NNCFNode]:
    """
    This model will cluster all operations with type from special_types. Connected nodes is nodes that:
        1. Have path between nodes with only identity type nodes on it
        2. Have common input (identity type nodes can be on path from this input)

    :param graph: Graph to work with.
    :param special_types: List of types that should be grouped to groups of dependent nodes.
    :return: Clusterization of `special_types` nodes to the dependent groups.
    """
    topologically_sorted_nodes = graph.topological_sort()
    all_special_nodes = [
        node for node in graph.get_all_nodes()
        if node.node_type in special_types
    ]

    # 0. Initially all nodes is a separate clusters
    clusterization = Clusterization[NNCFNode](lambda x: x.node_id)
    for i, node in enumerate(all_special_nodes):
        cluster = Cluster[NNCFNode](
            i, [node],
            [get_position(topologically_sorted_nodes, node.node_id)])
        clusterization.add_cluster(cluster)

    for node in topologically_sorted_nodes:
        if node.node_type in identity_types:
            continue

        all_outputs = find_next_nodes_not_of_types(graph, node, identity_types)
        all_output_special_nodes = [
            node for node in all_outputs if node.node_type in special_types
        ]
        if node.node_type in special_types:
            all_output_special_nodes.append(node)
        merge_clusters_for_nodes(all_output_special_nodes, clusterization)

    return clusterization
Exemple #17
0
def get_input_masks(node: NNCFNode,
                    graph: NNCFGraph) -> List[Optional[NNCFTensor]]:
    """
    Returns input masks for all inputs of given NNCFNode.

    :param node: Given NNCFNode.
    :param graph: Graph to work with.
    :return: Input masks.
    """
    input_masks = [
        input_node.data['output_mask']
        for input_node in graph.get_previous_nodes(node)
    ]
    return input_masks
Exemple #18
0
 def _handle_quantize_inputs_option(
         self, quantizer_setup: SingleConfigQuantizerSetup,
         nncf_graph: NNCFGraph) -> SingleConfigQuantizerSetup:
     qp_ids_to_discard = []
     for qp_id, qp in quantizer_setup.quantization_points.items():
         if qp.is_activation_quantization_point():
             insertion_point = qp.insertion_point
             target_node = nncf_graph.get_node_by_name(
                 insertion_point.target_node_name)
             if not self.quantize_inputs and target_node.metatype in INPUT_NOOP_METATYPES:
                 qp_ids_to_discard.append(qp_id)
     for qp_id in qp_ids_to_discard:
         quantizer_setup.discard(qp_id, keep_shared_input_qps=True)
     return quantizer_setup
Exemple #19
0
def get_last_nodes_of_type(graph: NNCFGraph,
                           op_types: List[str]) -> List[NNCFNode]:
    """
    Looking for last node in graph with type in `op_types`.
    Last == layer with type in `op_types`, that there is a path from this layer to the model output
    such that there are no other operations with type in `op_types` on it.

    :param op_types: Types of modules to track.
    :param graph: Graph to work with.
    :return: List of all last pruned nodes.
    """
    graph_outputs = graph.get_output_nodes()  # NNCFNodes here

    visited = {node_id: False for node_id in graph.get_all_node_ids()}
    partial_traverse_function = partial(traverse_function,
                                        type_check_fn=lambda x: x in op_types,
                                        visited=visited)
    last_nodes_of_type = []
    for output in graph_outputs:
        last_nodes_of_type.extend(
            graph.traverse_graph(output, partial_traverse_function, False))

    return last_nodes_of_type
def get_ip_graph_for_test(nncf_graph: NNCFGraph,
                          weighted_node_names: List[NNCFNodeName] = None) -> InsertionPointGraph:
    pre_hooks = []
    post_hooks = []
    for node in nncf_graph.get_all_nodes():
        in_edges = nncf_graph.get_input_edges(node)
        for in_edge in in_edges:
            ip = PreHookInsertionPoint(node.node_name, in_edge.input_port_id)
            pre_hooks.append(ip)

        if issubclass(node.metatype, PTSplitMetatype):
            continue
        ip = PostHookInsertionPoint(node.node_name)
        post_hooks.append(ip)

    weighted_target_points = None
    if weighted_node_names is not None:
        weighted_target_points = []
        for name in weighted_node_names:
            weighted_target_points.append(name)
    ip_graph = InsertionPointGraph(nncf_graph, weight_modifiable_node_names=weighted_target_points,
                                   allowed_pre_hook_insertion_points=pre_hooks,
                                   allowed_post_hook_insertion_points=post_hooks)
    return ip_graph
Exemple #21
0
 def _add_custom_layer_subgraph(self, nncf_graph: NNCFGraph,
                                custom_layer_name: str) -> NNCFGraph:
     # TODO (vshampor): filter meaningless ops such as Identity, resource read etc.
     custom_layer_info = self._custom_layer_infos[custom_layer_name]
     node_name_vs_nncf_node_ids = {}  # type: Dict[NNCFNodeName, int]
     for node_info in custom_layer_info.node_infos.values():
         weight_node_name = node_info.weight_node_name
         is_shared = False
         if weight_node_name is not None:
             shared_node_dict = custom_layer_info.shared_weight_node_names_vs_weighted_op_node_names
             is_shared = len(shared_node_dict[weight_node_name]) > 1
         nncf_node = nncf_graph.add_nncf_node(
             node_name=node_info.target_node_name,
             node_type=node_info.node_type,
             node_metatype=node_info.node_metatype,
             # TODO (vshampor): collect layer attributes for custom nodes
             layer_attributes=None,
             layer_name=node_info.weight_node_name,  # sic!
             is_shared=is_shared,
             ignored_algorithms=['quantization'])
         node_name_vs_nncf_node_ids[
             node_info.target_node_name] = nncf_node.node_id
         self._nncf_node_names_vs_custom_layer_name[
             node_info.target_node_name] = custom_layer_name
     for edge, edge_data in custom_layer_info.edge_infos.items():
         from_node_name, to_node_name = edge
         from_node_id = node_name_vs_nncf_node_ids[from_node_name]
         to_node_id = node_name_vs_nncf_node_ids[to_node_name]
         nncf_graph.add_edge_between_nncf_nodes(
             from_node_id,
             to_node_id,
             tensor_shape=edge_data.tensor_shape,
             input_port_id=edge_data.input_port_id,
             output_port_id=edge_data.output_port_id,
             dtype=edge_data.dtype)
     return nncf_graph
    def _paint_activation_quantizer_node(self, nncf_graph: NNCFGraph,
                                         quantizer_id: NonWeightQuantizerId,
                                         quantizer_info: NonWeightQuantizerInfo,
                                         bitwidth_color_map: Dict[int, str],
                                         groups_of_adjacent_quantizers: GroupsOfAdjacentQuantizers):
        # pylint:disable=too-many-branches
        affected_insertion_points_list = quantizer_info.affected_insertions

        for target_point in affected_insertion_points_list:
            nncf_node_name = target_point.target_node_name
            nncf_node = nncf_graph.get_node_by_name(nncf_node_name)
            node_id = nncf_node.node_id

            input_port_id = target_point.input_port_id

            if input_port_id is None:
                # Post-hooking used for activation quantization
                # Currently only a single post-hook can immediately follow an operation
                succs = list(nncf_graph.get_next_nodes(nncf_node))
                assert len(succs) == 1
                target_nncf_node_key = nncf_graph.get_node_key_by_id(succs[0].node_id)
            else:
                # Pre-hooking used for activation quantization
                previous_nodes = nncf_graph.get_previous_nodes(nncf_node)
                target_node = None
                for prev_node in previous_nodes:
                    prev_edge = nncf_graph.get_nx_edge(prev_node, nncf_node)
                    if prev_edge[NNCFGraph.INPUT_PORT_ID_EDGE_ATTR] == input_port_id:
                        target_node = prev_node
                        break

                assert target_node is not None, "Could not find a pre-hook quantizer node for a specific " \
                                                "input port!"
                target_nncf_node_id = target_node.node_id
                target_nncf_node_key = nncf_graph.get_node_key_by_id(target_nncf_node_id)

            activation_fq_node = self._nx_graph.nodes[target_nncf_node_key]
            bitwidth = quantizer_info.quantizer_module_ref.num_bits
            activation_fq_node['color'] = bitwidth_color_map[bitwidth]
            activation_fq_node['style'] = 'filled'
            node_id = activation_fq_node[NNCFGraph.ID_NODE_ATTR]

            activation_fq_node['label'] = 'AFQ_[{}]_#{}'.format(
                quantizer_info.quantizer_module_ref.get_quantizer_config(),
                str(node_id))
            grouped_mode = bool(groups_of_adjacent_quantizers)
            if grouped_mode:
                group_id_str = 'UNDEFINED'
                group_id = groups_of_adjacent_quantizers.get_group_id_for_quantizer(quantizer_id)
                if node_id is None:
                    nncf_logger.error('No group for activation quantizer: {}'.format(target_nncf_node_key))
                else:
                    group_id_str = str(group_id)
                activation_fq_node['label'] += "_G" + group_id_str
Exemple #23
0
    def _get_multiforward_nodes(self,
                                graph: NNCFGraph) -> List[List[NNCFNode]]:
        """
        Groups nodes based on their `layer_name` property to determine groups of nodes belonging to
        a single weighted layer object in the model, i.e. the group of operations in the graph that reuse one and
        the same set of weights, and returns the groups that have more than one element.

        :return: List of lists of nodes; each list corresponds to a group of nodes united by the common
         underlying layer object of the original model.
        """
        ret = defaultdict(list)
        for node in graph.get_nodes_by_types(self._prune_operations_types):
            ret[node.layer_name].append(node)
        return [
            ret[module_identifier] for module_identifier in ret
            if len(ret[module_identifier]) > 1
        ]
Exemple #24
0
def count_filters_num(graph: NNCFGraph,
                      op_metatypes: List[Type[OperatorMetatype]],
                      output_channels: Dict[NNCFNodeName, int] = None) -> int:
    """
    Counts filters of `op_metatypes` layers taking into account new output channels number.

    :param graph: Graph to work with.
    :param op_metatypes: List of metatypes defining convolution operations.
    :param output_channels:  A dictionary of output channels number in pruned model.
    :return: Current number of filters according to given graph and output channels.
    """
    filters_num = 0
    output_channels = output_channels or {}
    for node in graph.get_nodes_by_metatypes(op_metatypes):
        filters_num += output_channels.get(node.node_name,
                                           node.layer_attributes.out_channels)
    return filters_num
Exemple #25
0
def get_conv_in_out_channels(graph: NNCFGraph):
    """
    Collects the number of input and output channels for each convolution in the graph.

    :param graph: NNCFGraph
    :return Dictionary with the number of input channels to convolution layers:
            {node_name: input_channels_num}
            Dictionary with the number of output channels from convolution layers:
            {node_name: output_channels_num}
    """
    in_channels, out_channels = {}, {}
    for node in graph.get_all_nodes():
        if isinstance(node.layer_attributes, ConvolutionLayerAttributes):
            name = node.node_name
            if name in in_channels and name in out_channels:
                continue
            in_channels[name] = node.layer_attributes.in_channels
            out_channels[name] = node.layer_attributes.out_channels
    return in_channels, out_channels
Exemple #26
0
    def _calculate_output_shape(self, graph: NNCFGraph,
                                node: NNCFNode) -> Tuple[int, ...]:
        """
        Calculates output shape of convolution layer by input edge.

        :param graph: the model graph
        :param node: node from NNCF graph
        :return: output shape
        """
        in_edge = graph.get_input_edges(node)[0]
        shape = list(in_edge.tensor_shape)[2:]
        attrs = node.layer_attributes

        assert isinstance(attrs, ConvolutionLayerAttributes)

        for i, _ in enumerate(shape):
            if attrs.transpose:
                shape[i] = (shape[i] - 1) * attrs.stride[
                    i] - 2 * attrs.padding_values[i] + attrs.kernel_size[i]
            else:
                shape[i] = (shape[i] + 2 * attrs.padding_values[i] -
                            attrs.kernel_size[i]) // attrs.stride[i] + 1
        return tuple(shape)
Exemple #27
0
def get_weight_node_name(graph: NNCFGraph, node_name: NNCFNodeName) -> NNCFNodeName:
    node = graph.get_node_by_name(node_name)
    while list(graph.get_previous_nodes(node)):
        node = list(graph.get_previous_nodes(node))[-1]
    return node.node_name
Exemple #28
0
    def convert(self) -> NNCFGraph:
        nncf_graph = NNCFGraph()
        producer_layer_id = None
        model_config = self._model.get_config()
        layer_name = None
        for layer_config in model_config['layers']:
            layer_name = layer_config['config']['name']
            if layer_name in self._custom_layer_infos:
                nncf_graph = self._add_custom_layer_subgraph(
                    nncf_graph, layer_name)
                continue
            layer_type = self._get_layer_type(layer_config)
            layer_dtype = self._get_layer_dtype(layer_config)
            data_format = layer_config['config'].get('data_format')
            model_layer = self._get_layer(layer_name)
            layer_metatype = get_keras_layer_metatype(model_layer)

            attrs = dict(type=layer_type,
                         dtype=layer_dtype,
                         metatype=layer_metatype,
                         data_format=data_format,
                         in_ports=[0],
                         out_ports=[0],
                         is_shared=False)

            layer_attributes = None
            if layer_metatype in DEPTHWISE_CONV_LAYER_METATYPES:
                layer_attributes = _get_conv_layer_attributes(
                    self._get_layer(layer_name), is_depthwise=True)
            elif layer_metatype in GENERAL_CONV_LAYER_METATYPES:
                layer_attributes = _get_conv_layer_attributes(
                    self._get_layer(layer_name), is_depthwise=False)
            elif layer_metatype in LAYER_METATYPES_AGNOSTIC_TO_DATA_PRECISION_WITH_MULTIPLE_INPUTS:
                layer_attributes = _get_multiple_input_layer_attributes(
                    model_layer)
            elif layer_metatype in RESHAPE_METATYPES:
                layer_attributes = _get_reshape_layer_attributes(model_layer)

            if layer_attributes is not None:
                attrs.update({NNCFGraph.LAYER_ATTRIBUTES: layer_attributes})

            node_name = layer_name
            nncf_node = nncf_graph.add_nncf_node(
                node_name,
                node_type=layer_type,
                node_metatype=layer_metatype,
                layer_attributes=layer_attributes,
                layer_name=layer_name,
                is_shared=False)

            input_shapes = self._prepare_shape(model_layer.input_shape)
            output_shapes = self._prepare_shape(model_layer.output_shape)
            self._node_info[node_name] = {
                'layer_name': layer_name,
                'target_node_name': layer_name,
                'inbound_node_idx': None,
                'input_shapes': input_shapes,
                'output_shapes': output_shapes,
            }

            if producer_layer_id is not None:
                input_shapes = self._prepare_shape(
                    self._model.get_layer(layer_name).input_shape)
                nncf_graph.add_edge_between_nncf_nodes(
                    producer_layer_id,
                    nncf_node.node_id,
                    tensor_shape=input_shapes[0],
                    input_port_id=0,
                    output_port_id=0,
                    dtype=Dtype.FLOAT
                )  # TODO(vshampor): determine from keras layers
            producer_layer_id = nncf_node.node_id

        if layer_name is not None:
            last_producer_layer_name = layer_name
            last_producer_layer_id = producer_layer_id
            output_model_layer = self._model.get_layer(
                last_producer_layer_name)
            output_aux_node_name = PREFIX_AUXILIARY_OUTPUT_NODE + '_0'
            output_node = nncf_graph.add_nncf_node(
                node_name=output_aux_node_name,
                node_type=NNCFGraphNodeType.OUTPUT_NODE,
                node_metatype=OutputNoopMetatype)
            nncf_graph.add_edge_between_nncf_nodes(
                last_producer_layer_id,
                output_node.node_id,
                tensor_shape=self._prepare_shape(
                    output_model_layer.output_shape),
                input_port_id=0,
                output_port_id=0,
                dtype=Dtype.FLOAT)

        return nncf_graph
Exemple #29
0
    def convert(self) -> NNCFGraph:
        nncf_graph = NNCFGraph()
        node_name_vs_nncf_node_ids = {}  # type: Dict[str, int]
        output_node_id_vs_model_output_idx = {}  # type: Dict[int, int]

        # Regular nodes
        for node_name, node_info in self._node_info.items():
            layer_name = node_info['layer_name']
            node_name_vs_nncf_node_ids[layer_name] = []
            layer_info = self._layer_info[layer_name]
            metatype = layer_info['metatype']
            layer = self._get_layer(layer_name)
            if metatype in DEPTHWISE_CONV_LAYER_METATYPES:
                layer_attributes = _get_conv_layer_attributes(
                    self._get_layer(layer_name), is_depthwise=True)
            elif metatype in GENERAL_CONV_LAYER_METATYPES:
                layer_attributes = _get_conv_layer_attributes(
                    self._get_layer(layer_name), is_depthwise=False)
            elif metatype in LAYER_METATYPES_AGNOSTIC_TO_DATA_PRECISION_WITH_MULTIPLE_INPUTS:
                layer_attributes = _get_multiple_input_layer_attributes(layer)
            elif metatype in RESHAPE_METATYPES:
                layer_attributes = _get_reshape_layer_attributes(layer)
            else:
                layer_attributes = None
            is_shared = len(self._layer_name_to_node_names[layer_name]) > 1
            nncf_node = nncf_graph.add_nncf_node(
                node_name=node_name,
                node_type=layer_info['type'],
                node_metatype=metatype,
                layer_attributes=layer_attributes,
                layer_name=layer_name,
                is_shared=is_shared)
            node_name_vs_nncf_node_ids[node_name] = nncf_node.node_id

            #pylint:disable=protected-access
            if layer in self._model._output_layers:
                output_idx = self._model._output_layers.index(layer)
                output_node_id_vs_model_output_idx[
                    nncf_node.node_id] = output_idx

        # Regular edges
        for edge, edge_info in self._edge_info.items():
            from_node_name, to_node_name = edge
            from_node_id = node_name_vs_nncf_node_ids[from_node_name]
            to_node_id = node_name_vs_nncf_node_ids[to_node_name]
            nncf_graph.add_edge_between_nncf_nodes(
                from_node_id,
                to_node_id,
                tensor_shape=edge_info['tensor_shape'],
                input_port_id=edge_info['to_node_input_port_id'],
                output_port_id=edge_info['from_node_output_port_id'],
                dtype=edge_info['dtype'])

        # Custom nodes and edges
        for custom_layer_name in self._custom_layer_infos:
            nncf_graph = self._add_custom_layer_subgraph(
                nncf_graph, custom_layer_name)
            # TODO (vshampor): connect the subgraphs with the rest of the graph

        for output_node_id, model_output_idx in output_node_id_vs_model_output_idx.items(
        ):
            # Ticket: 56853
            # We won't add an NNCF output auxiliary node for all of the NNCF nodes corresponding to real
            # model output, but only for the nodes that do not serve as a tensor source for any other node.
            # The reason is that current TF capabilities do not allow to insert post-hooks after TF functional model
            # output nodes without changing the name of the corresponding output, which won't be obvious to the user.
            nncf_node = nncf_graph.get_node_by_id(output_node_id)
            if not nncf_graph.get_next_nodes(nncf_node):
                output_aux_node_name = f'{nncf_node.node_name}_{PREFIX_AUXILIARY_OUTPUT_NODE}_{model_output_idx}'
                output_node = nncf_graph.add_nncf_node(
                    node_name=output_aux_node_name,
                    node_type=NNCFGraphNodeType.OUTPUT_NODE,
                    node_metatype=OutputNoopMetatype)
                node_info = self._node_info[
                    nncf_node.
                    node_name]  # works if _node_info keys are identical to node_names
                nncf_graph.add_edge_between_nncf_nodes(
                    nncf_node.node_id,
                    output_node.node_id,
                    tensor_shape=node_info['output_shapes'][0],
                    input_port_id=0,
                    output_port_id=0,
                    dtype=Dtype.FLOAT)

        return nncf_graph
Exemple #30
0
    def create_pruning_groups(self,
                              graph: NNCFGraph) -> Clusterization[NNCFNode]:
        """
        This function groups ALL nodes with pruning types to groups that should be pruned together.
            1. Create clusters for special ops (eltwises) that should be pruned together
            2. Create groups of nodes that should be pruned together (taking into account clusters of special ops)
            3. Add remaining single nodes
            4. Unite clusters for Conv + Depthwise conv (should be pruned together too)
            5. Checks for groups (all nodes in group can prune or all group can't be pruned)
        Return groups of modules that should be pruned together.

        :param graph: Graph to work with and their initialization parameters as values.
        :return: Clusterization of pruned nodes.
        """
        # pylint:disable=too-many-branches
        all_nodes_to_prune = graph.get_nodes_by_types(
            self._prune_operations_types)  # NNCFNodes here

        # 1. Clusters for special ops
        identity_like_types = self._identity_mask_propagation_op_metatype.get_all_op_aliases(
        )
        special_ops_clusterization = cluster_special_ops(
            graph, self._grouping_operations_types, identity_like_types)

        pruned_nodes_clusterization = Clusterization[NNCFNode](
            lambda x: x.node_id)

        # 2. Clusters for nodes that should be pruned together (taking into account clusters for special ops)
        for i, cluster in enumerate(
                special_ops_clusterization.get_all_clusters()):
            all_pruned_inputs = {}
            clusters_to_merge = []

            for node in cluster.elements:
                sources = get_sources_of_node(node, graph,
                                              self._prune_operations_types)
                for source_node in sources:
                    if pruned_nodes_clusterization.is_node_in_clusterization(
                            source_node.node_id):
                        # Merge clusters if some node already added in another cluster
                        cluster = pruned_nodes_clusterization.get_cluster_containing_element(
                            source_node.node_id)
                        clusters_to_merge.append(cluster.id)
                    elif source_node.node_id not in all_pruned_inputs:
                        all_pruned_inputs[source_node.node_id] = source_node

            if all_pruned_inputs:
                cluster = Cluster[NNCFNode](i, all_pruned_inputs.values(),
                                            all_pruned_inputs.keys())
                clusters_to_merge.append(cluster.id)
                pruned_nodes_clusterization.add_cluster(cluster)

            # Merge clusters if one source node in several special ops clusters
            pruned_nodes_clusterization.merge_list_of_clusters(
                clusters_to_merge)

        last_cluster_idx = len(special_ops_clusterization.get_all_clusters())

        # 3. Add remaining single nodes as separate clusters
        for node in all_nodes_to_prune:
            if not pruned_nodes_clusterization.is_node_in_clusterization(
                    node.node_id):
                cluster = Cluster[NNCFNode](last_cluster_idx, [node],
                                            [node.node_id])
                pruned_nodes_clusterization.add_cluster(cluster)

                last_cluster_idx += 1

        stop_propagation_ops = self._stop_propagation_op_metatype.get_all_op_aliases(
        )
        # 4. Merge clusters for Conv + Depthwise conv (should be pruned together too)
        for node in all_nodes_to_prune:
            cluster_id = pruned_nodes_clusterization.get_cluster_containing_element(
                node.node_id).id

            if is_prunable_depthwise_conv(node):
                previous_convs = get_previous_convs(
                    graph, node, self._prune_operations_types,
                    stop_propagation_ops)
                previous_clusters = [
                    pruned_nodes_clusterization.get_cluster_containing_element(
                        node.node_id).id for node in previous_convs
                ]
                pruned_nodes_clusterization.merge_list_of_clusters(
                    [cluster_id] + previous_clusters)

        # 5. Merge nodes into one cluster if some module forwards several times
        multiforward_nodes = self._get_multiforward_nodes(graph)
        for list_of_nodes in multiforward_nodes:
            clusters_to_merge = [
                pruned_nodes_clusterization.get_cluster_containing_element(
                    node.node_id).id for node in list_of_nodes
            ]
            pruned_nodes_clusterization.merge_list_of_clusters(
                clusters_to_merge)

            # Merge previous convolutions into one cluster
            all_previous_convs = []
            for node in list_of_nodes:
                nncf_node = graph.get_node_by_id(node.node_id)
                previous_convs = get_previous_convs(
                    graph, nncf_node, self._prune_operations_types,
                    stop_propagation_ops)
                # Check if previous node isn't multiforward,
                # in case of multiforward nodes cycle
                for previous_conv in previous_convs:
                    if previous_conv not in list_of_nodes:
                        all_previous_convs.append(previous_conv)

            previous_clusters = [
                pruned_nodes_clusterization.get_cluster_containing_element(
                    node.node_id).id for node in all_previous_convs
            ]
            pruned_nodes_clusterization.merge_list_of_clusters(
                previous_clusters)

        # 6. Checks for groups (all nodes in group can be pruned or all group can't be pruned).
        model_analyser = ModelAnalyzer(graph, self._pruning_operator_metatypes,
                                       is_prunable_depthwise_conv)
        can_prune_analysis = model_analyser.analyse_model_before_pruning()
        can_prune_and_should_prune_analysis = self._should_prune_groups_analysis(
            graph, pruned_nodes_clusterization, can_prune_analysis)
        can_prune_final_analysis = self._pruning_dimensions_analysis(
            graph, can_prune_and_should_prune_analysis)
        self._filter_groups(pruned_nodes_clusterization,
                            can_prune_final_analysis)
        return pruned_nodes_clusterization