Exemplo n.º 1
0
 def replace_sub_graph(self, graph: Graph, match: dict):
     node = match['softmax']
     if 'temperature' in node and node['temperature'] != 1.0:
         in_node = node.in_node()
         out_nodes = [node for node in node.out_nodes().values()]
         graph.remove_edge(node.in_node().id, node.id)
         temperature = np.array([1.0 / node.temperature])
         scalar_value_op = Const(graph, dict(value=temperature, shape=temperature.shape,
                                             symbol_dict={'name': node.id + '/const'}))
         mul_op = Mul(graph, dict(name=node.id + '/mul_', symbol_dict={'name': node.id + '/mul_'}))
         mul_node = mul_op.create_node(inputs=[in_node, scalar_value_op.create_node()])
         edge_attrs = graph.get_edge_data(node.id, out_nodes[0].id)[0]
         graph.add_edges_from([(mul_node.id, node.id, edge_attrs)])
Exemplo n.º 2
0
    def replace_pattern(self, graph: Graph, match: dict):
        """
            Adds layers with type 'Const' that produce blob from 'bin' file. The pass finds data nodes with one output which
            doesn't have edge with 'bin' attribute (or with two outputs and at least one output doesn't have 'bin' attr)
            and generate Const op node before the node and data node before the Const node. The data node before 'Const'
            node is needed because the op node dumps input tensors to bin file.
        """
        node = match['data']
        if len(node.in_nodes()) > 0:
            return

        if self._check_bin_attrs(node):
            if node.has_valid('value'):
                const_node_name = node.soft_get('name', node.id)
                const_node_name = graph.unique_id(
                    re.sub(r'\/Output_\d+\/Data_(.?)+', '', const_node_name))
                log.debug("Added Const node '{}'".format(const_node_name))
                const_node = Const(
                    graph, {
                        'name':
                        const_node_name,
                        'value':
                        node.value,
                        'force_shape':
                        node.soft_get('force_shape', None),
                        'override_output_shape':
                        node.has_valid('force_shape'),
                        'force_type':
                        node.soft_get('force_type', None),
                        'correct_data_type':
                        node.soft_get('correct_data_type', None),
                        'rt_info':
                        node.soft_get('rt_info', RTInfo()),
                    }).create_node()
                const_node.add_input_port(0)
                graph.add_edges_from([(const_node_name, node.id, {'out': 0})])

                node_copy = node.copy_node()
                const_node.type_infer(const_node)
                graph.add_edges_from([(node_copy.id, const_node_name, {
                    'in': 0,
                    'bin': 'custom'
                })])
            elif not self._check_that_node_from_body(node):
                log.debug('node = {}'.format(node.graph.node[node.id]))
                raise Error(
                    'Discovered data node without inputs and value, node.name = {}, consumer.name = {}. '
                    + refer_to_faq_msg(23), node.soft_get('name'),
                    node.out_node().soft_get('name')
                    if len(node.out_nodes()) else "<no consumer>")
 def replace_sub_graph(self, graph: Graph, match: dict):
     ph = match['placeholder']
     if ph.name in graph.graph['freeze_placeholder']:
         name = ph.name
         if ph.has_and_set('data_type'):
             data_type = ph.data_type
         else:
             data_type = SUPPORTED_DATA_TYPES[
                 graph.graph['cmd_params'].data_type][0]
         string_value = graph.graph['freeze_placeholder'][name]
         try:
             if data_type != np.bool:
                 value = mo_array(string_value, dtype=data_type)
             # TODO: investigate why boolean type is allowed only for TensorFlow
             elif data_type == np.bool and graph.graph['fw'] == 'tf':
                 from openvino.tools.mo.front.tf.common import tf_data_type_cast
                 if isinstance(string_value, list):
                     casted_list = list()
                     for v in mo_array(string_value):
                         casted_list.append(
                             tf_data_type_cast[ph.data_type](v))
                     value = mo_array(string_value, dtype=data_type)
                 else:
                     value = tf_data_type_cast[ph.data_type](string_value)
             else:
                 raise Error("Cannot cast value {} to {} data_type".format(
                     string_value, data_type))
         except:
             raise Error("Cannot cast value {} to {} data_type".format(
                 string_value, data_type))
         try:
             value = np.reshape(a=value, newshape=ph.shape)
         except:
             raise Error("Can not reshape value {} to shape {}".format(
                 value, ph.shape))
         out_edges = list(graph.out_edges(ph.id, data=True))
         new_node = Const(graph).create_node(
             attrs={
                 'value': value,
                 'data_type': type(value),
                 'name': name + '/const_placeholder',
                 'shape': ph.shape
             })
         graph.erase_node(ph)
         graph.add_edges_from([(new_node.id, v, attrs)
                               for u, v, attrs in out_edges])
         log.info(
             "Placeholder node \"{}\" was replaced with Const node \"{}\" with value \"{}\""
             .format(name, new_node.name, value))
Exemplo n.º 4
0
 def test_sub_graph_between_nodes_do_not_include_incoming_edges_for_input_nodes(
         self):
     """
     Check that the function doesn't add input nodes for the start nodes of the sub-graph. For example, we do not
     need to add node 5 in the case below if we find match from node 1 till node 4.
       5->
          \
     1 -> 2 -> 3 -> 4
     """
     graph = Graph()
     graph.add_nodes_from(list(range(1, 6)))
     graph.add_edges_from([(1, 2), (2, 3), (3, 4), (5, 2)])
     sub_graph_nodes = sub_graph_between_nodes(graph, [2], [4])
     self.assertIsNotNone(sub_graph_nodes)
     self.assertListEqual(sorted(sub_graph_nodes), [2, 3, 4])
Exemplo n.º 5
0
 def test_sub_graph_between_nodes_placeholder_excluded(self):
     """
     Check that the function do not check that node is Placeholders for the nodes not included into the sub-graph.
     For example, node 5 is Placeholder but it is not included into the sub-graph, so this attribute is ignored.
       5->
          \
     1 -> 2 -> 3 -> 4
     """
     graph = Graph()
     graph.add_nodes_from(list(range(1, 6)))
     graph.node[5]['op'] = 'Parameter'
     graph.add_edges_from([(1, 2), (2, 3), (3, 4), (5, 2)])
     sub_graph_nodes = sub_graph_between_nodes(graph, [2], [4])
     self.assertIsNotNone(sub_graph_nodes)
     self.assertListEqual(sorted(sub_graph_nodes), [2, 3, 4])
Exemplo n.º 6
0
 def test_sub_graph_between_nodes_control_flow_not_included_forward(self):
     """
     Check that the function works correctly for case when control flow edges should not be traversed (edge 3 -> 5).
        1 -> 2 -> 3 -> 4
                   \
                    -> 5 -> 6
     """
     graph = Graph()
     graph.add_nodes_from(list(range(1, 7)))
     graph.add_edges_from([(1, 2), (2, 3), (3, 4),
                           (3, 5, {
                               'control_flow_edge': True
                           }), (5, 6)])
     sub_graph_nodes = sub_graph_between_nodes(graph, [1], [4],
                                               include_control_flow=False)
     self.assertIsNotNone(sub_graph_nodes)
     self.assertListEqual(sorted(sub_graph_nodes), sorted([1, 2, 3, 4]))
Exemplo n.º 7
0
def build_matcher(graph: Graph,
                  nodes: list,
                  edges: list,
                  node_attrs: list = None,
                  edge_attrs: list = None):
    if node_attrs is not None or edge_attrs is not None:
        log.warning(
            '\'edge_attrs\' or `\'node_attrs\'` parameter was passed to function \'find_pattern_matches\', '
            'but they are not used anymore. Pattern matching proceeds according to \'nodes\' and \'edges\' '
            'parameters. Please avoid passing \'edge_attrs\' and \'node_attrs\' parameters to any pattern '
            'matching function like \'find_pattern_matches\', \'apply_pattern\' and \'pattern\' because it '
            'will be deprecated in the next release.')

    subgraph = Graph(name='pattern')
    subgraph.add_nodes_from(nodes)
    subgraph.add_edges_from(edges)
    return ism.MultiDiGraphMatcher(graph, subgraph, node_match, edge_match)
