def lift_up_through_eltwise(node: Node, reverse_channels: Node): r""" BEFORE AFTER previous_op previous_op' \ / previous_op previous_op' ReverseChannels ReverseChannels \ / \ / Eltwise Eltwise | | ReverseChannels next_op | next_op returns two objects: first - boolean value whatever we should continue propagating current ReverseChannels operation up or not second - list of new ReverseChannels operations that were produced while propagating reverse_channels up """ before_shape = reverse_channels.in_port(0).data.get_shape() port_axis = [] for idx, port in node.in_ports().items(): shape = port.data.get_shape() non_one_dims = np.where(shape != 1)[0] if shape[reverse_channels.axis] == 1: continue # nothing to flip for this input if len(non_one_dims) == 1 and shape[non_one_dims.item()] == reverse_channels.order.size: axis = non_one_dims.item() elif np.array_equal(before_shape, shape): axis = reverse_channels.axis else: # shape has multiple non-one values and shape is not fully broadcasted to value port shape # it is safe not to propagate reverse channels return False, [] port_axis.append((port, axis)) copies = [] for port, axis in port_axis: reverse_channels_copy = reverse_channels.copy_node({'axis': mo_array(axis)}) src = port.get_connection().get_source() if src.node.soft_get('type') == 'Parameter': # For Parameter nodes tensor debug attributes should not move to the last node # of subgraph. It is needed for the proper mapping of input framework name. # For this reason "source" mode is used to keep tensor debug attributes at Parameter node. port.get_connection().set_source(reverse_channels_copy.out_port(0), attributes_save_mode="source") else: port.get_connection().set_source(reverse_channels_copy.out_port(0)) src.connect(reverse_channels_copy.in_port(0)) copies.append(reverse_channels_copy) reverse_channels.out_port(0).get_connection().set_source( reverse_channels.in_port(0).get_connection().get_source()) reverse_channels.in_port(0).disconnect() # propagated reverse_channels successfully through current node, will continue propagation return True, copies
def quantize_data(fake_quantize: Node, dst_type: type, quantized_type: type, mode: str): graph = fake_quantize.graph name = fake_quantize.soft_get('name', fake_quantize.id) levels = fake_quantize.levels quantize = fake_quantize.copy_node( dict(name=name + '/Copy', stop_value_propagation=False), graph) fake_quantize.in_port(0).get_connection().set_destination( quantize.in_port(0)) # inherit input limits fake_quantize.in_port(1).get_connection().set_destination( quantize.in_port(1)) fake_quantize.in_port(2).get_connection().set_destination( quantize.in_port(2)) # calculate output limits for quantized weights assert mode in ["signed", "unsigned"] i_min_value = -(levels // 2) if mode == "signed" else 0 i_min = mo_array(i_min_value, dtype=dst_type) if not quantize.in_node( 0).shape.size else mo_array([i_min_value], dtype=dst_type) i_max = mo_array(levels + i_min - 1, dtype=dst_type) assert i_max - i_min == levels - 1 out_low = Const(graph, dict(name=name + '/Copy/out_low', value=i_min)).create_node() out_high = Const(graph, dict(name=name + '/Copy/out_high', value=i_max)).create_node() out_low.out_port(0).connect(quantize.in_port(3)) out_high.out_port(0).connect(quantize.in_port(4)) out_low.out_port(0).connect(fake_quantize.in_port(1)) out_high.out_port(0).connect(fake_quantize.in_port(2)) original_const = quantize.in_port(0).get_source().node quantized_data_name = original_const.soft_get( 'name', original_const.id) + '/quantized' cast = Cast( graph, dict(name=quantized_data_name, dst_type=quantized_type, stop_value_propagation=False)).create_node() quantize.out_port(0).connect(cast.in_port(0)) cast.out_port(0).connect(fake_quantize.in_port(0))
def _fuse_mul(graph: Graph, node: Node, fuse_nodes: list, backward: bool = True): """ This function takes Mul node and array of convolution/fc nodes for further fusion Parameters ---------- x : bool If backward is False, that means that Convolution/FC goes after Mul node else means that Mul goes after Convolutions/FC :param backward: :param fuse_nodes: :param node: :param graph: """ is_fused = False const_port, tensor_port = get_value_in_port(node), get_tensor_in_port(node) if const_port is None or tensor_port is None: log.warning( 'Cannot do fuse_mul for node {} because this node has wrong inputs' .format(node.id)) return False for fuse_node in fuse_nodes: if fuse_node.soft_get('can_be_fused') is False: log.warning( 'Node {} can\'t be used in fusing because attr can_be_fused = False' .format(fuse_node.name)) return False if len(fuse_node.in_ports()) < 2: log.warning('Node {} has no weights node'.format(fuse_node.name)) return False if not backward and not fuse_node.has_valid('layout'): log.warning('Node {} has no layout attr'.format(fuse_node.name)) return False weights_port = fuse_node.in_port(1) if not weights_port.data.has_valid('output_channel_dim') or \ not weights_port.data.has_valid('input_channel_dim'): log.warning( 'Cannot do fuse_mul for node {} because there is no field ' + 'output_channel_dim and/or input_channel_dim in weights.'. format(fuse_node.soft_get('name'))) return False inp_ch = weights_port.data.get_attr('input_channel_dim') out_ch = weights_port.data.get_attr('output_channel_dim') if max(inp_ch, out_ch) >= len(weights_port.data.get_shape()): log.warning('Node {} has wrong weights shape'.format( fuse_node.name)) return False for fuse_node in fuse_nodes: weights_port = fuse_node.in_port(1) value = mo_array(const_port.data.get_value()) value = np.squeeze(value) # TODO : ch_dim should be equal to node.in_node(1).value.shape # We will multiply weights according output/input channel dimension ch_dim = weights_port.data.get_attr( 'output_channel_dim' if backward else 'input_channel_dim') shape = mo_array([weights_port.data.get_shape()[ch_dim]]) # Scalar broadcast if value.size == 1: value = np.full(shape, value.item(), dtype=value.dtype) # Common broadcast for forward fusion if not backward: cnt = shape[-1] / value.shape[0] if fuse_node.layout == 'NCHW': tmp = mo_array([], dtype=value.dtype) for val in value: tmp = np.concatenate((tmp, np.repeat(val, cnt))) value = mo_array(tmp) else: value = np.tile(value, int(cnt)) # Expand dims for multiplication (ex. [38] to [38, 1, 1]) wdims_number = weights_port.data.get_attr('dims_number') for x in range(wdims_number - ch_dim - 1): shape = np.append(shape, 1) mul_val = mo_array(value) # If the value fails to reshape to the provided shape, skip fusing. # This can happen in case of group != 1 of the convolution. try: value = np.reshape(value, shape) except ValueError: log.error( "Cannot fuse const from {} to {}. Reshape failed. Skipping.". format(node.soft_get('name', node.id), fuse_node.soft_get('name', fuse_node.id)), extra={'is_warning': True}) return False # Weights multiplication mul_name = node.name + '_copy' mul_const = Const(graph, { 'value': value, 'name': mul_name + '/const' }).create_node() w_mul = node.copy_node({ 'name': mul_name, 'in_ports_count': len(node.in_ports()), 'out_ports_count': len(node.out_ports()), 'can_be_fused': False }) w_mul.in_port(const_port.idx).connect(mul_const.out_port(0)) w_const = weights_port.get_source() weights_port.get_connection().set_source(w_mul.out_port(0)) w_const.connect(w_mul.in_port(tensor_port.idx)) fuse_node_in_data = fuse_node.in_node(weights_port.idx) w_const_out_data = w_const.node.out_node(w_const.idx) # During this reconnection new data node name is copied from the data node # outgoing from w_const port. Duplicate names of data nodes lead to appearing # of duplicate op node names after constant folding. So we should manually # set a unique name for the new data node. if fuse_node_in_data.soft_get('name') == w_const_out_data.soft_get('name') and \ fuse_node_in_data.soft_get('name', None) is not None: fuse_node.in_node( weights_port.idx)['name'] = graph.unique_id(mul_name) # If we fuse in backward direction we should multiply biases if they exists if backward and len(fuse_node.in_ports()) == 3 and not fuse_node.in_port(2).disconnected() and \ not fuse_node.has_and_set('shape_input'): conv_bias = fuse_node.in_port(2) conv_bias.data.set_value(conv_bias.data.get_value() * np.squeeze(mul_val)) mul_const.infer(mul_const) w_mul.infer(w_mul) log.debug('Fused: {} to {}'.format(node.name, fuse_node.name)) is_fused = True if is_fused: # Delete Mul node producer_port = tensor_port.get_source() tensor_port.disconnect() const_port.disconnect() # as Mul node is added before convolution, output tensor from Convolution node # corresponds to original Mul node node.out_port(0).get_connection().set_source(producer_port, "dest") return is_fused