def version_1(cls, node: TFLiteNode, **kwargs): node_opts = node.get_options(Conv2DOptions) G = kwargs['G'] opts = kwargs['opts'] all_nodes = kwargs['all_nodes'] inputs = [all_nodes[t] for t in node.input] x = inputs[0] x_shape = x[2].shape in_b, h, w, in_c = tuple(x_shape) filt = inputs[1] weights_node = filt[0] filt_shape = filt[2].shape # ['in_c', 'h', 'w', 'out_c'] filt_out_c, filt_h, filt_w, filt_in_c = tuple(filt_shape) # get filter dimensions if filt_h > h or filt_w > w: LOG.warning( "Filter %s of shape [%dx%d] is bigger than input of shape [%dx%d]", node.name, filt_h, filt_w, h, w) filt_dim = Conv2DFilterDim(filt_h, filt_w, filt_out_c, in_c=filt_in_c) filt_dim = filt_dim.impose_order(cls.TF_LITE_FILTER_ORDER) # compute padding pad = cls.get_tf_padding(node_opts.Padding()) # does it have biases if len(inputs) > 2: bias = inputs[2] bias_node = bias[0] else: bias_node = ConstantInputParameters( f'{node.name}_bias', dims=Dim.unnamed([filt_out_c]), value=np.zeros([filt_out_c], dtype=np.float32)) # TODO - check params = Conv2DParameters( node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim(node_opts.DilationHFactor(), node_opts.DilationWFactor()), padding=pad, has_bias=True, in_dims_hint=SparseList([['h', 'w', 'c'], cls.TF_LITE_FILTER_ORDER.copy(), ['out_c']]), out_dims_hint=SparseList([['h', 'w', 'c']]), constant_store=G.constant_store) G.add_edge(NNEdge(from_node=weights_node, to_node=params, to_idx=1)) G.add_edge(NNEdge(from_node=bias_node, to_node=params, to_idx=2)) cls.new_load_filter_parameters(G, params, node.input[0], weights_node, bias_node, node.output[0], opts) # if opts.get('load_dequantized'): # weights_node.value, bias_node.value = cls.load_dequantized_filter_parameters( # node.input, bias_node.value) # else: # qrec, weights_node.value, bias_node.value = cls.load_filter_parameters(G, params, node.input, bias_node.value, # node.output, opts) # if qrec: # G.quantization[NodeId(weights_node)].out_qs[0] = qrec.in_qs[1] # G.quantization[NodeId(bias_node)].out_qs[0] = qrec.in_qs[2] in_dim = Dim.named_ordered(h=h, w=w, c=in_c) out_dims = params.get_output_size( [in_dim, Dim.unnamed(filt_dim.shape), Dim.unnamed([filt_out_c])]) pout_dims = ProvisionalDim([in_b] + out_dims[0].shape) G.add_edge( NNEdge(from_node=x[0], to_node=params, from_idx=x[1], to_idx=0)) params = cls.fuse_activation(node_opts, node.name, params, **kwargs) all_nodes[node.output[0]] = (params, 0, pout_dims) return params
def _common(cls, node: TFLiteNode, **kwargs): node_opts = node.get_options(DepthwiseConv2DOptions) G = kwargs['G'] opts = kwargs['opts'] all_nodes = kwargs['all_nodes'] inputs = [all_nodes[t] for t in node.input] x = inputs[0] x = cls.remove_known_batch_dimension(G, x, node) x_shape = x[2].shape in_b, h, w, in_c = tuple(x_shape) filt = inputs[1] weights_node = filt[0] filt_shape = filt[2].shape # ['in_c', 'h', 'w', 'out_c'] filt_in_c, filt_h, filt_w, filt_out_c = tuple(filt_shape) # get filter dimensions if filt_h > h or filt_w > w: LOG.warning( "Filter %s of shape [%dx%d] is bigger than input of shape [%dx%d]", node.name, filt_h, filt_w, h, w) filt_dim = Conv2DFilterDim(filt_h, filt_w, filt_out_c, in_c=filt_in_c) filt_dim = filt_dim.impose_order(cls.TF_LITE_DW_FILTER_ORDER) # multiplier should match filter check(filt_dim.out_c == node_opts.DepthMultiplier() * in_c, "invalid multiplier") groups = filt_dim.out_c // node_opts.DepthMultiplier() # compute padding pad = cls.get_tf_padding(node_opts.Padding()) # does it have biases if len(inputs) > 2: bias = inputs[2] bias_node = bias[0] else: bias_node = ConstantInputParameters( f'{node.name}_bias', dims=Dim.unnamed([filt_out_c]), value=np.zeros([filt_out_c], dtype=np.float32)) # TODO - check # TFLITE produces single channel input DW convolutions with the # multiplier equal to the number of out channels. This is just # a normal convolution and since we don't handle the channel # multiplier at present (but can) just convert them to normal # convolutions convert_to_conv = in_c == 1 and groups == 1 if convert_to_conv: filt_dim.impose_order(cls.TF_LITE_FILTER_ORDER) params = Conv2DParameters( node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim(node_opts.DilationHFactor(), node_opts.DilationWFactor()), padding=pad, has_bias=True, in_dims_hint=[['h', 'w', 'c'], cls.TF_LITE_FILTER_ORDER.copy(), ['out_c']], out_dims_hint=[['h', 'w', 'c']]) else: filt_dim.impose_order(cls.TF_LITE_DW_FILTER_ORDER) params = Conv2DParameters( node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim(node_opts.DilationHFactor(), node_opts.DilationWFactor()), padding=pad, groups=groups, multiplier=node_opts.DepthMultiplier(), has_bias=True, tf_depthwise=True, in_dims_hint=[['h', 'w', 'c'], cls.TF_LITE_DW_FILTER_ORDER.copy(), ['out_c']], out_dims_hint=[['h', 'w', 'c']]) G.add_edge(NNEdge(from_node=weights_node, to_node=params, to_idx=1)) G.add_edge(NNEdge(from_node=bias_node, to_node=params, to_idx=2)) cls.new_load_filter_parameters(G, params, params.filter.actual_shape, params.filter.get_order_idx('out_c'), node.input[0], weights_node, bias_node, node.output[0], opts, dw_to_pw=convert_to_conv) in_dim = Dim.named_ordered(h=h, w=w, c=in_c) out_dims = params.get_output_size( [in_dim, Dim.unnamed(filt_dim.shape), Dim.unnamed([filt_out_c])]) pout_dims = ProvisionalDim([in_b] + out_dims[0].shape) G.add_edge( NNEdge(from_node=x[0], to_node=params, from_idx=x[1], to_idx=0)) params = cls.fuse_activation(node_opts, node.name, params, **kwargs) all_nodes[node.output[0]] = (params, 0, pout_dims) return params
def _common(cls, node: TFLiteNode, **kwargs): node_opts = node.get_options(ConcatenationOptions) G = kwargs['G'] opts = kwargs['opts'] all_nodes = kwargs['all_nodes'] inputs = [all_nodes[t] for t in node.input] inp_shapes = [input[2].shape for input in inputs] buffer_idxes = [tensor.buffer_idx for tensor in node.input] non_zero_idxes = [idx for idx in buffer_idxes if idx != 0] duplicates = [ idx for idx, count in Counter(non_zero_idxes).items() if count > 1 ] if duplicates: LOG.warning( f'concat {node.name} has duplicate inputs. Inserting copies but this is not very efficient.' ) for idx in duplicates: dup_idxes = [i for i, x in enumerate(buffer_idxes) if x == idx] for dup_idx in dup_idxes[1:]: cparams = CopyParameters( G.unique_name( f'{node.name}_dup_{dup_idxes[0]}_{dup_idx}')) dup_inp = inputs[dup_idx] G.add_edge( NNEdge(from_node=dup_inp[0], from_idx=dup_inp[1], to_node=cparams)) inputs[dup_idx] = tuple([cparams, 0] + list(dup_inp[2:])) axis = node_opts.Axis() if any(inp_shape[axis] is None for inp_shape in inp_shapes): raise ValueError("concat on undefined axis in node %s" % node.name) def red_func(x, y): return y.copy() if x is None else [ (elem if y[idx] is not None and elem is not None else None) if idx != axis else elem + y[axis] for idx, elem in enumerate(x) ] pout_shape = reduce(red_func, inp_shapes) if all(cls.is_constant(inp) for inp in inputs): # cls.remove_none_from_constants(inputs, pout_shape) LOG.info("reducing %s to a constant", node.name) value = np.concatenate([cls.get_constant(inp) for inp in inputs], axis=axis) params = ConstantInputParameters(node.name, value=value) else: axis -= sum(1 if dim is None else 0 for dim in pout_shape[:axis:]) params = ConcatParameters(node.name, axis=axis, axis_hint=None) for idx, inp in enumerate(inputs): inp_node, inp_idx = cls._maybe_insert_reshape( G, inp, inp_shapes[idx], pout_shape) G.add_edge( NNEdge(from_node=inp_node, to_node=params, from_idx=inp_idx, to_idx=idx)) if opts.get('load_quantization'): G.quantization[NodeId(params)] = cls.load_tf_quantization( node.input, node.output) cls.fuse_activation(node_opts, node.name, params, **kwargs) all_nodes[node.output[0]] = (params, 0, ProvisionalDim(pout_shape)) return params
def version_1(cls, node: TFLiteNode, **kwargs): node_opts = node.get_options(Conv2DOptions) G = kwargs['G'] opts = kwargs['opts'] all_nodes = kwargs['all_nodes'] inputs = [all_nodes[t] for t in node.input] x = inputs[0] x_shape = x[2].shape in_b, h, w, in_c = tuple(x_shape) filt = inputs[1] filt_tensor = node.input[1] filt_shape = filt[2].shape # ['in_c', 'h', 'w', 'out_c'] filt_out_c, filt_h, filt_w, filt_in_c = tuple(filt_shape) # get filter dimensions filt_tensor.used = True if filt_h > h or filt_w > w: LOG.warning( "Filter %s of shape [%dx%d] is bigger than input of shape [%dx%d]", node.name, filt_h, filt_w, h, w) filt_dim = Conv2DFilterDim(filt_h, filt_w, filt_out_c, in_c=filt_in_c) filt_dim = filt_dim.impose_order(cls.TF_LITE_FILTER_ORDER) # compute padding pad = cls.get_tf_padding(node_opts.Padding()) # does it have biases has_bias = len(inputs) > 2 if has_bias: node.input[2].used = True params = Conv2DParameters(node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim( node_opts.DilationHFactor(), node_opts.DilationWFactor()), padding=pad, has_bias=has_bias, in_dims_hint=SparseList([['h', 'w', 'c']]), out_dims_hint=SparseList([['h', 'w', 'c']]), constant_store=G.constant_store) if opts.get('load_dequantized'): cls.load_dequantized_filter_parameters(params, node.input) else: cls.load_filter_parameters(G, params, node.input, node.output, opts) in_dim = Dim.named_ordered(h=h, w=w, c=in_c) out_dims = params.get_output_size([in_dim]) pout_dims = ProvisionalDim([in_b] + out_dims[0].shape) G.add_edge( NNEdge(from_node=x[0], to_node=params, from_idx=x[1], to_idx=0)) params = cls.fuse_activation(node_opts, node.name, params, **kwargs) all_nodes[node.output[0]] = (params, 0, pout_dims) return params
def new_load_filter_parameters(cls, G, params, filter_shape, filter_scale_axis, input_tensor, weights_node, bias_node, output_tensor, opts, dw_to_pw=False): weights_node.meta['filter_params'] = True bias_node.meta['filter_params'] = True # if quantizaton is not loaded then the constants will already be dequantized if dw_to_pw: # Conv has been converted from depthwise to pointwise so reorder the weights tensor weights_node.value = np.transpose(weights_node.value, cls.TF_LITE_DW_FILTER_TRANSPOSE) weights_node.dims = Dim.unnamed(weights_node.value.shape) if not opts.get('load_quantization'): return wqtype = weights_node.qtype if wqtype is None: LOG.warning('quantization is missing on node %s', params.name) return # scale weights as requested. change asymmetric and/or unsigned weights to signed symmetric if wqtype.asymmetric or not wqtype.signed: if opts.get('rescale_perchannel'): wqtype = cls.get_weights_qtype_by_channel( filter_shape, filter_scale_axis, weights_node) else: wqtype = cls.get_weights_qtype_by_tensor(weights_node) else: if opts.get('rescale_perchannel'): if len(wqtype.scale) != filter_shape[filter_scale_axis]: wqtype = cls.get_weights_qtype_by_channel( filter_shape, filter_scale_axis, weights_node) else: if len(wqtype.scale) > 1: wqtype = cls.get_weights_qtype_by_tensor(weights_node) iqtype = input_tensor.qtype # correct input qtype to symmetric tensor scaled if iqtype.asymmetric or not iqtype.signed or len(iqtype.scale) > 1: iqtype = QType.from_min_max_sq(min_val=iqtype.min_val, max_val=iqtype.max_val) else: iqtype = deepcopy(iqtype) oqtype = output_tensor.qtype # correct output qtype to symmetric tensor scaled if oqtype.asymmetric or not oqtype.signed or len(oqtype.scale) > 1: oqtype = QType.from_min_max_sq(min_val=oqtype.min_val, max_val=oqtype.max_val) else: oqtype = deepcopy(oqtype) # dqbias = bias_node.dqvalue bias_scale = (iqtype.scale * wqtype.scale).astype(np.float32) bqtype = QType(dtype=np.int32, scale=bias_scale) # NOTE: In some tensorflow graphs the biases are hugely negative or hugely # positive. I've never seen this without a relun after and the weights on # these channels were 0. Actually they should be pruned. # don't overwrite the quantized values since we may move around quantization later # bias_node.value = bqtype.quantize(dqbias) # bias_node.qtype = bqtype if dw_to_pw and wqtype.quantized_dimension: wqtype.quantized_dimension = 0 mulbiases_q = MultMulBiasScaleQType.from_filter( iqtype, wqtype, oqtype, params) qrec = QRec.scaled(in_qs=[iqtype, wqtype, bqtype], out_qs=[oqtype], calc_q=bqtype, acc_q=bqtype, mul_biases_q=mulbiases_q) # now set the quantization records on the node and its constants G.quantization[NodeId(params)] = qrec G.quantization[NodeId(weights_node)] = QRec.scaled( out_qs=[deepcopy(wqtype)]) G.quantization[NodeId(bias_node)] = QRec.scaled( out_qs=[deepcopy(bqtype)])
def version_1(cls, node: TFLiteNode, **kwargs): node_opts = node.get_options(Conv2DOptions) G = kwargs['G'] opts = kwargs['opts'] all_nodes = kwargs['all_nodes'] inputs = [all_nodes[t] for t in node.input] x = inputs[0] x = cls.remove_known_batch_dimension(G, x, node) x_shape = x[2].shape in_b, h, w, in_c = tuple(x_shape) filt = inputs[1] weights_node = filt[0] filt_shape = filt[2].shape # ['in_c', 'h', 'w', 'out_c'] filt_out_c, filt_h, filt_w, filt_in_c = tuple(filt_shape) # get filter dimensions if filt_h > h or filt_w > w: LOG.warning( "Filter %s of shape [%dx%d] is bigger than input of shape [%dx%d]", node.name, filt_h, filt_w, h, w) filt_dim = Conv2DFilterDim(filt_h, filt_w, filt_out_c, in_c=filt_in_c) filt_dim = filt_dim.impose_order(cls.TF_LITE_FILTER_ORDER) # compute padding pad = cls.get_tf_padding(node_opts.Padding()) # does it have biases if len(inputs) > 2: bias = inputs[2] bias_node = bias[0] else: bias_node = ConstantInputParameters( f'{node.name}_bias', dims=Dim.unnamed([filt_out_c]), value=np.zeros([filt_out_c], dtype=np.float32)) # TODO - check groups = in_c // filt_in_c params = Conv2DParameters( node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim(node_opts.DilationHFactor(), node_opts.DilationWFactor()), groups=groups, padding=pad, has_bias=True, in_dims_hint=[['h', 'w', 'c'], cls.TF_LITE_FILTER_ORDER.copy(), ['out_c']], out_dims_hint=[['h', 'w', 'c']]) G.add_edge(NNEdge(from_node=weights_node, to_node=params, to_idx=1)) G.add_edge(NNEdge(from_node=bias_node, to_node=params, to_idx=2)) cls.new_load_filter_parameters(G, params, params.filter.actual_shape, params.filter.get_order_idx('out_c'), node.input[0], weights_node, bias_node, node.output[0], opts) in_dim = Dim.named_ordered(h=h, w=w, c=in_c) out_dims = params.get_output_size( [in_dim, Dim.unnamed(filt_dim.shape), Dim.unnamed([filt_out_c])]) pout_dims = ProvisionalDim([None] + out_dims[0].shape) G.add_edge( NNEdge(from_node=x[0], to_node=params, from_idx=x[1], to_idx=0)) oparams = cls.fuse_activation(node_opts, node.name, params, **kwargs) all_nodes[node.output[0]] = (oparams, 0, pout_dims) return oparams
def _common(cls, node: TFLiteNode, **kwargs): node_opts = node.get_options(DepthwiseConv2DOptions) G = kwargs['G'] opts = kwargs['opts'] all_nodes = kwargs['all_nodes'] inputs = [all_nodes[t] for t in node.input] x = inputs[0] x_shape = x[2].shape in_b, h, w, in_c = tuple(x_shape) filt = inputs[1] filt_tensor = node.input[1] filt_shape = filt[2].shape # ['in_c', 'h', 'w', 'out_c'] filt_in_c, filt_h, filt_w, filt_out_c = tuple(filt_shape) # get filter dimensions filt_tensor.used = True if filt_h > h or filt_w > w: LOG.warning( "Filter %s of shape [%dx%d] is bigger than input of shape [%dx%d]", node.name, filt_h, filt_w, h, w) filt_dim = Conv2DFilterDim(filt_h, filt_w, filt_out_c, in_c=filt_in_c) filt_dim = filt_dim.impose_order(cls.TF_LITE_DW_FILTER_ORDER) # multiplier should match filter check(filt_dim.out_c == node_opts.DepthMultiplier() * in_c, "invalid multiplier") groups = filt_dim.out_c // node_opts.DepthMultiplier() # compute padding pad = cls.get_tf_padding(node_opts.Padding()) # does it have biases has_bias = len(inputs) > 2 if has_bias: node.input[2].used = True # TFLITE produces single channel input DW convolutions with the # multiplier equal to the number of out channels. This is just # a normal convolution and since we don't handle the channel # multiplier at present (but can) just convert them to normal # convolutions convert_to_conv = in_c == 1 and groups == 1 if convert_to_conv: filt_dim.impose_order(cls.TF_LITE_FILTER_ORDER) params = Conv2DParameters( node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim(node_opts.DilationHFactor(), node_opts.DilationWFactor()), padding=pad, has_bias=has_bias, in_dims_hint=SparseList([['h', 'w', 'c']]), out_dims_hint=SparseList([['h', 'w', 'c']]), constant_store=G.constant_store) else: filt_dim.impose_order(cls.TF_LITE_DW_FILTER_ORDER) params = Conv2DParameters( node.name, filt=filt_dim, stride=StrideDim(node_opts.StrideH(), node_opts.StrideW()), dilation=DilationDim(node_opts.DilationHFactor(), node_opts.DilationWFactor()), padding=pad, groups=groups, multiplier=node_opts.DepthMultiplier(), has_bias=has_bias, tf_depthwise=True, in_dims_hint=SparseList([['h', 'w', 'c']]), out_dims_hint=SparseList([['h', 'w', 'c']]), constant_store=G.constant_store) if opts.get('load_dequantized'): cls.load_dequantized_filter_parameters(params, node.input, convert_to_conv, is_dw=True) else: cls.load_filter_parameters(G, params, node.input, node.output, opts, converted_to_conv=convert_to_conv) in_dim = Dim.named_ordered(h=h, w=w, c=in_c) out_dims = params.get_output_size([in_dim]) pout_dims = ProvisionalDim([in_b] + out_dims[0].shape) G.add_edge( NNEdge(from_node=x[0], to_node=params, from_idx=x[1], to_idx=0)) params = cls.fuse_activation(node_opts, node.name, params, **kwargs) all_nodes[node.output[0]] = (params, 0, pout_dims) return params