Exemplo n.º 8
0
 def test_sub_graph_between_nodes_control_flow_included(self):
     """
     Check that the function works correctly for case when control flow edges must be traversed (edge 5 -> 2).
     6 -> 5->
             \
        1 -> 2 -> 3 -> 4
     """
     graph = Graph()
     graph.add_nodes_from(list(range(1, 7)))
     graph.add_edges_from([(1, 2), (2, 3), (3, 4),
                           (5, 2, {
                               'control_flow_edge': True
                           }), (6, 5)])
     sub_graph_nodes = sub_graph_between_nodes(graph, [1], [4],
                                               include_control_flow=True)
     self.assertIsNotNone(sub_graph_nodes)
     self.assertListEqual(sorted(sub_graph_nodes),
                          sorted([1, 2, 3, 4, 5, 6]))
Exemplo n.º 9
0
    def find_and_replace_pattern(self, graph: Graph):
        """
        This function finds all const data nodes that have more that one consumer and then duplicate them
        """
        data_nodes = [Node(graph, id) for id in graph.nodes() if Node(graph, id).soft_get('kind') == 'data']
        for node in data_nodes:
            # Check that node has const values and more than one consumer
            if len(node.in_nodes()) and node.in_node().soft_get('type') == 'Const' and len(node.out_nodes()) > 1 and \
                            node.value is not None:
                # Here we delete all edges between base node and it's consumers (except first), and then duplicate this
                # node to connect with other consumers
                for v, d in node.get_outputs():
                    out_node = Node(graph, v)
                    e_attrs = d
                    graph.remove_edge(node.id, out_node.id)
                    data = Op.create_input_data_node(graph, "Copy_{}".format(node.id), mo_array(node.value),
                                                     graph.node[node.id])

                    graph.add_edges_from([(data.id, out_node.id, e_attrs)])
Exemplo n.º 10
0
    def add_reshape_before_op_node(graph: Graph, data_node_name: str, op_node_name: str, edge_attrs: dict):
        """
        Adds reshape operation which expands dimension of the specified data tensor to 4D.
        :param graph: graph to operate on.
        :param data_node_name: the name of the data node to be reshaped to 4D tensor.
        :param op_node_name: name of the TFCustomSubgraphCall node which produces the tensor.
        :param edge_attrs: edge attributes which should be preserved.
        :return: None
        """
        data_node = Node(graph, data_node_name)

        graph.remove_edge(data_node_name, op_node_name)

        assert data_node['shape'] is not None

        new_shape = CustomSubgraphCall.make_shape_4d(data_node['shape'])

        # reshape shape data node
        reshape_shape_data_node_name = graph.unique_id("Reshape_shape_")
        graph.add_node(reshape_shape_data_node_name, kind='data', name=reshape_shape_data_node_name, value=new_shape,
                       shape=[1])

        # reshape operation node
        reshape_node_name = graph.unique_id("Reshape_")
        graph.add_node(reshape_node_name, kind='op', type='Reshape', name=reshape_node_name, op='Reshape',
                       data_type=data_node['data_type'])
        update_ie_fields(graph.node[reshape_node_name])

        # reshaped data node
        reshaped_value = None
        if data_node['value'] is not None:
            reshaped_value = np.reshape(data_node['value'], new_shape)
        reshaped_data_node_name = graph.unique_id("reshaped_data_")
        graph.add_node(reshaped_data_node_name, kind='data', name=reshaped_data_node_name, shape=new_shape,
                       value=reshaped_value, nchw_layout=True)

        graph.add_edges_from([
            (data_node_name, reshape_node_name, {'in': 0}),
            (reshape_shape_data_node_name, reshape_node_name, {'in': 1}),
            (reshape_node_name, reshaped_data_node_name, {'out': 0}),
            (reshaped_data_node_name, op_node_name, edge_attrs)
        ])
Exemplo n.º 11
0
    def test_sub_graph_between_nodes_include_incoming_edges_for_internal_nodes(
            self):
        """
        Check that the function adds input nodes for the internal nodes of the graph. For example, we need to add node 5
        and 6 in the case below if we find match from node 1 till node 4.
        6 -> 5 ->
                 \
            1 -> 2 -> 3 -> 4
        :return:
        """
        graph = Graph()
        graph.add_nodes_from(list(range(1, 7)))
        graph.add_edges_from([(1, 2), (2, 3), (3, 4), (5, 2), (6, 5)])
        sub_graph_nodes = sub_graph_between_nodes(graph, [1], [4])
        self.assertIsNotNone(sub_graph_nodes)
        self.assertListEqual(sorted(sub_graph_nodes), list(range(1, 7)))

        sub_graph_nodes = sub_graph_between_nodes(graph, [1], [2])
        self.assertIsNotNone(sub_graph_nodes)
        self.assertListEqual(sorted(sub_graph_nodes), [1, 2, 5, 6])
Exemplo n.º 12
0
    def replace_pattern(self, graph: Graph, match: dict):
        conv = match['conv']
        stb = match['space_to_batch']
        bts = match['batch_to_space']

        block_size = match['stb_bs']

        input = match['input']
        output = match['output']
        stb_out = match['stb_output']
        conv_out = match['conv_output']

        in_edge_attrs = graph.get_edge_data(input.id, stb.id)[0]
        out_edge_attrs = graph.get_edge_data(bts.id, output.id)[0]

        graph.remove_edge(input.id, stb.id)
        graph.remove_edge(stb_out.id, conv.id)
        graph.remove_edge(conv.id, conv_out.id)
        graph.remove_edge(bts.id, output.id)

        conv.dilation[conv.spatial_dims] = block_size.value[conv.spatial_dims]

        pad_begin = match['stb_pad_begin'].value - match['bts_crop_begin'].value
        pad_end = match['stb_pad_end'].value - match['bts_crop_end'].value
        conv.pad[conv.spatial_dims] = [[pad_begin[x], pad_end[x]]
                                       for x in conv.spatial_dims]
        conv['auto_pad'] = None

        graph.add_edges_from([
            (input.id, conv.id, {
                'in': 0,
                **in_edge_attrs
            }),
            (conv.id, output.id, {
                'out': 0,
                **out_edge_attrs
            }),
        ])
Exemplo n.º 13
0
    def create_and_connect_input_data_node(graph: Graph,
                                           op_node: Node,
                                           attrs: dict = None,
                                           edge_attrs: dict = None):
        assert op_node is not None and op_node.kind == 'op'
        if attrs is None:
            attrs = {}
        if edge_attrs is None:
            edge_attrs = {}

        data_node = graph.unique_id(op_node.id)
        default_attrs = dict(kind='data',
                             name=data_node,
                             value=None,
                             shape=None,
                             data_type=None,
                             infer=None)
        default_attrs.update(attrs)
        graph.add_node(data_node, **add_attrs_props(default_attrs))
        data_node = Node(graph, data_node)
        op_node.add_input_port(edge_attrs['in'], skip_if_exist=True)
        graph.add_edges_from([(data_node.id, op_node.id, edge_attrs)])
        return data_node
Exemplo n.º 14
0
    def replace_pattern(self, graph: Graph, match: dict):
        node = match['op']
        node.op = 'Conv2D'

        if node.bias_term:
            num_inputs = len(node.in_nodes()) - 2
            w_node = node.in_node(len(node.in_nodes()) - 2)
            b_node = node.in_node(len(node.in_nodes()) - 1)
        else:
            num_inputs = len(node.in_nodes()) - 1
            w_node = node.in_node(len(node.in_nodes()) - 1)

        for i in range(1, num_inputs):
            in_i = node.in_node(i)
            out_i = node.out_node(i)
            conv_id = graph.unique_id(node.id + '__')
            graph.add_node(conv_id, **copy.deepcopy(node.get_attrs()))
            new_conv = Node(graph, conv_id)
            new_conv.name = conv_id

            graph.remove_edge(in_i.id, node.id)
            graph.remove_edge(node.id, out_i.id)
            graph.add_edges_from([
                (w_node.id, conv_id, {
                    'in': 1,
                    'bin': 'weights'
                }),
            ])

            if node.bias_term:
                graph.add_edges_from([
                    (b_node.id, conv_id, {
                        'in': 2,
                        'bin': 'biases'
                    }),
                ])

            graph.add_edges_from([
                (in_i.id, conv_id, {
                    'in': 0
                }),
            ])
            graph.add_edge(conv_id, out_i.id, **{'out': 0})
