From 03e7cbafc2ff4f2848c42ce4a3dc424e040df66d Mon Sep 17 00:00:00 2001 From: Tobi-Alonso <tobi.alonso@gmail.com> Date: Wed, 24 Jun 2020 08:49:57 +0100 Subject: [PATCH] [FPGADataflow] Fix InferChannelwiseLinearLayer. Correct output datatype inference from idt and parameters. Add parameter type inference. Check parameter shape. Check if retrived intializer is None (dynamic input) --- .../fpgadataflow/convert_to_hls_layers.py | 78 +++++++++++++------ 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py index c34a7ef23..c480a6001 100644 --- a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py +++ b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py @@ -496,6 +496,22 @@ class InferThresholdingLayer(Transformation): class InferChannelwiseLinearLayer(Transformation): """Convert any channel-wise Add/Mul into a HLS layer.""" + def get_smallest_possible(self, vals): + """Returns smallest (fewest bits) possible DataType that can represent + value. Prefers unsigned integers where possible.""" + vals = np.array(vals) + for v in vals: + assert int(v) == v, "Error float value" + + for k in DataType.__members__: + dt = DataType[k] + if dt in [DataType.BIPOLAR, DataType.BIPOLAR]: + # not currently supported + continue + + if (dt.min() <= vals).all() and (vals <= dt.max()).all(): + return dt + def apply(self, model): graph = model.graph node_ind = 0 @@ -503,45 +519,51 @@ class InferChannelwiseLinearLayer(Transformation): for node in graph.node: node_ind += 1 if node.op_type == "Add" or node.op_type == "Mul": + # assuming input[0] is dynamic ll_input = node.input[0] ll_output = node.output[0] ll_in_shape = model.get_tensor_shape(ll_input) - # check that: - # input 1 is an initializer - # with shape (ch, 1) - # and initializer is integers + + # check if input 1 has an initializer ll_const = node.input[1] if ll_const is not None: ll_cinit = model.get_initializer(ll_const) + if ll_cinit is None: + # input 1 is also dynamic + continue else: continue - ll_cinit_shape = list(ll_cinit.shape) - # get number of channels from input + # get number of channels and channel index from input ll_in_layout = model.get_tensor_layout(ll_input) if ll_in_layout == DataLayout.NHWC or ll_in_layout == DataLayout.NC: + ch_index = -1 ch = ll_in_shape[-1] elif ll_in_layout == DataLayout.NCHW: + ch_index = 1 ch = ll_in_shape[1] else: continue - # check if the shape of initializer is compatible with - # number of channels, e.g. (ch,1) or (ch) - # TODO: verify plausible shapes - if np.prod(ll_cinit_shape) != ch: + # check if the shape of initializer is compatible + ll_cinit_shape = list(ll_cinit.shape) + if np.prod(ll_cinit_shape) == 1: + # TODO broadcast + pass + + if np.prod(ll_cinit_shape) != ch or ll_cinit_shape[ch_index] != ch: + # parameter shape not compatible with Channelwise_batch continue # check initializer contains integers as floats if not (ll_cinit.astype(np.int32) == ll_cinit).all(): continue - # all initializer conditions are met + # check inputs idt = model.get_tensor_datatype(ll_input) - - # skip conversion for layers with float input if not idt.is_integer(): + # skip conversion for layers with float input continue # check layout of inputs/outputs, and convert if needed @@ -559,25 +581,32 @@ class InferChannelwiseLinearLayer(Transformation): ll_output = nchw_to_nhwc(ll_output, model, node_ind, reverse=True) node_ind += 1 - # create node with no parallelization first - pe = 1 - assert ch % pe == 0, "Requirement IFC divisable by PE is violated." + # get parameter data type + param_min = min(ll_cinit.flatten()) + param_max = max(ll_cinit.flatten()) + pdt = self.get_smallest_possible([param_min, param_max]) # set function and determine output data type if node.op_type == "Add": func = "add" - if idt.signed(): - odt = DataType.get_smallest_possible(2 * idt.min()) - else: - odt = DataType.get_smallest_possible(2 * idt.max()) + out_min = idt.min() + param_min + out_max = idt.max() + param_max + odt = self.get_smallest_possible([out_min, out_max]) elif node.op_type == "Mul": func = "mul" - if idt.signed(): - odt = DataType.get_smallest_possible(abs(idt.min()) * idt.min()) - else: - odt = DataType.get_smallest_possible(idt.max() * idt.max()) + possible_limits = [] + possible_limits += [idt.min() * param_min] + possible_limits += [idt.min() * param_max] + possible_limits += [idt.max() * param_min] + possible_limits += [idt.max() * param_max] + odt = self.get_smallest_possible(possible_limits) + model.set_initializer(ll_const, ll_cinit.reshape(ch)) model.set_tensor_datatype(ll_output, odt) + + # create node with no parallelization first + pe = 1 + assert ch % pe == 0, "Requirement IFC divisable by PE is violated." # create and insert node new_node = helper.make_node( "ChannelwiseOp_Batch", @@ -589,6 +618,7 @@ class InferChannelwiseLinearLayer(Transformation): NumChannels=ch, PE=pe, inputDataType=idt.name, + paramDataType=pdt.name, outputDataType=odt.name, numInputVectors=list(ll_in_shape[:-1]), ) -- GitLab