def test_brevitas_act_export_relu(abits, max_val, scaling_impl_type, QONNX_export): min_val = -1.0 ishape = (1, 15) b_act = QuantReLU( bit_width=abits, max_val=max_val, scaling_impl_type=scaling_impl_type, restrict_scaling_type=RestrictValueType.LOG_FP, quant_type=QuantType.INT, ) if scaling_impl_type == ScalingImplType.PARAMETER: checkpoint = { "act_quant_proxy.fused_activation_quant_proxy.tensor_quant.\ scaling_impl.learned_value": torch.tensor(0.49).type(torch.FloatTensor) } b_act.load_state_dict(checkpoint) if QONNX_export: m_path = export_onnx_path BrevitasONNXManager.export(b_act, ishape, m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: bo.export_finn_onnx(b_act, ishape, export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype(np.float32) idict = {model.graph.input[0].name: inp_tensor} odict = oxe.execute_onnx(model, idict, True) produced = odict[model.graph.output[0].name] inp_tensor = torch.from_numpy(inp_tensor).float() b_act.eval() expected = b_act.forward(inp_tensor).detach().numpy() if not np.isclose(produced, expected, atol=1e-3).all(): print(abits, max_val, scaling_impl_type) print("scale: ", b_act.quant_act_scale().type(torch.FloatTensor).detach()) if abits < 5: print( "thres:", ", ".join(["{:8.4f}".format(x) for x in b_act.export_thres[0]]), ) print("input:", ", ".join(["{:8.4f}".format(x) for x in inp_tensor[0]])) print("prod :", ", ".join(["{:8.4f}".format(x) for x in produced[0]])) print("expec:", ", ".join(["{:8.4f}".format(x) for x in expected[0]])) assert np.isclose(produced, expected, atol=1e-3).all() os.remove(export_onnx_path)
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if n.op_type == "Flatten": consumer = model.find_consumer(n.output[0]) if consumer is not None and consumer.op_type == "TopK": axis = get_by_name(consumer.attribute, "axis") if axis is None or axis.i != -1: continue start_name = n.input[0] data_layout = model.get_tensor_layout(start_name) if data_layout != DataLayout.NHWC: warnings.warn( """Transformation can't be applied. The input to flatten has to have DataLayout.NHWC""" ) continue (b, h, w, c) = model.get_tensor_shape(start_name) if h != 1 or w != 1: continue # get parameter k from topk k = model.get_tensor_shape(consumer.output[1])[-1] # swap conections # new tensor because dims change middle_name = model.make_new_valueinfo_name() topk_indices = oh.make_tensor_value_info( middle_name, TensorProto.INT64, [b, h, w, k] ) end_name = consumer.output[1] graph.value_info.append(topk_indices) # remove old nodes graph.node.remove(n) graph.node.remove(consumer) # set inputs and outputs correctly consumer.input[0] = start_name consumer.output[1] = middle_name model.set_tensor_shape(consumer.output[0], (b, h, w, k)) n.input[0] = middle_name n.output[0] = end_name # insert them back in graph.node.insert(node_ind - 1, consumer) graph.node.insert(node_ind, n) graph_modified = True model = model.transform(InferShapes()) return (model, graph_modified)
def test_brevitas_QConv2d(dw, in_channels): ishape = (1, 32, 111, 111) if dw is True: groups = in_channels out_channels = in_channels kernel_size = 3 padding = 1 stride = 1 w_shape = (32, 1, 3, 3) else: groups = 1 out_channels = 64 kernel_size = 1 padding = 0 stride = 1 w_shape = (64, 32, 1, 1) b_conv = QuantConv2d( in_channels=in_channels, out_channels=out_channels, groups=groups, kernel_size=kernel_size, padding=padding, stride=stride, bias=False, bias_quant_type=QuantType.FP, compute_output_bit_width=False, compute_output_scale=False, weight_bit_width=4, weight_quant_type=QuantType.INT, weight_scaling_impl_type=ScalingImplType.STATS, weight_scaling_stats_op=StatsOp.MAX, weight_scaling_per_output_channel=True, weight_restrict_scaling_type=RestrictValueType.LOG_FP, weight_narrow_range=True, weight_scaling_min_val=2e-16, ) weight_tensor = gen_finn_dt_tensor(DataType.INT4, w_shape) b_conv.weight = torch.nn.Parameter(torch.from_numpy(weight_tensor).float()) bo.export_finn_onnx(b_conv, ishape, export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = np.random.uniform(low=-1.0, high=1.0, size=ishape).astype(np.float32) idict = {model.graph.input[0].name: inp_tensor} odict = oxe.execute_onnx(model, idict, True) produced = odict[model.graph.output[0].name] inp_tensor = torch.from_numpy(inp_tensor).float() b_conv.eval() expected = b_conv.forward(inp_tensor).detach().numpy() assert np.isclose(produced, expected, atol=1e-3).all() os.remove(export_onnx_path)
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False nodes = [n for n in graph.node] for n in nodes: node_ind += 1 if ( n.op_type == "GlobalAveragePool" or n.op_type == "Reshape" or n.op_type == "Transpose" or n.op_type == "Flatten" ): in0 = n.input[0] if in0 is None: continue # find and check producer on our input prod0 = model.find_producer(in0) if prod0 is None: continue if prod0.op_type in ["Mul", "Add", "Div"]: # check if second input of producer is an initializer init0 = model.get_initializer(prod0.input[1]) # if either initializer is None, skip if init0 is None: continue # if initializer is not scalar, skip if np.prod(init0.shape) != 1: continue # move prod0 from input to output, old_prod0_in = prod0.input[0] old_prod0_out = prod0.output[0] scalar_op_odt = model.get_tensor_datatype(old_prod0_out) old_n_out = n.output[0] in_shape = model.get_tensor_shape(n.input[0]) out_shape = model.get_tensor_shape(n.output[0]) n.input[0] = old_prod0_in n.output[0] = old_prod0_out prod0.input[0] = old_prod0_out prod0.output[0] = old_n_out model.set_tensor_shape(n.input[0], in_shape) model.set_tensor_shape(n.output[0], out_shape) model.set_tensor_shape(prod0.output[0], out_shape) model.set_tensor_datatype(prod0.output[0], scalar_op_odt) model.set_tensor_datatype(n.output[0], DataType.FLOAT32) graph.node.remove(prod0) graph.node.insert(node_ind - 1, prod0) graph_modified = True else: continue if graph_modified: model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) return (model, graph_modified)
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if ( n.op_type == "Add" and not model.is_fork_node(n) and not model.is_join_node(n) ): consumer = model.find_consumer(n.output[0]) if ( consumer is not None and consumer.op_type == "MatMul" and not model.is_join_node(consumer) ): add_weight_name = n.input[1] matmul_weight_name = consumer.input[1] A = model.get_initializer(add_weight_name) W = model.get_initializer(matmul_weight_name) if (A is None) or (W is None): warnings.warn("MatMul or Add params are not constant, skipping") continue start_name = n.input[0] middle_name = n.output[0] end_name = consumer.output[0] mm_out_shape = model.get_tensor_shape(end_name) if all(x == 1 for x in A.shape): # if the add is scalar, we can move it past the matmul # by taking it past the matmul with a dot product Anew = np.dot(A * np.ones(W.shape[0], dtype=np.float32), W) # update the add weight model.set_initializer(add_weight_name, Anew) new_matmul = oh.make_node( "MatMul", [start_name, matmul_weight_name], [middle_name], name=consumer.name, ) new_add = oh.make_node( "Add", [middle_name, add_weight_name], [end_name], name=n.name, ) graph.node.insert(node_ind, new_matmul) graph.node.insert(node_ind + 1, new_add) model.set_tensor_shape(middle_name, mm_out_shape) # remove old nodes graph.node.remove(n) graph.node.remove(consumer) graph_modified = True model = model.transform(InferShapes()) return (model, graph_modified)
def test_brevitas_act_export_relu_imagenet(abits, max_val, scaling_per_channel): out_channels = 32 ishape = (1, out_channels, 1, 1) min_val = -1.0 b_act = QuantReLU( bit_width=abits, quant_type=QuantType.INT, scaling_impl_type=ScalingImplType.PARAMETER, scaling_per_channel=scaling_per_channel, restrict_scaling_type=RestrictValueType.LOG_FP, scaling_min_val=2e-16, max_val=6.0, return_quant_tensor=True, per_channel_broadcastable_shape=(1, out_channels, 1, 1), ) if scaling_per_channel is True: rand_tensor = (2) * torch.rand((1, out_channels, 1, 1)) else: rand_tensor = torch.tensor(1.2398) checkpoint = { "act_quant_proxy.fused_activation_quant_proxy.tensor_quant.\ scaling_impl.learned_value": rand_tensor.type(torch.FloatTensor) } b_act.load_state_dict(checkpoint) bo.export_finn_onnx(b_act, ishape, export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype(np.float32) idict = {model.graph.input[0].name: inp_tensor} odict = oxe.execute_onnx(model, idict, True) produced = odict[model.graph.output[0].name] inp_tensor = torch.from_numpy(inp_tensor).float() b_act.eval() expected = b_act.forward(inp_tensor).tensor.detach().numpy() if not np.isclose(produced, expected, atol=1e-3).all(): print(abits, max_val) print("scale: ", b_act.quant_act_scale().type(torch.FloatTensor).detach()) if abits < 5: print( "thres:", ", ".join(["{:8.4f}".format(x) for x in b_act.export_thres[0]]), ) print("input:", ", ".join(["{:8.4f}".format(x) for x in inp_tensor[0]])) print("prod :", ", ".join(["{:8.4f}".format(x) for x in produced[0]])) print("expec:", ", ".join(["{:8.4f}".format(x) for x in expected[0]])) assert np.isclose(produced, expected, atol=1e-3).all() os.remove(export_onnx_path)
def test_end2end_mobilenet_tidy_and_merge_with_preproc(): preproc_model = load_test_checkpoint_or_skip( build_dir + "/end2end_mobilenet_preproc.onnx") model = load_test_checkpoint_or_skip(build_dir + "/end2end_mobilenet_export.onnx") model = model.transform(InferShapes()) model = model.transform(FoldConstants()) model = model.transform(InsertTopK()) # get initializer from Mul that will be absorbed into topk a0 = model.get_initializer(model.graph.node[-2].input[1]) np.save(build_dir + "/end2end_mobilenet_topk_scale.npy", a0) model = model.transform(absorb.AbsorbScalarMulAddIntoTopK()) model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) model = model.transform(InferDataLayouts()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveUniqueParameterTensors()) model = model.transform(GiveReadableTensorNames()) model = model.transform(MergeONNXModels(preproc_model)) model.save(build_dir + "/end2end_mobilenet_tidy.onnx")
def test_infer_shapes(): # load the onnx model raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") model = ModelWrapper(raw_m) graph = model.graph # multi-thresholding node to be inserted between the first Relu and MaxPool node # get Relu node to use data Relu_node = graph.node[3] assert Relu_node.op_type == "Relu", "The wrong model was chosen for the check" # create thresholds tensor as constant mt_thresh0 = helper.make_tensor_value_info("mt_thresh0", TensorProto.FLOAT, [8, 7]) # random numbers for the thresholds # thresholds for one channel have to be sorted to guarantee the correct behavior mt_thresh0_values = np.empty([8, 7], dtype=np.float32) for i in range(len(mt_thresh0_values)): mt_thresh0_values[i] = np.sort(np.random.random_sample(7) * 10) model.set_initializer(mt_thresh0.name, mt_thresh0_values) # add multi-thresholding node and change Relu node mt_node = helper.make_node( "MultiThreshold", ["mt_v0", "mt_thresh0"], [Relu_node.output[0]], domain="finn.custom_op.general", ) Relu_node.output[0] = "mt_v0" # explicitly remove any present shape from ReLU and MultiThreshold outputs util.remove_by_name(model.graph.value_info, Relu_node.output[0]) util.remove_by_name(model.graph.value_info, mt_node.output[0]) graph.node.insert(4, mt_node) # first check routine # check if at least one shape is not specified assert not ( model.check_all_tensor_shapes_specified() ), "All tensors are already specified before the shape inference execution" # perform shape inference on mixed model model = model.transform(InferShapes()) # second check routine # now all shapes should be specified and mt_node output shape is (1,8,28,28) assert (model.check_all_tensor_shapes_specified() ), "There are still tensors that are not specified" assert (model.get_tensor_shape(mt_node.output[0])) == ([ 1, 8, 28, 28 ]), "output of multi-thresholding node has wrong shape"
def test_import_and_tidy(self, topology, wbits, abits): prev_chkpt_name = get_checkpoint_name(topology, wbits, abits, "export") model = load_test_checkpoint_or_skip(prev_chkpt_name) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveReadableTensorNames()) model = model.transform(InferDataTypes()) model = model.transform(RemoveStaticGraphInputs()) chkpt = get_checkpoint_name(topology, wbits, abits, "import_and_tidy") model.save(chkpt)
def tidy_up(model): log("Basic transformations launched") model = model.transform(InferShapes()) model = model.transform(FoldConstants()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveReadableTensorNames()) model = model.transform(InferDataTypes()) model = model.transform(RemoveStaticGraphInputs()) log("Basic transformations completed") save(model, "0_tidy") return model
def test_const_folding_shapes(): raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") model = ModelWrapper(raw_m) model = model.transform(InferShapes()) mm_node_w_in = model.get_nodes_by_op_type("MatMul")[0].input[1] assert model.find_producer(mm_node_w_in) is not None assert model.find_producer(mm_node_w_in).op_type == "Reshape" assert model.get_initializer(mm_node_w_in) is None model = model.transform(FoldConstants()) assert model.find_producer(mm_node_w_in) is None assert model.get_initializer(mm_node_w_in) is not None
def test_depthwise_conv_lowering(idt, k, ifm_dim, ifm_ch, stride, padding): wdt = idt odt = DataType.INT32 ofm_ch = ifm_ch ofm_dim = compute_conv_output_dim(ifm_dim, k, stride, pad=padding[0]) # set up onnx model inp = oh.make_tensor_value_info("inp", TensorProto.FLOAT, [1, ifm_ch, ifm_dim, ifm_dim]) outp = oh.make_tensor_value_info("outp", TensorProto.FLOAT, [1, ofm_ch, ofm_dim, ofm_dim]) W = oh.make_tensor_value_info("W", TensorProto.FLOAT, [ofm_ch, 1, k, k]) dw_cnv = oh.make_node( "Conv", inputs=["inp", "W"], outputs=["outp"], kernel_shape=[k, k], pads=padding, strides=[stride, stride], group=ifm_ch, ) graph = oh.make_graph( nodes=[dw_cnv], name="dw_cnv_graph", inputs=[inp], outputs=[outp], value_info=[W], ) model = oh.make_model(graph, producer_name="dws_cnv-model") model = ModelWrapper(model) model.set_tensor_datatype("inp", idt) model.set_tensor_datatype("outp", odt) model.set_tensor_datatype("W", wdt) w_tensor = gen_finn_dt_tensor(wdt, [ofm_ch, 1, k, k]) model.set_initializer("W", w_tensor) model = model.transform(InferShapes()) input_tensor = gen_finn_dt_tensor(idt, [1, ifm_ch, ifm_dim, ifm_dim]) input_dict = {"inp": input_tensor} output_dict = oxe.execute_onnx(model, input_dict) expected = output_dict["outp"] model = model.transform(LowerConvsToMatMul()) output_dict = oxe.execute_onnx(model, input_dict) produced = output_dict["outp"] assert (produced == expected).all() # check if created nodes have attributes that indicate depthwise conv assert model.get_tensor_sparsity("W") is not None im2col_node = getCustomOp(model.graph.node[1]) assert im2col_node.get_nodeattr("depthwise") == 1
def test_sort_nonlinear_graph(): ch = 2 ifmdim = 16 input_shape = (1, ch, ifmdim, ifmdim) top_in = helper.make_tensor_value_info("top_in", TensorProto.FLOAT, input_shape) top_out = helper.make_tensor_value_info("top_out", TensorProto.FLOAT, input_shape) num_of_params = 8 value_info = [] for i in range(num_of_params): value_info += [ helper.make_tensor_value_info("p" + str(i), TensorProto.FLOAT, input_shape) ] modelproto = helper.make_model( helper.make_graph( name="test", inputs=[top_in], outputs=[top_out], value_info=value_info, nodes=[ # Not sorted nodes helper.make_node("Mul", ["fork1", "p2"], ["t3"]), helper.make_node("Add", ["t4", "p3"], ["t5"]), helper.make_node("Add", ["t2", "t3"], ["t4"]), helper.make_node("Add", ["t6", "t7"], ["t8"]), helper.make_node("Add", ["fork3", "fork3"], ["top_out"]), helper.make_node("Mul", ["t5", "p4"], ["fork2"]), helper.make_node("Add", ["top_in", "p0"], ["fork1"]), helper.make_node("Mul", ["fork1", "p1"], ["t2"]), helper.make_node("Add", ["fork2", "p5"], ["t6"]), helper.make_node("Add", ["fork2", "p6"], ["t7"]), helper.make_node("Mul", ["t8", "p7"], ["fork3"]), ], )) model = ModelWrapper(modelproto) model = model.transform(InferShapes()) np.random.seed(0) for i in range(num_of_params): model.set_initializer("p" + str(i), np.random.rand(*input_shape).astype(np.float32)) new_model = model.transform(SortGraph()) # Test ret = new_model.analysis(ta.nodes_topologically_sorted) assert ret[ "nodes_topologically_sorted"], "Nodes are not topologically sorted."
def test_const_folding_shapes(): lfc = get_test_model_untrained("LFC", 1, 1) bo.export_finn_onnx(lfc, (1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) reshape_node = model.graph.node[0] assert reshape_node.op_type == "Reshape" assert list(model.get_tensor_shape( reshape_node.input[0])) == [1, 1, 28, 28] assert list(model.get_tensor_shape(reshape_node.output[0])) == [1, 784] os.remove(export_onnx_path)
def test_fpgadataflow_fmpadding(idim, pad, num_ch, simd, pad_style, idt, mode): if num_ch % simd != 0: pytest.skip(" num_ch % simd != 0, skipping") # generate input data x = gen_finn_dt_tensor(idt, [1, idim, idim, num_ch]) input_dict = {"inp": x} odim = idim + pad model = make_single_fmpadding_modelwrapper(idim, pad, num_ch, simd, idt, pad_style) model = model.transform(InferShapes()) model = model.transform(SetExecMode(mode)) model = model.transform(GiveUniqueNodeNames()) if mode == "cppsim": model = model.transform(PrepareCppSim()) model = model.transform(CompileCppSim()) elif mode == "rtlsim": model = model.transform(PrepareIP(test_fpga_part, target_clk_ns)) model = model.transform(HLSSynthIP()) model = model.transform(PrepareRTLSim()) y_produced = oxe.execute_onnx(model, input_dict)["outp"] expected_oshape = (1, odim, odim, num_ch) assert y_produced.shape == expected_oshape # calculate reference # calculate correct pad according to parameters if pad_style == 2: if pad % 2 == 0: pad_up = pad // 2 pad_left = pad // 2 else: pad_up = pad // 2 + 1 pad_left = pad // 2 + 1 else: pad_up = pad // 2 pad_left = pad // 2 pad_down = pad - pad_up pad_right = pad - pad_left y_expected = np.pad(x, ((0, 0), (pad_up, pad_down), (pad_left, pad_right), (0, 0)), "constant") assert (y_produced == y_expected).all() if mode == "rtlsim": node = model.get_nodes_by_op_type("FMPadding_Batch")[0] inst = getCustomOp(node) cycles_rtlsim = inst.get_nodeattr("cycles_rtlsim") exp_cycles_dict = model.analysis(exp_cycles_per_layer) exp_cycles = exp_cycles_dict[node.name] assert np.isclose(exp_cycles, cycles_rtlsim, atol=10) assert exp_cycles != 0
def test_conv_lowering_conv_1x1(): np.random.seed(0) in_feature_dim = 7 in_chn = 3 kernel_size = 1 out_feature_dim = in_feature_dim input_shape = [1, in_chn, in_feature_dim, in_feature_dim] output_shape = [1, in_chn, out_feature_dim, out_feature_dim] conv_param_shape = [in_chn, in_chn, kernel_size, kernel_size] conv_config = {} conv_config["dilations"] = [1, 1] conv_config["group"] = 1 conv_config["kernel_shape"] = [kernel_size, kernel_size] conv_config["pads"] = [0, 0, 0, 0] conv_config["strides"] = [1, 1] top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, input_shape) top_out = oh.make_tensor_value_info("top_out", TensorProto.FLOAT, output_shape) value_info = [ oh.make_tensor_value_info("p1", TensorProto.FLOAT, conv_param_shape) ] modelproto = oh.make_model( oh.make_graph( name="test", inputs=[top_in], outputs=[top_out], value_info=value_info, nodes=[ oh.make_node("Conv", ["top_in", "p1"], ["top_out"], **conv_config) ], )) model = ModelWrapper(modelproto) model = model.transform(InferShapes()) model.set_initializer("p1", np.random.rand(*conv_param_shape).astype(np.float32)) new_model = model.transform(LowerConvsToMatMul()) inp_dict = {"top_in": np.random.rand(*input_shape).astype(np.float32)} assert oxe.compare_execution(model, new_model, inp_dict) assert new_model.graph.node[0].op_type == "Transpose" assert new_model.graph.node[1].op_type == "MatMul" assert new_model.graph.node[2].op_type == "Transpose" assert len(new_model.graph.node) == 3
def test_end2end_mobilenet_convert_to_hls_layers(): model = load_test_checkpoint_or_skip(build_dir + "/end2end_mobilenet_lowered.onnx") model = model.transform(to_hls.InferPool_Batch()) model = model.transform(to_hls.InferConvInpGen()) model = model.transform(to_hls.InferVVAU()) model = model.transform(to_hls.InferQuantizedStreamingFCLayer(mem_mode)) model = model.transform(to_hls.InferChannelwiseLinearLayer()) model = model.transform(to_hls.InferLabelSelectLayer()) model = model.transform(InferShapes()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveReadableTensorNames()) model.save(build_dir + "/end2end_mobilenet_hls_layers.onnx")
def test_end2end_mobilenet_export(): # export preprocessing preproc_onnx = build_dir + "/end2end_mobilenet_preproc.onnx" mean = [0.485, 0.456, 0.406] std = 0.226 ch = 3 preproc = NormalizePreProc(mean, std, ch) bo.export_finn_onnx(preproc, (1, 3, 224, 224), preproc_onnx) preproc_model = ModelWrapper(preproc_onnx) # set input finn datatype to UINT8 preproc_model.set_tensor_datatype(preproc_model.graph.input[0].name, DataType["UINT8"]) preproc_model = preproc_model.transform(InferShapes()) preproc_model = preproc_model.transform(FoldConstants()) preproc_model = preproc_model.transform(GiveUniqueNodeNames()) preproc_model = preproc_model.transform(GiveUniqueParameterTensors()) preproc_model = preproc_model.transform(GiveReadableTensorNames()) preproc_model.save(build_dir + "/end2end_mobilenet_preproc.onnx") # export mobilenet finn_onnx = build_dir + "/end2end_mobilenet_export.onnx" mobilenet = get_test_model_trained("mobilenet", 4, 4) bo.export_finn_onnx(mobilenet, (1, 3, 224, 224), finn_onnx) # calculate golden output with pytorch/brevitas and save as .npy # get single image as input and prepare image img = Image.open("/workspace/finn/tests/brevitas/king_charles.jpg") # resize smallest side of the image to 256 pixels and resize larger side # with same ratio img = resize_smaller_side(256, img) # crop central 224*224 window img = crop_center(224, img) # save image as numpy array and as torch tensor to enable testing in # brevitas/pytorch and finn and transpose from (H, W, C) to (C, H, W) img_np = np.asarray(img).copy().astype(np.float32).transpose(2, 0, 1) img_np = img_np.reshape(1, 3, 224, 224) np.save(build_dir + "/end2end_mobilenet_input.npy", img_np) img_torch = torch.from_numpy(img_np).float() # do forward pass in PyTorch/Brevitas input_tensor = preproc.forward(img_torch) golden = mobilenet.forward(input_tensor).detach().numpy() golden_topk = golden.flatten() golden_top5 = np.argsort(golden_topk)[-5:] golden_top5 = np.flip(golden_top5) golden_top5_prob = [] for index in golden_top5: golden_top5_prob.append(golden_topk[index]) # save golden output values np.save(build_dir + "/end2end_mobilenet_golden_top5.npy", golden_top5) np.save(build_dir + "/end2end_mobilenet_golden_top5_prob.npy", golden_top5_prob) assert os.path.isfile(finn_onnx) assert os.path.isfile(build_dir + "/end2end_mobilenet_preproc.onnx")
def test_all_tensors_f32(): top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, [2]) add_param = oh.make_tensor_value_info("add_param", TensorProto.FLOAT, [2]) mul_param = oh.make_tensor_value_info("mul_param", TensorProto.FLOAT, [2]) top_out = oh.make_tensor_value_info("top_out", TensorProto.FLOAT, [2]) modelproto = oh.make_model( oh.make_graph( name="test", inputs=[top_in], outputs=[top_out], value_info=[add_param, mul_param], nodes=[ oh.make_node("Add", ["top_in", "add_param"], ["middle"]), oh.make_node("Mul", ["middle", "mul_param"], ["top_out"]), ], )) model = ModelWrapper(modelproto) model = model.transform(InferShapes()) ret = model.analysis(ta.all_tensors_f32) assert ret["all_tensors_f32"] is True top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, [2]) add_param = oh.make_tensor_value_info("add_param", TensorProto.INT8, [2]) mul_param = oh.make_tensor_value_info("mul_param", TensorProto.FLOAT, [2]) top_out = oh.make_tensor_value_info("top_out", TensorProto.FLOAT, [2]) modelproto = oh.make_model( oh.make_graph( name="test", inputs=[top_in], outputs=[top_out], value_info=[add_param, mul_param], nodes=[ oh.make_node("Add", ["top_in", "add_param"], ["middle"]), oh.make_node("Mul", ["middle", "mul_param"], ["top_out"]), ], )) model = ModelWrapper(modelproto) model = model.transform(InferShapes()) ret = model.analysis(ta.all_tensors_f32) assert ret["all_tensors_f32"] is False
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if (n.op_type == "Add" and not model.is_fork_node(n) and not model.is_join_node(n)): consumer = model.find_consumer(n.output[0]) if (consumer is not None and consumer.op_type == "Mul" and not model.is_join_node(consumer)): # have: (x) -> add(,B) -> (x+B) -> mul(,A) -> (xA+BA) # want: (x) -> mul(,A) -> (xA) -> add(,BA) -> (xA+BA) # assume input 0 is from the previous layer, input 1 is the # trained (constant) parameter mul_weight_name = consumer.input[1] add_weight_name = n.input[1] A = model.get_initializer(mul_weight_name) B = model.get_initializer(add_weight_name) if (A is None) or (B is None): warnings.warn( "Mul or add does not have constant params, skipping" ) continue start_name = n.input[0] middle_name = n.output[0] end_name = consumer.output[0] # compute new param value for add BA = B * A # make and insert new nodes new_mul = oh.make_node( "Mul", [start_name, mul_weight_name], [middle_name], name=consumer.name, ) new_add = oh.make_node("Add", [middle_name, add_weight_name], [end_name], name=n.name) graph.node.insert(node_ind, new_mul) graph.node.insert(node_ind + 1, new_add) # replace add value model.set_initializer(add_weight_name, BA) # remove old nodes graph.node.remove(n) graph.node.remove(consumer) graph_modified = True model = model.transform(InferShapes()) return (model, graph_modified)
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if n.op_type == "Add": consumer = model.find_consumer(n.output[0]) if consumer is not None and consumer.op_type == "Conv": conv_node = consumer add_node = n add_weight_name = n.input[1] conv_in_name = consumer.input[0] conv_in_shape = model.get_tensor_shape(conv_in_name) A = model.get_initializer(add_weight_name) assert A is not None, "Initializer for add weights is not set." start_name = n.input[0] end_name = consumer.output[0] conv_out_shape = model.get_tensor_shape(end_name) if all(x == 1 for x in A.shape): # create a tensor filled with the add constant, in # the shape expected by the convolution conv_in_const = np.zeros(conv_in_shape, dtype=np.float32) conv_in_const.fill(A.item()) # create an execution context and put in const input exec_ctx = model.make_empty_exec_context() exec_ctx[conv_in_name] = conv_in_const # execute the conv node only execute_node(conv_node, exec_ctx, model.graph) # retrieve the conv output Anew = exec_ctx[end_name] # strip out repetition Anew = Anew[0, :, 0, 0].reshape(1, -1, 1, 1) # update the add weight model.set_initializer(add_weight_name, Anew) # rewire add input to be conv input conv_node.input[0] = start_name model.set_tensor_shape(start_name, conv_in_shape) # use old conv input tensor as conv output conv_node.output[0] = conv_in_name model.set_tensor_shape(conv_in_name, conv_out_shape) # use new conv output as new add node input add_node.input[0] = conv_in_name # use old conv output as new add node output add_node.output[0] = end_name # move add node past conv node graph.node.remove(add_node) graph.node.insert(node_ind, add_node) graph_modified = True model = model.transform(InferShapes()) return (model, graph_modified)
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if ( n.op_type == "Mul" and not model.is_fork_node(n) and not model.is_join_node(n) ): consumer = model.find_consumer(n.output[0]) if ( consumer is not None and consumer.op_type == "MatMul" and not model.is_join_node(consumer) ): mul_weight_name = n.input[1] matmul_weight_name = consumer.input[1] A = model.get_initializer(mul_weight_name) W = model.get_initializer(matmul_weight_name) if (A is None) or (W is None): warnings.warn("MatMul or Mul params are not constant, skipping") continue start_name = n.input[0] middle_name = n.output[0] end_name = consumer.output[0] mm_out_shape = model.get_tensor_shape(end_name) if all(x == 1 for x in A.shape): # if the mul is scalar, we can simply swap the order of ops # make and insert new nodes new_matmul = oh.make_node( "MatMul", [start_name, matmul_weight_name], [middle_name], name=consumer.name, ) new_mul = oh.make_node( "Mul", [middle_name, mul_weight_name], [end_name], name=n.name, ) graph.node.insert(node_ind, new_matmul) graph.node.insert(node_ind + 1, new_mul) model.set_tensor_shape(middle_name, mm_out_shape) # remove old nodes graph.node.remove(n) graph.node.remove(consumer) graph_modified = True model = model.transform(InferShapes()) return (model, graph_modified)
def step_mobilenet_convert_to_hls_layers(model: ModelWrapper, cfg: DataflowBuildConfig): mem_mode = cfg.default_mem_mode.value model = model.transform(to_hls.InferPool_Batch()) model = model.transform(to_hls.InferConvInpGen()) model = model.transform(to_hls.InferVVAU()) model = model.transform(to_hls.InferQuantizedStreamingFCLayer(mem_mode)) model = model.transform(to_hls.InferChannelwiseLinearLayer()) model = model.transform(to_hls.InferLabelSelectLayer()) model = model.transform(InferShapes()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveReadableTensorNames()) return model
def test_batchnorm_to_affine_lfc_w1a1(): lfc = get_test_model_trained("LFC", 1, 1) bo.export_finn_onnx(lfc, (1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) new_model = model.transform(BatchNormToAffine()) # load one of the test vectors raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") input_tensor = onnx.load_tensor_from_string(raw_i) input_dict = {"0": nph.to_array(input_tensor)} assert oxe.compare_execution(model, new_model, input_dict) os.remove(export_onnx_path)
def test_move_scalar_past_matmul_only_if_linear(test_args): scalar_op = test_args[0] transf_fxn = test_args[1] input_shape = [1, 2] matmul_shape = [2, 2] top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, input_shape) top_out = oh.make_tensor_value_info("top_out", TensorProto.FLOAT, input_shape) p1 = oh.make_tensor_value_info("p1", TensorProto.FLOAT, [1, 1]) p2 = oh.make_tensor_value_info("p2", TensorProto.FLOAT, matmul_shape) p3 = oh.make_tensor_value_info("p3", TensorProto.FLOAT, matmul_shape) p4 = oh.make_tensor_value_info("p4", TensorProto.FLOAT, matmul_shape) modelproto = oh.make_model( oh.make_graph( name="test", inputs=[top_in], outputs=[top_out], value_info=[p1, p2, p3, p4], nodes=[ oh.make_node(scalar_op, ["top_in", "p1"], ["t1"]), oh.make_node("MatMul", ["t1", "p2"], ["fork"]), oh.make_node("MatMul", ["fork", "p3"], ["t3"]), oh.make_node(scalar_op, ["t3", "fork"], ["t4"]), oh.make_node("MatMul", ["t4", "p4"], ["top_out"]), ], )) model = ModelWrapper(modelproto) model = model.transform(InferShapes()) np.random.seed(0) model.set_initializer("p1", np.random.rand(1, 1).astype(np.float32)) model.set_initializer("p2", np.random.rand(*matmul_shape).astype(np.float32)) model.set_initializer("p3", np.random.rand(*matmul_shape).astype(np.float32)) model.set_initializer("p4", np.random.rand(*matmul_shape).astype(np.float32)) # Transform new_model = model.transform(transf_fxn) # Test inp_dict = {"top_in": np.random.rand(*input_shape).astype(np.float32)} assert ox.compare_execution(model, new_model, inp_dict) assert new_model.graph.node[0].op_type == "MatMul" assert new_model.graph.node[1].op_type == scalar_op assert new_model.graph.node[2].op_type == "MatMul" assert new_model.graph.node[3].op_type == scalar_op assert new_model.graph.node[4].op_type == "MatMul"
def test_sign_to_thres(): lfc = get_test_model_trained("LFC", 1, 1) bo.export_finn_onnx(lfc, (1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) new_model = model.transform(ConvertSignToThres()) assert new_model.graph.node[3].op_type == "MultiThreshold" # load one of the test vectors raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") input_tensor = onnx.load_tensor_from_string(raw_i) input_dict = {"0": nph.to_array(input_tensor)} assert oxe.compare_execution(model, new_model, input_dict) os.remove(export_onnx_path)
def post_processing(model): log("Starting Post Processing") # Insert Top-1 node at the end model = model.transform(InsertTopK(k=1)) # Tidy-up again model = model.transform(InferShapes()) model = model.transform(FoldConstants()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(GiveReadableTensorNames()) model = model.transform(InferDataTypes()) model = model.transform(RemoveStaticGraphInputs()) log("Finished Post Processing!") save(model, "2_with_pre_post") return model
def test_const_folding(): raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") model = ModelWrapper(raw_m) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") raw_o = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/output_0.pb") input_tensor = onnx.load_tensor_from_string(raw_i) output_tensor = onnx.load_tensor_from_string(raw_o) input_dict = {"Input3": np_helper.to_array(input_tensor)} output_dict = oxe.execute_onnx(model, input_dict) assert np.isclose( np_helper.to_array(output_tensor), output_dict["Plus214_Output_0"], atol=1e-3 ).all()
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if ( n.op_type == self.op_name and not model.is_fork_node(n) and not model.is_join_node(n) ): consumer = model.find_consumer(n.output[0]) if ( consumer is not None and consumer.op_type == self.op_name and not model.is_join_node(consumer) ): op0_param_name = n.input[1] op1_param_name = consumer.input[1] op0_param = model.get_initializer(op0_param_name) op1_param = model.get_initializer(op1_param_name) assert ( op0_param is not None ), """Initializer for parameters for op0 is not set.""" assert ( op1_param is not None ), """Initializer for parameters for op1 is not set.""" start_name = n.input[0] end_name = consumer.output[0] # compute the new parameter new_param = self.make_collapsed_param_fxn(op0_param, op1_param) # make and insert new node new_node_param_name = op0_param_name new_node = oh.make_node( self.op_name, [start_name, new_node_param_name], [end_name] ) graph.node.insert(node_ind, new_node) # replace parameter value model.set_initializer(new_node_param_name, new_param) # be conservative with param/output DataTypes model.set_tensor_datatype(new_node_param_name, DataType.FLOAT32) model.set_tensor_datatype(end_name, DataType.FLOAT32) # remove old nodes graph.node.remove(n) graph.node.remove(consumer) graph_modified = True model = model.transform(InferShapes()) return (model, graph_modified)
def apply(self, model): graph = model.graph node_ind = 0 graph_modified = False for n in graph.node: node_ind += 1 if (n.op_type == "Transpose" and not model.is_fork_node(n) and not model.is_join_node(n)): consumer = model.find_consumer(n.output[0]) if (consumer is not None and consumer.op_type == "Mul" and not model.is_join_node(consumer)): mul_weight_name = consumer.input[1] A = model.get_initializer(mul_weight_name) if A is None: warnings.warn("Mul param is not constant, skipping") continue transp_node = n mul_node = consumer start_name = transp_node.input[0] middle_name = transp_node.output[0] end_name = mul_node.output[0] transp_in_shape = model.get_tensor_shape(start_name) transp_out_shape = model.get_tensor_shape(middle_name) transp_in_layout = model.get_tensor_layout(start_name) transp_out_layout = model.get_tensor_layout(middle_name) if transp_in_layout is None or transp_out_layout is None: warnings.warn("""Datalayout is not set for tensors. Transformation can't be applied.""") continue if all(x == 1 for x in A.shape): # if the mul is scalar, we can simply swap the order of ops # rewire transpose input to be mul input mul_node.input[0] = start_name model.set_tensor_shape(start_name, transp_in_shape) model.set_tensor_layout(start_name, transp_in_layout) mul_node.output[0] = middle_name model.set_tensor_shape(middle_name, transp_in_shape) model.set_tensor_layout(middle_name, transp_in_layout) transp_node.input[0] = middle_name transp_node.output[0] = end_name model.set_tensor_shape(end_name, transp_out_shape) model.set_tensor_layout(end_name, transp_out_layout) graph.node.remove(transp_node) graph.node.insert(node_ind, transp_node) graph_modified = True if graph_modified is True: model = model.transform(InferDataLayouts()) model = model.transform(InferShapes()) return (model, graph_modified)