Exemplo n.º 15
0
def load_parallel_component(file_descr, graph: Graph, prev_layer_id):
    """
    Load ParallelComponent of the Kaldi model.
    ParallelComponent contains parallel nested networks.
    VariadicSplit is inserted before nested networks.
    Outputs of nested networks concatenate with layer Concat.

    :param file_descr: descriptor of the model file
    :param graph: graph with the topology.
    :param prev_layer_id: id of the input layers for parallel component layer
    :return: id of the concat layer - last layer of the parallel component layers
    """
    nnet_count = read_token_value(file_descr, b'<NestedNnetCount>')
    log.debug(
        'Model contains parallel component with {} nested networks'.format(
            nnet_count))

    split_points = []
    outputs = []
    inputs = []

    for i in range(nnet_count):
        read_token_value(file_descr, b'<NestedNnet>')
        collect_until_token(file_descr, b'<Nnet>')
        g = Graph()
        load_kalid_nnet1_model(g, file_descr, 'Nested_net_{}'.format(i))

        # input to nnet1 models is of a rank 1 but we also insert batch_size to 0th axis
        # 1st axis contains input_size of the nested subnetwork
        # we split input from the main network to subnetworks
        input_node = Node(g, 'Parameter')
        split_points.append(input_node['shape'][1])
        g.remove_node(input_node.id)

        mapping = {
            node: graph.unique_id(node)
            for node in g.nodes(data=False) if node in graph
        }
        g = nx.relabel_nodes(g, mapping)
        for val in mapping.values():
            g.node[val]['name'] = val
        graph.add_nodes_from(g.nodes(data=True))
        graph.add_edges_from(g.edges(data=True))
        sorted_nodes = tuple(nx.topological_sort(g))

        outputs.append(Node(graph, sorted_nodes[-1]))
        inputs.append(Node(graph, sorted_nodes[0]))

    split_id = graph.unique_id(prefix='NestedNets/VariadicSplit')
    attrs = {
        'out_ports_count': nnet_count,
        'size_splits': split_points,
        'axis': 1,
        'name': split_id
    }
    variadic_split_node = AttributedVariadicSplit(graph, attrs).create_node()
    prev_layer_node = Node(graph, prev_layer_id)
    prev_layer_node.add_output_port(0)
    graph.create_edge(
        prev_layer_node, variadic_split_node, 0, 0,
        create_edge_attrs(prev_layer_id, variadic_split_node.id,
                          prev_layer_id))

    concat_id = graph.unique_id(prefix='Concat')
    graph.add_node(concat_id, parameters=None, op='concat', kind='op')
    concat_node = Node(graph, concat_id)

    # Connect each output of variadic_split_node to each subnetwork's inputs in ParallelComponent
    # and each subnetwork's output to concat_node
    for i, (input_node, output_node) in enumerate(zip(inputs, outputs)):
        output_node.add_output_port(0)
        concat_node.add_input_port(i)
        graph.create_edge(
            output_node, concat_node, 0, i,
            create_edge_attrs(output_node.id, concat_id, output_node.id, i, 0))
        graph.create_edge(
            variadic_split_node, input_node, i, 0,
            create_edge_attrs(variadic_split_node.id, input_node.id,
                              variadic_split_node.id, 0, i))
    return concat_id
