def parse(self): logger.debug("Parsing %s...", self.type) op = self.tflite opcode = self.model.OperatorCodes(op.OpcodeIndex()).BuiltinCode() assert (opcode in self.TypeMapping) assert (op.InputsLength() == 3), "TFLite Conv always has bias" assert (op.OutputsLength() == 1) # input ilayout = Layout('NHWC', 'NCHW') it = self.parseInput(0, ilayout) # weight wlayout = Layout('CHWM', 'MCHW') if self.isDepthwise else Layout( 'OHWI', 'OIHW') wt = self.parseInput(1, wlayout) # bias self.parseInput(2, is_bias=True) # output olayout = Layout('NHWC', 'NCHW') ot = self.parseOutput(0, olayout) # options op_opt = op.BuiltinOptions() option = tflite.DepthwiseConv2DOptions( ) if self.isDepthwise else tflite.Conv2DOptions() option.Init(op_opt.Bytes, op_opt.Pos) self.attrs['dilations'] = [ option.DilationHFactor(), option.DilationWFactor() ] self.attrs['group'] = wt.shape[3] if self.isDepthwise else 1 self.attrs['kernel_shape'] = wt.shape[1:3] self.attrs['strides'] = [option.StrideH(), option.StrideW()] # XXX Not enabled as ONNXRuntime has limitation to infer pads for non-1 dilation # self.attrs['auto_pad'] = PaddingMapping[option.Padding()] if self.isDepthwise: assert (option.DepthMultiplier() == 1) self.attrs['pads'] = computePaddingSize(option.Padding(), it.shape[1:3], self.attrs['kernel_shape'], self.attrs['strides'], self.attrs['dilations']) handleFusedActivation(self, option, ot) self.setParsed()
def handle_conv2d_parsing(op: tflite.Operator, builtin_code: tflite.BuiltinOperator, graph: tflite.SubGraph, model: tflite.Model) -> Tuple[str, Layer]: op_opt = op.BuiltinOptions() opt = tflite.Conv2DOptions() opt.Init(op_opt.Bytes, op_opt.Pos) stride = opt.StrideW() input_ids = op.InputsAsNumpy() output_ids = op.OutputsAsNumpy() assert len(input_ids) == 3 input_tensor_id, filter_tensor_id, bias_tensor_id = list(input_ids) input_tensor = graph.Tensors(input_tensor_id) input_shape = input_tensor.ShapeAsNumpy() assert len(input_shape) == 4 _, input_width, input_height, input_depth = list(input_shape) filter_tensor = graph.Tensors(filter_tensor_id) filter_shape = filter_tensor.ShapeAsNumpy() assert len(filter_shape) == 4 if builtin_code == tflite.BuiltinOperator.CONV_2D: filter_count, filter_width, filter_height, filter_in_channels = list(filter_shape) else: filter_in_channels, filter_width, filter_height, filter_count = list(filter_shape) filter_quantization = filter_tensor.Quantization() filter_scales = filter_quantization.ScaleAsNumpy() filter_data = clean_data_int_op(model.Buffers(filter_tensor.Buffer()).DataAsNumpy(), is_32=False).reshape(filter_shape) padding = opt.Padding() padding_size = 0 if padding == tflite.Padding.SAME: padding_size = int(filter_width) // 2 else: # PADDING should be valid in this case raise Exception(f"Unexpected padding type: {padding}") bias_tensor = graph.Tensors(bias_tensor_id) bias_data = clean_data_int_op(model.Buffers(bias_tensor.Buffer()).DataAsNumpy(), is_32=True) assert len(output_ids) == 1 output_id = output_ids[0] output_tensor = graph.Tensors(output_id) output_shape = output_tensor.ShapeAsNumpy() output_batch_size, output_width, output_height, output_channels = list(output_shape) assert output_channels == filter_count output_quantization = output_tensor.Quantization().ScaleAsNumpy() assert len(output_quantization) == 1 output_scale = output_quantization[0] activation_type = opt.FusedActivationFunction() followed_by_relu = activation_type == tflite.ActivationFunctionType.RELU output_name = output_tensor.Name() input_name = input_tensor.Name() if builtin_code == tflite.BuiltinOperator.CONV_2D: return output_name, Conv2d( input=None, filter_width=filter_width, filter_height=filter_height, filter_in_channels=filter_in_channels, filter_count=filter_count, filter_data=filter_data, bias_data=bias_data, input_width=input_width, input_height=input_height, stride=stride, padding=padding_size, followed_by_relu=followed_by_relu, output_scale=output_scale, filter_scales=filter_scales, output_width=output_width, output_height=output_height ), input_name else: return output_name, Conv2d_DW( input=None, filter_width=filter_width, filter_height=filter_height, filter_in_channels=filter_in_channels, filter_count=filter_count, filter_data=filter_data, bias_data=bias_data, input_width=input_width, input_height=input_height, stride=stride, padding=padding_size, followed_by_relu=followed_by_relu, output_scale=output_scale, filter_scales=filter_scales, output_width=output_width, output_height=output_height ), input_name
def test_mobilenet(): cur_dir = os.path.dirname(os.path.abspath(__file__)) tflm_dir = os.path.abspath(cur_dir + '/../assets/tests') tflm_name = 'mobilenet_v1_1.0_224_quant.tflite' path = os.path.join(tflm_dir, tflm_name) with open(path, 'rb') as f: buf = f.read() model = tflite.Model.GetRootAsModel(buf, 0) ############# model ######################################################### # Version of the TFLite Converter. assert (model.Version() == 3) # Strings are binary format, need to decode. # Description is useful when exchanging models. assert (model.Description().decode('utf-8') == 'TOCO Converted.') # How many operator types in this model. assert (model.OperatorCodesLength() == 5) # A model may have multiple subgraphs. assert (model.SubgraphsLength() == 1) # How many tensor buffer. assert (model.BuffersLength() == 90) ############# subgraph ###################################################### # Chose one subgraph. graph = model.Subgraphs(0) # Tensors in the subgraph are represented by index description. assert (graph.InputsLength() == 1) assert (graph.OutputsLength() == 1) assert (graph.InputsAsNumpy()[0] == 88) assert (graph.OutputsAsNumpy()[0] == 87) # All arrays can dump as Numpy array, or access individually. assert (graph.Inputs(0) == 88) assert (graph.Outputs(0) == 87) # Name may used to debug or check for model containing multiple subgraphs. assert (graph.Name() == None) # Operators in the subgraph. assert (graph.OperatorsLength() == 31) # Let's use the first operator. op = graph.Operators(0) ############# operator type ################################################# # Operator Type is also stored as index, which can obtain from `Model` object. op_code = model.OperatorCodes(op.OpcodeIndex()) # The first operator is a convolution. assert (op_code.BuiltinCode() == tflite.BuiltinOperator.CONV_2D) # Custom operator need more interface, won't cover here. assert (op_code.BuiltinCode() != tflite.BuiltinOperator.CUSTOM) ############# the operator ################################################## # The first operator is a convolution. # The inputs are: data, weight and bias. assert (op.InputsLength() == 3) assert (op.OutputsLength() == 1) # The data of first Conv2D is input of the model assert (op.Inputs(0) == 88) assert (op.Inputs(0) == graph.Inputs(0)) # Operators have dedicated options per its type assert (op.BuiltinOptionsType() == tflite.BuiltinOptions.Conv2DOptions) op_opt = op.BuiltinOptions() ############# operator option ############################################### # Check the Conv2D options. # Parse the Table of options. opt = tflite.Conv2DOptions() opt.Init(op_opt.Bytes, op_opt.Pos) # The options. assert (opt.Padding() == tflite.Padding.SAME) assert (opt.StrideW() == 2) assert (opt.StrideH() == 2) assert (opt.DilationWFactor() == 1) assert (opt.DilationHFactor() == 1) # Further check activation function type if there were. assert ( opt.FusedActivationFunction() == tflite.ActivationFunctionType.NONE) ############# tensor ######################################################## # Check the weight tensor of the first convolution. tensor_index = op.Inputs(1) # use `graph.Tensors(index)` to get the tensor object. tensor = graph.Tensors(tensor_index) # view the shape assert (tensor.ShapeLength() == 4) assert (tensor.ShapeAsNumpy()[1] == 3) # All arrays can dump as Numpy array, or access individually. assert (tensor.Shape(1) == 3) # data type has been encoded assert (tensor.Type() == tflite.TensorType.UINT8) # name is in binary format, decode it assert ( tensor.Name().decode('utf-8') == 'MobilenetV1/MobilenetV1/Conv2d_0/weights_quant/FakeQuantWithMinMaxVars' ) # buffer of the tensor is represented in index too. assert (tensor.Buffer() == 66) # quantization parameters of the tensor, only valid for quantized model assert (tensor.Quantization()) assert (not tensor.IsVariable()) ############# quant ####################################################### # Quantization parameters of the tensor, only valid for quantized model quant = tensor.Quantization() # Scale and zero point assert (quant.ScaleAsNumpy()[0] == 0.02182667888700962) assert (quant.ZeroPointAsNumpy()[0] == 151) # Min/max also avaiable assert (quant.MinAsNumpy()[0] == -3.265998125076294) assert (quant.MaxAsNumpy()[0] == 2.2779781818389893) # All arrays can dump as Numpy array, or access individually. assert (quant.Scale(0) == 0.02182667888700962) assert (quant.ZeroPoint(0) == 151) assert (quant.Min(0) == -3.265998125076294) assert (quant.Max(0) == 2.2779781818389893) ############# memory ####################################################### # Get the buffer object. buf = model.Buffers(tensor.Buffer()) assert (buf.DataLength() == 864) assert (buf.DataAsNumpy()[0] == 151) # All arrays can dump as Numpy array, or access individually. assert (buf.Data(0) == 151) # The Numpy array is flattened. npa = buf.DataAsNumpy() assert (npa.shape == (864, ))
assert (op.OutputsLength() == 1) # The data of first Conv2D is input of the model assert (op.Inputs(0) == 88) assert (op.Inputs(0) == graph.Inputs(0)) # Operators have dedicated options per its type assert (op.BuiltinOptionsType() == tflite.BuiltinOptions.Conv2DOptions) op_opt = op.BuiltinOptions() ############# operator option ############################################### # Check the Conv2D options. # Parse the Table of options. opt = tflite.Conv2DOptions() opt.Init(op_opt.Bytes, op_opt.Pos) # The options. assert (opt.Padding() == tflite.Padding.SAME) assert (opt.StrideW() == 2) assert (opt.StrideH() == 2) assert (opt.DilationWFactor() == 1) assert (opt.DilationHFactor() == 1) # Further check activation function type if there were. assert (opt.FusedActivationFunction() == tflite.ActivationFunctionType.NONE) ############# tensor ######################################################## # Check the weight tensor of the first convolution.
def calc_flops(path): with open(path, 'rb') as f: buf = f.read() model = tflite.Model.GetRootAsModel(buf, 0) graph = model.Subgraphs(0) help(tflite.BuiltinOperator) # ABS = 101 # CONV_2D = 3 # CUMSUM = 128 # print funcs _dict_builtin_op_code_to_name = { v: k for k, v in tflite.BuiltinOperator.__dict__.items() if type(v) == int } def print_header(): print("%-18s | M FLOPS" % ("OP_NAME")) print("------------------------------") def print_flops(op_code_builtin, flops): print("%-18s | %.1f" % (_dict_builtin_op_code_to_name[op_code_builtin], flops / 1.0e6)) def print_none(op_code_builtin): print("%-18s | <IGNORED>" % (_dict_builtin_op_code_to_name[op_code_builtin])) def print_footer(total_flops): print("------------------------------") print("Total: %.1f M FLOPS" % (total_flops / 1.0e6)) total_flops = 0.0 print_header() for i in range(graph.OperatorsLength()): op = graph.Operators(i) op_code = model.OperatorCodes(op.OpcodeIndex()) op_code_builtin = op_code.BuiltinCode() op_opt = op.BuiltinOptions() flops = 0.0 if op_code_builtin == tflite.BuiltinOperator.CONV_2D: # input shapes: in, weight, bias in_shape = graph.Tensors(op.Inputs(0)).ShapeAsNumpy() filter_shape = graph.Tensors(op.Inputs(1)).ShapeAsNumpy() bias_shape = graph.Tensors(op.Inputs(2)).ShapeAsNumpy() # output shape out_shape = graph.Tensors(op.Outputs(0)).ShapeAsNumpy() # ops options opt = tflite.Conv2DOptions() opt.Init(op_opt.Bytes, op_opt.Pos) # opt.StrideH() # flops. 2x means mul(1)+add(1). 2x not needed if you calculate MACCs # refer to https://github.com/AlexeyAB/darknet/src/convolutional_layer.c `l.blopfs =` flops = 2 * out_shape[1] * out_shape[2] * filter_shape[ 0] * filter_shape[1] * filter_shape[2] * filter_shape[3] print_flops(op_code_builtin, flops) elif op_code_builtin == tflite.BuiltinOperator.DEPTHWISE_CONV_2D: in_shape = graph.Tensors(op.Inputs(0)).ShapeAsNumpy() filter_shape = graph.Tensors(op.Inputs(1)).ShapeAsNumpy() out_shape = graph.Tensors(op.Outputs(0)).ShapeAsNumpy() # flops flops = 2 * out_shape[1] * out_shape[2] * filter_shape[ 0] * filter_shape[1] * filter_shape[2] * filter_shape[3] print_flops(op_code_builtin, flops) else: print_none(op_code_builtin) total_flops += flops print_footer(total_flops)