Exemplo n.º 16
0
    def replace_pattern(graph, match: dict):
        # Here we will found all parts of TI: condition, inputs/outputs, back edges, body and create TensorIterator Op
        # and make all checks needed for TensorIterator work
        cond_data = match['condition'].out_node(
            0) if not match['condition'].out_port(0).disconnected() else None
        time_data = match['condition'].out_node(1) if len(
            match['condition'].out_nodes()) >= 1 else None
        name = match['condition'].name

        back_edges = []
        inputs = []
        outputs = []

        if cond_data is not None:
            for node in cond_data.out_nodes():
                if node['kind'] == 'op' and node[
                        'op'] == 'TensorIteratorBackEdge':
                    back_edges.append(node.id)
                elif node['kind'] == 'op' and node[
                        'op'] == 'TensorIteratorInput':
                    inputs.append(node.id)
                elif node['kind'] == 'op' and node[
                        'op'] == 'TensorIteratorOutput':
                    outputs.append(node.id)

        if time_data is not None:
            for node in time_data.out_nodes():
                if node['kind'] == 'op' and node['op'] == 'TensorIteratorInput':
                    inputs.append(node.id)
                elif node['kind'] == 'op' and node[
                        'op'] == 'TensorIteratorOutput':
                    outputs.append(node.id)
                else:
                    # something goes wrong here
                    assert False
        condition = match['condition']
        tensor_sequence_length = condition.in_node(0)

        nodes_to_remove = [
            n.id
            for n in (condition, cond_data, time_data, tensor_sequence_length)
            if n is not None
        ]
        graph.remove_nodes_from(nodes_to_remove)

        body_nodes, extra_inputs = get_body(graph, inputs, outputs)

        if cond_data is not None:
            body_nodes = list(set(body_nodes) - set([cond_data]))

        inputs += extra_inputs

        assert all([node in graph.nodes() for node in body_nodes])

        inputs = [Node(graph, node) for node in inputs]
        outputs = [Node(graph, node) for node in outputs]
        back_edges = [Node(graph, node) for node in back_edges]

        external_inputs = [{
            'external_data_id':
            node.in_node(1 if node.has_valid('axis') else 0),
            'internal_data_id':
            node.out_node(0),
            'axis':
            node.axis,
            'start':
            node.start,
            'end':
            node.end,
            'stride':
            node.stride,
            'part_size':
            node.part_size
        } for node in inputs]

        external_outputs = [{
            'external_data_id':
            node.out_node(0),
            'internal_data_id':
            node.in_node(1 if node.has_valid('axis') else 0),
            'axis':
            node.axis,
            'start':
            node.start,
            'end':
            node.end,
            'stride':
            node.stride,
            'part_size':
            node.part_size
        } for node in outputs]

        back_edges_data = [{
            'from_data_id': node.in_node(1),
            'to_data_id': node.out_node(0),
            'init_data_id': node.in_node(0),
        } for node in back_edges]

        body = Graph(name='body')
        body.graph = graph.graph
        body.add_nodes_from([(node, graph.node[node]) for node in body_nodes])
        body.add_edges_from([
            (u, v, k, d) for u, v, k, d in graph.edges(data=True, keys=True)
            if u in body_nodes and v in body_nodes
        ])

        graph.remove_nodes_from(body_nodes + [match['condition'].id] +
                                [inp.id for inp in inputs] +
                                [out.id for out in outputs])
        internal_id_count = 0
        real_back_edges = []
        for edge in back_edges_data:
            assert edge['from_data_id'].id in body.nodes()
            assert edge['to_data_id'].id in body.nodes()
            assert edge['init_data_id'].id in body.nodes()
            edge['from_data_id'] = Node(body, edge['from_data_id'].id)
            edge['to_data_id'] = Node(body, edge['to_data_id'].id)
            edge['init_data_id'] = Node(body, edge['init_data_id'].id)
            add_opoutput(body, edge['from_data_id'].id, 0, False)

            # Assign/reuse ids for the back-edge start; it comes from from_data_id
            assert len(edge['from_data_id'].in_nodes()) == 1
            # layer id
            if not edge['from_data_id'].in_node().has_valid(
                    'internal_layer_id'):
                edge['from_data_id'].in_node(
                )['internal_layer_id'] = internal_id_count
                internal_id_count += 1
            edge['from_layer'] = edge['from_data_id'].in_node(
            )['internal_layer_id']

            # port id
            if 'internal_port_id' not in edge['from_data_id'].in_edge():
                edge['from_data_id'].in_edge(
                )['internal_port_id'] = internal_id_count
                internal_id_count += 1
            edge['from_port'] = edge['from_data_id'].in_edge(
            )['internal_port_id']

            # Look at all consumers for a data that ends a back-edge
            # For each such consumer, there will be a separate back-edge (and input)
            current_real_back_edges = []
            for _, consumer, key, edge_attrs in body.out_edges(
                    edge['to_data_id'].id, data=True, keys=True):

                real_edge = {}
                real_edge.update(
                    edge)  # all real back_edges have the same back-edge start

                consumer = Node(body, consumer)

                if real_edge['to_data_id'].in_node().has_valid(
                        'internal_layer_id'):
                    assert False
                    real_edge['to_data_id'].out_node()['internal_layer_id'] = \
                        real_edge['to_data_id'].in_node().internal_layer_id
                elif not consumer.has_valid('internal_layer_id'):
                    consumer['internal_layer_id'] = internal_id_count
                    internal_id_count += 1
                real_edge['to_layer'] = consumer['internal_layer_id']

                assert 'internal_port_id' not in edge_attrs
                assert len(real_edge['init_data_id'].out_edges()) == 1
                assert not 'internal_port_id' in real_edge[
                    'init_data_id'].out_edge()
                edge_attrs['internal_port_id'] = internal_id_count
                internal_id_count += 1
                real_edge['to_port'] = edge_attrs['internal_port_id']
                real_edge['consumer'] = consumer
                real_edge['consumer_key'] = key

                real_edge['attrs'] = deepcopy(edge_attrs)
                current_real_back_edges.append(real_edge)

            # connect initial data node with each consumer providing actual edge attributes
            body.add_edges_from([
                (real_edge['init_data_id'].id, real_edge['consumer'].id,
                 real_edge['consumer_key'], real_edge['attrs'])
                for real_edge in current_real_back_edges
            ])

            body.remove_nodes_from(
                [edge['to_data_id'].id, edge['to_data_id'].in_node().id])
            real_back_edges += current_real_back_edges

        real_external_inputs = []

        for ext_inp in external_inputs:
            assert ext_inp['external_data_id'].id not in body.nodes()
            assert ext_inp['internal_data_id'].id in body.nodes()
            ext_inp['internal_data_id'] = Node(body,
                                               ext_inp['internal_data_id'].id)

            if ext_inp['axis'] is not None:
                # Insert squeezing resize at input port that has partitioning
                shape = ext_inp['internal_data_id'].shape.copy()
                assert not ext_inp['internal_data_id'].has_valid('value')
                new_input_data = Op._create_data_node(
                    body,
                    ext_inp['internal_data_id'].name + '/UnsqueezedInput',
                    dict(shape=shape_insert(shape, ext_inp['axis'], 1)))

                reshape_op = Squeeze(
                    body,
                    dict(name=ext_inp['internal_data_id'].name +
                         '/InputSqueeze'))
                reshape_dim_data = Const(
                    body, {
                        'name':
                        ext_inp['internal_data_id'].name + '/ReshapeDim',
                        'value': ext_inp['axis']
                    }).create_node_with_data()
                reshape_op.create_node_with_data(
                    [new_input_data, reshape_dim_data],
                    data_nodes=[ext_inp['internal_data_id']])
                ext_inp['internal_data_id'] = new_input_data

            ext_inp['internal_data_id']['is_input'] = True
            assert len(ext_inp['internal_data_id'].in_nodes()) == 0
            ext_inp['external_port_id'] = internal_id_count
            internal_id_count += 1
            for _, consumer, edge_attrs in body.out_edges(
                    ext_inp['internal_data_id'].id, data=True):
                real_ext_inp = {}
                real_ext_inp.update(ext_inp)
                consumer = Node(body, consumer)
                if not consumer.has_valid('internal_layer_id'):
                    consumer['internal_layer_id'] = internal_id_count
                    internal_id_count += 1
                if not 'internal_port_id' in edge_attrs:
                    edge_attrs['internal_port_id'] = internal_id_count
                    internal_id_count += 1
                real_ext_inp['internal_layer_id'] = consumer[
                    'internal_layer_id']
                real_ext_inp['internal_port_id'] = edge_attrs[
                    'internal_port_id']
                real_external_inputs.append(real_ext_inp)

        for ext_out in external_outputs:
            assert ext_out['external_data_id'].id not in body.nodes()
            assert ext_out['internal_data_id'].id in body.nodes()
            ext_out['internal_data_id'] = Node(body,
                                               ext_out['internal_data_id'].id)

            if ext_out['axis'] is not None:
                # Insert unsqueezing resize at output port that has partitioning
                reshape_op = Unsqueeze(
                    body,
                    dict(name=ext_out['internal_data_id'].name +
                         '/OutputUnsqueeze'))
                reshape_dim_data = Const(
                    body, {
                        'name':
                        ext_out['internal_data_id'].name + '/ReshapeDim',
                        'value': ext_out['axis']
                    }).create_node_with_data()
                ext_out['internal_data_id'] = reshape_op.create_node_with_data(
                    [ext_out['internal_data_id'], reshape_dim_data])

            # TODO: add here working with simple outputs

            if not any([
                    out_node.soft_get('op', None) == 'Result'
                    for out_node in ext_out['internal_data_id'].out_nodes()
            ]):
                add_opoutput(body, ext_out['internal_data_id'].id, 0, False)

            # assert len(ext_out['internal_data_id'].out_nodes()) == 0
            assert len(ext_out['internal_data_id'].in_nodes()) == 1
            if not 'internal_layer_id' in ext_out['internal_data_id'].in_node(
            ):
                ext_out['internal_data_id'].in_node(
                )['internal_layer_id'] = internal_id_count
                internal_id_count += 1
            if not 'internal_port_id' in ext_out['internal_data_id'].in_edge():
                ext_out['internal_data_id'].in_edge(
                )['internal_port_id'] = internal_id_count
                internal_id_count += 1
            ext_out['internal_layer_id'] = ext_out['internal_data_id'].in_node(
            )['internal_layer_id']
            ext_out['internal_port_id'] = ext_out['internal_data_id'].in_edge(
            )['internal_port_id']
            ext_out['external_port_id'] = internal_id_count
            internal_id_count += 1

        # create TensorIterator layer with pre-computed components
        ti_op = TensorIterator(
            graph, {
                'name':
                name + '/TensorIterator',
                'body':
                body,
                'in_ports_count':
                len(external_inputs),
                'out_ports_count':
                len(external_outputs),
                'input_port_map': [{
                    field: external_input[field]
                    for field in [
                        'external_port_id', 'internal_layer_id',
                        'internal_port_id', 'axis', 'stride', 'part_size',
                        'start', 'end'
                    ]
                } for external_input in real_external_inputs],
                'output_port_map': [{
                    field: external_output[field]
                    for field in [
                        'external_port_id', 'internal_layer_id',
                        'internal_port_id', 'axis', 'stride', 'part_size',
                        'start', 'end'
                    ]
                } for external_output in external_outputs],
                'back_edges': [{
                    field: edge[field]
                    for field in
                    ['from_layer', 'from_port', 'to_layer', 'to_port']
                } for edge in real_back_edges],
            })

        ti_outs = ti_op.create_node_with_data(
            inputs=[inp['external_data_id'] for inp in external_inputs],
            edge_attrs=[{
                'external_port_id': inp['external_port_id']
            } for inp in external_inputs],
            data_nodes=[out['external_data_id'] for out in external_outputs])

        if not isinstance(ti_outs, list):
            ti_outs = [ti_outs]

        for i, out in enumerate(ti_outs):
            out.in_edge(
            )['external_port_id'] = external_outputs[i]['external_port_id']

        ti = ti_outs[0].in_node()
        TensorIterator.cover_body_input_data_nodes_with_parameter_ops(ti)
        TensorIterator.cover_body_constant_data_nodes_with_const_ops(ti)
        TensorIterator.normalize_internal_ids(ti)
Exemplo n.º 17
0
class IREngine(object):
    def __init__(self,
                 path_to_xml: str,
                 path_to_bin=None,
                 precision="FP32",
                 xml_tree=None):
        if not xml_tree and not os.path.exists(path_to_xml):
            raise AttributeError("File {} do not exists!".format(path_to_xml))

        if path_to_bin and not os.path.exists(path_to_bin):
            raise AttributeError("File {} do not exists!".format(path_to_bin))

        self.path_to_xml = str(path_to_xml)
        self.path_to_bin = str(path_to_bin) if path_to_bin else None
        self.xml_tree = xml_tree
        self.input_node = None
        self.ir_version = None
        self.meta_data = dict()

        if precision.upper() not in ['FP32', 'FP16']:
            raise AttributeError(
                "Precision {} is not supported!".format(precision))
        self.__load_ir()

    def __load_xml(self):
        xml_tree = self.xml_tree or ET.parse(self.path_to_xml)
        xml_root = xml_tree.getroot()
        xml_layers = {}
        xml_edges = []
        statistics = {}

        Edge = namedtuple('edge',
                          ['from_layer', 'from_port', 'to_layer', 'to_port'])

        # Create graph with operations only
        self.graph = Graph()
        self.graph.graph['hashes'] = {}

        self.graph.graph['ir_version'] = int(
            xml_root.attrib['version']) if xml_root.attrib.get(
                'version') is not None else None

        # NOTE: THis is MO internal attribute, it cannot be used for
        # defining graph input layout. We set it to NCHW as in MO back stage
        # during conversion for correct shape inference of layout specific
        # operations (ExtractImagePatches, SpaceToDepth, etc.)
        self.graph.graph['layout'] = 'NCHW'

        self.graph.name = xml_root.attrib['name'] if xml_root.attrib.get(
            'name') is not None else None

        # Parse XML
        for child in xml_root:
            if child.tag == 'layers':
                for layer in child:
                    layer_id, layer_attrs = self.__load_layer(layer)
                    xml_layers.update({layer_id: layer_attrs})
            elif child.tag == 'edges':
                for edge in child:
                    xml_edges.append(
                        Edge(edge.attrib['from-layer'],
                             int(edge.attrib['from-port']),
                             edge.attrib['to-layer'],
                             int(edge.attrib['to-port'])))
            elif child.tag == 'statistics':
                layers = child.findall('layer')
                for layer in layers:
                    statistics[layer.find('name').text] = {
                        'min': layer.find('min').text,
                        'max': layer.find('max').text
                    }
            elif child.tag == 'meta_data':
                for elem in child:
                    if elem.tag == 'cli_parameters':
                        for det in elem:
                            if det.tag != 'unset':
                                value = det.attrib['value']
                                if value in ['True', 'False']:
                                    value = False if value == 'False' else True
                                self.meta_data[det.tag] = value
                            else:
                                self.meta_data[det.tag] = det.attrib[
                                    'unset_cli_parameters'].split(',_')
            elif child.tag == 'quantization_parameters':
                # Section with Post Optimization Toolkit parameters
                self.meta_data['quantization_parameters'] = dict()
                for elem in child:
                    if elem.tag == 'config':
                        self.meta_data['quantization_parameters'][
                            'config'] = elem.text
                    elif elem.tag in ['version', 'cli_params']:
                        self.meta_data['quantization_parameters'][
                            elem.tag] = elem.attrib['value']

        self.graph.graph['cmd_params'] = Namespace(
            **self.meta_data)  # TODO check what we need all this attrs

        if len(statistics):
            self.graph.graph['statistics'] = statistics

        for layer in xml_layers.keys():
            self.graph.add_node(layer, **xml_layers[layer])

        xml_edges.sort(key=lambda x: x.to_layer)

        for edge in xml_edges:
            self.graph.add_edges_from([(edge.from_layer, edge.to_layer, {
                'from_port': edge.from_port,
                'to_port': edge.to_port
            })])

        # Insert data nodes between op nodes and insert data nodes with weights
        nodes = list(self.graph.nodes())
        for node in nodes:
            out_edges = Node(self.graph, node).get_outputs()
            data_nodes = {}
            for port in self.graph.node[node]['ports']:
                data = self.graph.unique_id(prefix='data_')
                self.graph.add_node(
                    data, **{
                        'kind': 'data',
                        'shape': self.graph.node[node]['ports'][port][0],
                        'value': None
                    })
                self.graph.add_edges_from([(node, data, {'out': port})])
                data_nodes.update({port: data})

            for out_node, edge_attrs in out_edges:
                self.graph.remove_edge(node, out_node)
                if edge_attrs['from_port'] in data_nodes:
                    data = data_nodes[edge_attrs['from_port']]
                else:
                    raise RuntimeError(
                        "SMTH wrong with IR! There is an edge from not existing port"
                    )
                self.graph.add_edges_from([(data, out_node, {
                    'in': edge_attrs['to_port']
                })])

    def __load_bin(self):
        bin_buff = np.fromfile(file=self.path_to_bin, dtype=np.uint8)
        graph = self.graph
        nodes = [node for node in graph.nodes()]
        hashes = defaultdict(dict)
        for node in nodes:
            for w in ['weights', 'biases', 'custom']:
                if w in graph.node[node]:
                    data = graph.unique_id(prefix='data_')
                    offset, size, in_port, precision = graph.node[node][w]
                    if Node(graph,
                            node).soft_get('type') == 'BinaryConvolution':
                        precision = np.uint8
                    value = np.frombuffer(buffer=bin_buff,
                                          dtype=precision,
                                          count=size,
                                          offset=offset)
                    hashes[graph.node[node]['name']][w] = hashlib.sha512(
                        value.tobytes()).hexdigest()
                    graph.add_node(
                        data, **{
                            'kind': 'data',
                            'value': value,
                            'shape': value.shape
                        })
                    graph.add_edges_from([(data, node, {'in': in_port})])
        self.graph.graph['hashes'].update(hashes)

    def __load_bin_hashes(self):
        graph = self.graph
        bin_hash_map = {
            name: blob_map.item(0)
            for name, blob_map in dict(
                np.load(self.path_to_bin, allow_pickle=True)).items()
        }

        for node in graph.nodes():
            for w in ['weights', 'biases', 'custom']:
                if w in graph.node[node]:
                    assert Node(graph, node).has_valid('name')
                    node_name = Node(graph, node).name
                    assert node_name in bin_hash_map and w in bin_hash_map[
                        node_name]
                    graph.node[node]['hashes'] = bin_hash_map[node_name][w]

    def __load_ir(self):
        self.__load_xml()
        if not self.path_to_bin:
            return

        if self.path_to_bin.endswith('.bin.hashes.npz'):
            self.__load_bin_hashes()
        else:
            self.__load_bin()

    def __load_layer(self, layer):
        """
            Layer example

            <layer id="1" name="862" precision="FP32" type="Convolution">
                <data dilation-x="1" dilation-y="1" group="1" kernel-x="1" kernel-y="5" output="32" pad-b="0" pad-r="2" pad-x="2" pad-y="0" stride-x="1" stride-y="1"/>
                <input>
                    <port id="0">
                        <dim>1</dim>
                        <dim>3</dim>
                        <dim>32</dim>
                        <dim>32</dim>
                    </port>
                </input>
                <output>
                    <port id="3">
                        <dim>1</dim>
                        <dim>32</dim>
                        <dim>32</dim>
                        <dim>32</dim>
                    </port>
                </output>
                <blobs>
                    <weights offset="0" size="1920"/>
                    <biases offset="1920" size="128"/>
                </blobs>
            </layer>

        """

        layer_id = layer.attrib['id']

        layer_attrs = layer.attrib
        layer_attrs.update({
            'ports': {},
            'restored_input_ports': {},
            'kind': 'op'
        })

        inputs_counter = 0

        for attr in layer:
            if attr.tag == 'data':
                new_attrs = self.__normalize_attrs(attr.attrib)
                new_attrs['ir_data_attrs'] = attr.attrib
                if layer.attrib['type'] == 'Const':
                    assert 'offset' in new_attrs and 'size' in new_attrs, \
                        'Incorrect attributes for Const layer, {} instead of {}!'.format(new_attrs.keys(), ['offset', 'size'])
                    precision = ""
                    for item in layer:
                        if item.tag == "output":
                            precision = item[0].attrib["precision"]
                            break
                    new_attrs.update(
                        self.__prepare_bin_attrs(layer, 0, 'custom',
                                                 new_attrs['offset'],
                                                 new_attrs['size'], precision))
                layer_attrs.update(new_attrs)
            elif attr.tag == 'input':
                inputs_counter = len(attr)

                input = attr
                for port in input:
                    port_id = int(port.attrib['id'])
                    input_shape = []
                    port_rt_info = {}
                    for dim in port:
                        if dim.tag == "dim":
                            input_shape.append(int(dim.text))
                        if dim.tag == 'rt_info':
                            for attr in dim:
                                port_rt_info.update(
                                    self.__read_rt_info_common(attr))

                    input_shape = shape_array([
                        d if d != -1 else dynamic_dimension_value
                        for d in input_shape
                    ])

                    in_tensor_names = None
                    if 'names' in port.attrib:
                        in_tensor_names = port.attrib['names']

                    # special attribute to pass information about operation input ports
                    layer_attrs['restored_input_ports'].update({
                        port_id: (input_shape, in_tensor_names, port_rt_info)
                    })
            elif attr.tag == 'output':
                output = attr
                for port in output:
                    port_id = int(port.attrib['id'])
                    output_shape = []
                    port_rt_info = {}
                    for dim in port:
                        if dim.tag == "dim":
                            output_shape.append(int(dim.text))
                        if dim.tag == 'rt_info':
                            for attr in dim:
                                port_rt_info.update(
                                    self.__read_rt_info_common(attr))

                    output_shape = shape_array([
                        d if d != -1 else dynamic_dimension_value
                        for d in output_shape
                    ])

                    out_tensor_names = None
                    if 'names' in port.attrib:
                        out_tensor_names = port.attrib['names']
                    # special attribute to pass information about operation input ports
                    # NOTE: renaming or structure changing of this attribute may have big impact on tests
                    layer_attrs['ports'].update({
                        port_id: (output_shape, out_tensor_names, port_rt_info)
                    })
            elif attr.tag == 'blobs':
                in_port = inputs_counter
                for blob_attr in attr:
                    layer_attrs.update(
                        self.__prepare_bin_attrs(
                            layer, in_port, blob_attr.tag,
                            blob_attr.attrib['offset'],
                            blob_attr.attrib['size'],
                            blob_attr.attrib.get('precision', None)))
                    in_port += 1
            elif attr.tag == 'body':
                xml_body_child = list(layer.iterfind('body'))
                assert len(xml_body_child) == 1

                body_ir, input_port_map, output_port_map, input_layers = \
                    self.__read_subgraph(layer, layer_attrs, xml_body_child, 'port_map')

                body_ir.input_node = input_layers[0]
                layer_attrs.update({'body': body_ir})
                layer_attrs.update({'input_port_map': input_port_map})
                layer_attrs.update({'output_port_map': output_port_map})

                xml_back_edges_map = list(layer.iterfind('back_edges'))
                if not len(xml_back_edges_map) == 1:
                    log.warning(
                        "TensorIterator body won\'t be compared due to missing back_edges section!"
                    )
                    continue
                xml_back_edges_map = xml_back_edges_map[0]

                back_edges = []

                for edge in xml_back_edges_map:
                    back_edges.append(self.__normalize_attrs(edge.attrib))

                layer_attrs.update({'back_edges': back_edges})

            elif attr.tag == 'then_body' or attr.tag == 'else_body':
                assert layer.attrib['type'] == 'If', "Incorrect IR! The operation {0}" \
                                                     " has sub-graphs for If operation"
                layer_attrs = self.__read_if(layer, layer_attrs)
                continue

            elif attr.tag == 'rt_info':
                layer_attrs = self.__read_rt_info(layer, layer_attrs)
                continue

        return layer_id, layer_attrs

    @staticmethod
    def __prepare_bin_attrs(xml_layer, in_port, tag, offset, size, precision):
        layer_attrs = dict()
        if precision is None:
            precision = xml_layer.attrib['precision']
        precision_map = {
            'FP32': (4, np.float32),
            'FP16': (2, np.float16),
            'I64': (8, np.int64),
            'I32': (4, np.int32),
            'I8': (1, np.int8),
            'U8': (1, np.uint8),
            'U1': (1, np.uint8),
            'U4': (1, np.uint8),
            'I4': (1, np.uint8),
            'BOOL': (1, np.bool),
            'BIN': (1, np.uint8),
            'U64': (8, np.uint64)
        }
        type_size, dtype = precision_map[precision]
        layer_attrs[tag] = (int(offset), int(size) // type_size, in_port,
                            dtype)
        return layer_attrs

    @staticmethod
    def __normalize_attrs(attrs: dict):
        """
        Normalize attributes for type 'data'.
        Replace " from values (not used right now) and make list of value with int, float or other types values.
        Example: {'order': '1,0,2'} -> {'order': [1, 0, 2]}
                 {'order': '1'}     -> {'order': 1}
        """
        normalized_attrs = {}
        for attr, value in attrs.items():
            value = value.replace('\"', '').replace(' ', '')
            value = value.split(',')
            n_value = []
            for val in value:
                if IREngine.__isint(val):
                    n_value.append(int(val))
                elif IREngine.__isfloat(val):
                    n_value.append(float(val))
                elif val in ['True', 'False', 'true', 'false']:
                    n_value.append(val in ['True', 'true'])
                else:
                    n_value.append(val)

            if len(n_value) == 1:
                normalized_attrs.update({attr: n_value[0]})
            else:
                normalized_attrs.update({attr: n_value})

        return normalized_attrs

    @staticmethod
    def __isfloat(value):
        try:
            float(value)
            return True
        except ValueError:
            return False

    @staticmethod
    def __isint(value):
        is_signed = value.startswith('+') or value.startswith('-')
        other_chars_are_digits = value[1:].isdigit()
        all_chars_are_digits = value.isdigit()
        return all_chars_are_digits or (is_signed and other_chars_are_digits)

    @staticmethod
    def __find_input(graph):
        inputs = []
        for node in sorted(graph.nodes()):
            node = Node(graph, node)
            if node.has_valid('type') and node.type in ('Input', 'Parameter'):
                inputs.append(node)

        if len(inputs) < 1:
            raise RuntimeError("Graph {} has less than one input node".format(
                graph.name))

        return inputs

    def compare(self, ref_net):
        if not isinstance(ref_net, IREngine):
            ir_input = self.__find_input(self.graph)[0]
            ref_input = self.__find_input(ref_net)[0]
            ref_graph = ref_net
        else:
            ir_input = self.input_node or self.__find_input(self.graph)[0]
            ref_input = ref_net.input_node or ref_net.__find_input(
                ref_net.graph)[0]
            ref_graph = ref_net.graph
        # TODO check that ir_input[0].id and ref_input[0].id are the same
        result, stderr = compare_graphs(graph=self.graph,
                                        graph_ref=ref_graph,
                                        last_node=ir_input.id,
                                        last_node_ref=ref_input.id,
                                        check_op_attrs=True)
        return result, stderr

    def generate_bin_hashes_file(self, path_for_file=None):
        # This function creates file with extension '.bin.hashes.npz' where hashes of bin exists.
        # For creating this file in custom filder use attribute path_for_file.
        # Where directory for file should be existed
        graph = self.graph
        if path_for_file is None:
            path_for_file = str(
                Path(self.path_to_xml).with_suffix('.bin.hashes.npz'))
        assert 'hashes' in graph.graph, "Loaded IR graph doesn't contain `hashes`: {}".format(
            self.path_to_xml)
        np.savez_compressed(path_for_file, **graph.graph['hashes'])
        return path_for_file

    def get_inputs(self):
        # Function return input nodes in dictionary: {input_node_name: input_node_shape, ...}
        input_nodes = self.__find_input(self.graph)
        return {
            input_node.name: input_node.out_node().shape
            for input_node in input_nodes
        }

    def __eq__(self, other):
        # To call this function create two IREngine objects (IR1, IR2) and compare them IR1 == IR2
        if not isinstance(other, IREngine):
            raise AttributeError(
                "IREngine can be compared only with IREngine object type")
        return self.compare(other)[0]

    def __read_subgraph(self, layer, layer_attrs, body_child, port_map_name):
        body_ir = IREngine(path_to_xml=None,
                           path_to_bin=self.path_to_bin,
                           xml_tree=ElementTree(body_child[0]))

        self.graph.graph['hashes'].update(body_ir.graph.graph['hashes'])

        xml_port_map = list(layer.iterfind(port_map_name))
        assert not len(xml_port_map) != 1, "If then_body won\'t be compared due to missing {1} section in node {0}! " \
            .format(layer_attrs['name'], port_map_name)
        xml_port_map = xml_port_map[0]

        input_layers = []
        input_port_map = []
        output_port_map = []

        for port in xml_port_map:
            if port.tag == 'input':
                if 'internal_layer_id' not in port.attrib:
                    log.warning(
                        "internal_layer_id attrib not found in input section")
                else:
                    input_layers.append(
                        Node(body_ir.graph, port.attrib['internal_layer_id']))
                    input_port_map.append(self.__normalize_attrs(port.attrib))
            elif port.tag == 'output':
                if 'internal_layer_id' not in port.attrib:
                    log.warning(
                        "internal_layer_id attrib not found in output section")
                else:
                    output_port_map.append(self.__normalize_attrs(port.attrib))

        return body_ir, input_port_map, output_port_map, input_layers

    def __read_if(self, layer, layer_attrs):

        xml_then_body_child = list(layer.iterfind('then_body'))
        xml_else_body_child = list(layer.iterfind('else_body'))
        assert len(xml_then_body_child) == 1 and len(
            xml_else_body_child) == 1, "If operation has only one subgraph"

        then_body_ir, then_input_port_map, then_output_port_map, _ = \
            self.__read_subgraph(layer, layer_attrs, xml_then_body_child, 'then_port_map')
        layer_attrs.update({'then_graph': then_body_ir})
        layer_attrs.update({'then_input_port_map': then_input_port_map})
        layer_attrs.update({'then_output_port_map': then_output_port_map})

        else_body_ir, else_input_port_map, else_output_port_map, _ = \
            self.__read_subgraph(layer, layer_attrs, xml_else_body_child, 'else_port_map')
        layer_attrs.update({'else_graph': else_body_ir})
        layer_attrs.update({'else_input_port_map': else_input_port_map})
        layer_attrs.update({'else_output_port_map': else_output_port_map})

        return layer_attrs

    def __read_rt_info(self, layer, layer_attrs):
        rt_info = RTInfo()
        xml_rt_info = list(layer.iterfind('rt_info'))[0]

        for attr in xml_rt_info:
            attr_name = attr.attrib['name']
            if attr_name == 'old_api_map_order':
                rt_info.info.update(
                    self.__read_old_api_map_order(attr, layer.attrib['type']))
            elif attr_name == 'old_api_map_element_type':
                rt_info.info.update(
                    self.__read_old_api_map_element_type(
                        attr, layer.attrib['type']))
            else:
                rt_info.info.update((self.__read_rt_info_common(attr)))

        layer_attrs.update({'rt_info': rt_info})
        return layer_attrs

    @staticmethod
    def __read_old_api_map_order(attr, layer_type):
        version = int(attr.attrib['version'])
        order = list(map(int, attr.attrib['value'].split(',')))
        old_api_map = OldAPIMapOrder(version=version)
        if layer_type == 'Parameter':
            old_api_map.old_api_transpose_parameter(order)
        elif layer_type == 'Result':
            old_api_map.old_api_transpose_result(order)
        else:
            raise AttributeError(
                "Cannot read old_api_map for layer of type: {}".format(
                    layer_type))

        return {('old_api_map_order', version): old_api_map}

    @staticmethod
    def __read_old_api_map_element_type(attr, layer_type):
        version = int(attr.attrib['version'])
        element_type = destination_type_to_np_data_type(attr.attrib['value'])
        old_api_map = OldAPIMapElementType(version=version)
        old_api_map.set_legacy_type(element_type)
        return {('old_api_map_element_type', version): old_api_map}

    @staticmethod
    def __read_rt_info_common(attr):
        attr_name = attr.attrib['name']
        version = int(attr.attrib['version'])
        rt_info = OrderedDict()
        for key in attr.attrib:
            if key not in ('name', 'version'):
                rt_info[key] = attr.attrib[key]
        return {(attr_name, version): rt_info}
Exemplo n.º 18
0
def concat_convolutions(graph: Graph, start_node: Node, last_node: Node):
    """
    This function converts group of convolutions into one
    """

    # Check that concatenation makes in the same order
    conv_nodes = get_next_operation(start_node)
    assert len(conv_nodes) == len(last_node.in_nodes())
    gconv = conv_nodes[0]

    for id in range(len(conv_nodes)):
        conv = conv_nodes[id]
        if conv.out_node().id != last_node.in_node(id).id:
            return False
        # Check that all convolutions have same weights shapes
        if not np.array_equal(conv.in_node(1).shape, gconv.in_node(1).shape):
            log.debug(
                'Grouped convolutions fusion : convolutions have different weights shape'
            )
            return False

    # Check that split and concat dims are valid
    channel_dim = gconv.channel_dims[0]
    split_axis = start_node.in_port(1).data.get_value()
    if channel_dim != split_axis or channel_dim != last_node.axis:
        log.debug(
            'Grouped convolutions fusion : split or concat has weird axis!')
        return False

    # Check that all convolutions has the same parameters
    conv_attrs = ['pad', 'stride']
    for attr in conv_attrs:
        for id in range(len(conv_nodes)):
            conv = conv_nodes[id]
            if not np.array_equal(gconv[attr], conv[attr]):
                log.debug(
                    'Grouped convolutions fusion : attrs {} doesn\'t match'.
                    format(attr))
                return False

    # Check that all Convolutions has biases (if exists)
    has_biases = False
    for id in range(len(conv_nodes)):
        conv = conv_nodes[id]
        if len(conv.in_nodes()) == 3:
            if not has_biases:
                has_biases = True
        elif has_biases:
            return False  # All convolution mast have biases

    # Check that all biases have same shape
    if has_biases:
        for id in range(len(conv_nodes)):
            conv = conv_nodes[id]
            if conv.in_node(2).shape != gconv.in_node(2).shape:
                log.debug(
                    'Group convolutions fusion : convolutions have different biases shape {} and {}'
                    .format(conv.in_node(2).shape,
                            gconv.in_node(2).shape))
                return False

    graph.remove_edge(gconv.in_node(0).id, gconv.id)
    graph.remove_edge(gconv.id, gconv.out_node().id)

    input = start_node.in_node(0)
    output = last_node.out_node()

    # Removing edges from data nodes to Split and Concat
    graph.remove_edge(input.id, start_node.id)
    graph.remove_edge(last_node.id, output.id)

    # Add edges to grouped convolution
    graph.add_edges_from([(input.id, gconv.id, {
        'in': 0
    }), (gconv.id, output.id, {
        'out': 0
    })])

    # Concatenation of convolutions
    weights_node = gconv.in_node(1)
    bias_node = gconv.in_node(2) if has_biases else None

    weights_value = mo_array(weights_node.value)
    bias_value = mo_array(bias_node.value) if has_biases else None

    # gconv.get_weights_permute.perm contains permutation indices
    # where feature dimension is set to zero position, so 0 value
    # in gconv.get_weights_permute.inv indicates original feature dimension index
    feature_dim = np.where(gconv.get_weights_permute.inv == 0)[0][0]

    for conv in conv_nodes[1:]:
        weights_value = np.concatenate((weights_value, conv.in_node(1).value),
                                       axis=feature_dim)
        if has_biases:
            bias_value = np.concatenate((bias_value, conv.in_node(2).value),
                                        axis=-1)  # Not validated

    weights_node.value = mo_array(weights_value)
    weights_node.shape = mo_array(weights_value.shape)

    if has_biases:
        bias_node.value = mo_array(bias_value)
        bias_node.shape = mo_array(bias_value.shape)

    log.debug('Start node : {} Last node : {}  Nodes inside : {}'.format(
        start_node.id, last_node.id, len(start_node.out_nodes())))
    log.debug('Output shape : {}'.format(weights_value.shape))

    gconv.group = len(conv_nodes)
    gconv.output = weights_node.shape[feature_dim]
    gconv.output_shape[feature_dim] = weights_node.shape[feature_dim]

    return True
Exemplo n.º 19
0
    def add_reshape_after_data_node(graph: Graph, data_node_name: str):
        """
        Adds reshape operation which changes shape of the tensor produced by TFSubgraphCall from 4D to real dimension
        of the tensor. The data_node_name node contains real dimensions of the tensor but they will be changed in the
        add_reshapes_for_tf_subgraph_calls function to a 4D because IE TF call layer supports output in 4D only.
        :param graph: graph to operate on.
        :param data_node_name: name of the data node to be reshaped to correct dimensions.
        :return: None
        """
        data_node = Node(graph, data_node_name)

        # if the data node was previously marked as output then we need to mark as output new reshaped data node
        is_out_node = False
        if len(data_node.out_nodes()) == 1 and data_node.out_node().has(
                'op') and data_node.out_node().op == 'Result':
            is_out_node = True
            graph.remove_node(data_node.out_node().id)

        # save old consumers nodes with edge attributes
        old_consumer_nodes_with_attrs = list()
        for index, out_op in enumerate(data_node.out_nodes()):
            edge_attrs = graph.get_edge_data(data_node_name, out_op.name)[0]
            old_consumer_nodes_with_attrs.append((out_op.name, edge_attrs))

        # remove old consumers from the data node
        for out_op in list(data_node.out_nodes()):
            graph.remove_edge(data_node_name, out_op.name)

        # reshape operation node
        reshape_node_name = graph.unique_id("Reshape_")
        graph.add_node(reshape_node_name,
                       kind='op',
                       type='Reshape',
                       name=reshape_node_name,
                       op='Reshape',
                       data_type=data_node['data_type'])
        update_ie_fields(graph.node[reshape_node_name])

        # reshape shape data node
        reshape_shape_data_node_name = graph.unique_id("Reshape_shape_")
        graph.add_node(reshape_shape_data_node_name,
                       kind='data',
                       name=reshape_shape_data_node_name,
                       value=mo_array(data_node['shape']),
                       shape=[1])

        # reshaped data node
        reshaped_value = None
        if data_node['value'] is not None:
            reshaped_value = mo_array(data_node['value'])
        reshaped_data_node_name = graph.unique_id("reshaped_data_")
        graph.add_node(reshaped_data_node_name,
                       kind='data',
                       name=reshaped_data_node_name,
                       shape=mo_array(data_node['shape']),
                       value=reshaped_value,
                       nchw_layout=True)

        if is_out_node:
            add_opoutput(graph, reshaped_data_node_name, 0, False)

        graph.add_edges_from([
            (data_node_name, reshape_node_name, {
                'in': 0
            }),
            (reshape_shape_data_node_name, reshape_node_name, {
                'in': 1
            }),
            (reshape_node_name, reshaped_data_node_name, {
                'out': 0
            }),
        ])

        for out_node_name, edge_attrs in old_consumer_nodes_with_attrs:
            graph.add_edges_from([(reshaped_data_node_name, out_node_name,
                                   edge_attrs)])
Exemplo n.º 20
0
def muladd_to_scaleshift_action(graph: Graph, match: dict):
    mul = match['mul']
    add = match['add']
    output = match['output']

    # Pass works correctly only in case when node have only 1 output
    if len(mul.out_port(0).get_destinations()) > 1:
        return

    if mul.soft_get('can_be_scaleshift') is False or add.soft_get('can_be_scaleshift') is False:
        return

    mul_weights_id = get_value_id(mul)
    mul_input_id = get_tensor_id(mul)
    add_weights_id = get_value_id(add)

    if mul_weights_id is None:
        log.debug("Mul->Add to ScaleShift: Mul {} has no weights".format(mul.name))
        return
    if mul_input_id is None:
        log.debug("Mul->Add to ScaleShift: Mul {} has no input".format(mul.name))
        return
    if add_weights_id is None:
        log.debug("Mul->Add to ScaleShift: Add {} has no weights".format(add.name))
        return

    input = mul.in_node(mul_input_id)
    weights = mul.in_node(mul_weights_id)
    bias = add.in_node(add_weights_id)

    # Transform values
    weights.value = np.squeeze(weights.value)
    weights.shape = int64_array(weights.value.shape)

    bias.value = np.squeeze(bias.value)
    bias.shape = int64_array(bias.value.shape)

    # Broadcast weights if they are scalar
    if weights.value.ndim == 0 and bias.value.ndim == 1:
        weights.value = np.full(bias.shape, weights.value.item(), dtype=weights.value.dtype)
        weights.shape = int64_array(weights.value.shape)

    if bias.shape != weights.shape:
        log.warning('Mul->Add to ScaleShift conversion stopped {} != {}'.format(weights.shape, bias.shape))
        return

    if bias.value.ndim != weights.value.ndim or bias.value.size != weights.value.size:
        log.debug("Skipping Mul->Add to ScaleShift conversion for nodes {}, {} because of different weights "
                  "and biases".format(mul.name, add.name))
        return

    if bias.value.size == 1 and weights.value.size == 1:
        log.debug("Skipping Mul->Add to ScaleShift conversion for nodes {}, {}. Will be converted to Power"
                  "".format(mul.name, add.name))
        return

    op_name = "ScaleShift"

    log.debug("Fusing Mul->Add to {}. Input nodes: {} and {}, bias.shape = {}, weights.shape = {}"
              "".format(op_name, mul.id, add.id, bias.shape, weights.shape))

    graph.remove_edge(input.node, mul.id)
    graph.remove_edge(weights.node, mul.id)
    graph.remove_edge(bias.node, add.id)
    graph.remove_edge(add.node, output.id)

    op_node = graph.unique_id(mul.name + '/Fused{}_'.format(op_name))

    graph.add_node(op_node, **add_attrs_props(dict(kind='op', type=op_name, name=op_node, op=op_name,
                                                   data_type=input.data_type)))
    scsh = Node(graph, op_node)
    scsh.add_input_port(0)
    scsh.add_input_port(1)
    scsh.add_input_port(2)
    scsh.add_output_port(0)

    update_ie_fields(graph.node[op_node])

    graph.add_edges_from([
        (input.node, op_node, {'in': 0}),
        (weights.node, op_node, {'in': 1, 'bin': 'weights'}),
        (bias.node, op_node, {'in': 2, 'bin': 'biases'}),
        (op_node, output.node, {'out': 0})
    ])

    return
Exemplo n.º 21
0
def partial_infer(graph: Graph, start_node: str = None):
    """
    Tries to execute constant parts of the graph and deduce as much as possible
    information following the data flow, e.g. calculate and propagate shapes and
    constant values. Partially or completely defined values are stored in data
    nodes (kind='data').
    """
    # We have to turn off strict mode due to above we add and remove edeges without attributes that is prohibited
    graph.strict_mode = False
    cycle_nodes = graph.get_nodes_with_attributes(is_cyclic=True)
    cycle_nodes = [Node(graph, node).out_node().id for node in cycle_nodes]
    ebunch_cyclic = list(
        graph.out_edges(nbunch=cycle_nodes, data=True, keys=True))
    ebunch_reconnected = exit_bound_edges(graph,
                                          sources=cycle_nodes,
                                          end_node_attrs={'op': 'Exit'})
    graph.remove_edges_from(ebunch_cyclic)
    graph.add_edges_from(ebunch_reconnected)

    try:
        nodes = list(nx.topological_sort(graph))
    except:
        raise Error('Graph contains a cycle. Can not proceed. ' +
                    refer_to_faq_msg(97))

    graph.remove_edges_from(ebunch_reconnected)
    graph.add_edges_from(ebunch_cyclic)
    graph.strict_mode = True

    # Mark all nodes as not inferred yet
    if start_node is not None:
        start_index = nodes.index(start_node)
        nx.set_node_attributes(G=graph.subgraph(nodes[start_index:]),
                               name='is_partial_inferred',
                               values=False)
    else:
        nx.set_node_attributes(G=graph,
                               name='is_partial_inferred',
                               values=False)

    nx.set_node_attributes(
        G=graph,
        name='executable',
        values={n: True
                for n in graph.get_nodes_with_attributes(kind='data')})

    # first we infer constant sub-graphs so the reverse infer could use constant values sub-graphs. For example,
    # convolution weights may be reshuffled by some operation in the graph and are not directly consumed by the conv
    # node
    infer_nodes(graph, nodes, True)

    # we may need to deduce shape for Parameter node(s) if it is not defined
    need_reverse_infer = False
    for parameter in graph.get_op_nodes(op='Parameter'):
        if parameter.soft_get('shape', None) is None:
            need_reverse_infer = True

    if need_reverse_infer:
        reverse_infer(graph, nodes)

    infer_nodes(graph, nodes, False)

    not_fully_inferred = graph.get_nodes_with_attributes(
        is_not_fully_inferred=True)
    for n in not_fully_inferred:
        node = Node(graph, n)
        if node.has_and_set('infer'):
            node.infer(node)

    return graph