Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support of some fp32 operators #58

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
108 changes: 75 additions & 33 deletions code_generator/CodeGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

import os

import numpy as np

from .constant import FUSE_SGD_UPDATE_STR, FUSHION_CONFIG
from .operators.basic_utils import tensor
from .OpGenerator import OpGenerator

Codegen_root = "./codegen/"
Expand All @@ -34,8 +37,6 @@ class CodeGenerator:
"""Provide utilities to generate C code for a given model and memory schdeule."""

parse_count = 0
header_handle = None
source_handle = None

def __init__(
self,
Expand Down Expand Up @@ -585,12 +586,13 @@ def _parseTrainable(self):
)
else:
self._parseBias(self.parse_count, layer_info["bias"].flatten())
self._parseEffectivescales(self.parse_count, layer_info["effective_scale"].flatten())
self._parseRequantize(
self.parse_count,
layer_info["shift"].flatten(),
layer_info["multiplier"].flatten(),
)
if layer_info["input_dtype"] == "int8":
self._parseEffectivescales(self.parse_count, layer_info["effective_scale"].flatten())
self._parseRequantize(
self.parse_count,
layer_info["shift"].flatten(),
layer_info["multiplier"].flatten(),
)

layer_info["parsed_trainable"] = self.parse_count
self.parse_count += 1
Expand Down Expand Up @@ -770,6 +772,37 @@ def _parseTrainable(self):

layer_info["parsed_trainable"] = self.parse_count
self.parse_count += 1
else:
# Parse constants of inputs
for t in op.input_tensors:
if t.constant():
# for TTE compatible
if "constant" in layer_info and layer_info["constant"] is not None:
continue
if t.data is None:
raise ValueError(f"constant tensor data not found for op:{layer_info['op']}")
self._parseConstant(t)

def _parseConstant(self, t: tensor):
def type_to_c_type(type: str) -> str:
if type == "int8":
return "unsigned char"
elif type == "float32":
return "float"
elif type == "int32":
return "int32_t"
elif type == "bool":
return "char" # Using bytes to store boolean
raise NotImplementedError

fp = self.header_handle
# 8bit implementation
string = f"const {type_to_c_type(t.dtype)} {t.graph_idx}" + "[" + str(np.prod(t.size)) + "] = {"
flat_data = t.data.flatten()
for d in flat_data:
string += f"{d}, "
string += "};\n"
fp.write(string)

def _parseCWHWeight(self, Lindex, weight, height, width, channel):
fp = self.header_handle
Expand Down Expand Up @@ -818,34 +851,43 @@ def _parseEffectivescales(self, Lindex, scales):
def _parseWeight(self, Lindex, weight, weight_name=None, is_const=True):
fp = self.header_handle
const_str = "const " if is_const else ""
string = f"{const_str}unsigned char weight" + str(Lindex) + "[" + str(len(weight)) + "] = {"
fp.write(string)
for _, value in enumerate(weight):
value = int(value)
if value < 0:
value += 256
fp.write(str(format(value, "#04x")) + ", ")
fp.write("};\n")

if self.is_training:
string = f"{const_str}float weight_fp" + str(Lindex) + "[" + str(len(weight)) + "] = {"
if weight.dtype == "float32":
string = f"{const_str}unsigned char weight_fp" + str(Lindex) + "[" + str(len(weight)) + "] = {"
for _, value in enumerate(weight):
string += f"{value}, "
string += "};\n"
fp.write(string)
elif weight.dtype == "int8":
string = f"{const_str}unsigned char weight" + str(Lindex) + "[" + str(len(weight)) + "] = {"
fp.write(string)
for _, w in enumerate(weight):
value = float(w)
fp.write(str(value) + ", ")
for _, value in enumerate(weight):
value = int(value)
if value < 0:
value += 256
fp.write(str(format(value, "#04x")) + ", ")
fp.write("};\n")

if weight_name is not None:
for r in self.trainSRAMTable:
if r.name == weight_name:
return
self.trainSRAMTable.append(tensorRecorder(weight_name, len(weight), "unknown"))

if weight.dtype == "int8":
string = f"{const_str}unsigned char* {weight_name}=weight" + str(Lindex) + ";\n"
else:
raise NotImplementedError
fp.write(string)
if self.is_training:
string = f"{const_str}float weight_fp" + str(Lindex) + "[" + str(len(weight)) + "] = {"
fp.write(string)
for _, w in enumerate(weight):
value = float(w)
fp.write(str(value) + ", ")
fp.write("};\n")

if weight_name is not None:
for r in self.trainSRAMTable:
if r.name == weight_name:
return
self.trainSRAMTable.append(tensorRecorder(weight_name, len(weight), "unknown"))

if weight.dtype == "int8":
string = f"{const_str}unsigned char* {weight_name}=weight" + str(Lindex) + ";\n"
else:
raise NotImplementedError
fp.write(string)
else:
raise NotImplementedError

def _parseWeightPartial(self, Lindex, weight, first_k_channel=None, weight_name=None, is_const=True):
fp = self.header_handle
Expand Down
1 change: 1 addition & 0 deletions code_generator/TTEParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,7 @@ def _convert_sub(self, op):
"input2_idx": input2_info["name"],
"output_idx": output_info["name"],
"input_size": input0_h * input0_w * input0_c,
"input2_size": input0_h * input0_w * input0_c,
"input_dtype": input_dtype,
"input2_dtype": input2_dtype,
"output_dtype": output_dtype,
Expand Down
109 changes: 89 additions & 20 deletions code_generator/TfliteConvertor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,38 @@
# ----------------------------------------------------------------------

import logging
from typing import List

import code_generator.converters.tflite_parser as TF_Parser
from code_generator.converters.tflite_parser.mean1dto2d import MEAN2D
from code_generator.converters.tflite_parser.reshape import tensor_mapping
from code_generator.converters.tflite_parser.utils import get_input_tensors, get_output_tensors, getOpCodeStr

from .constant import SKIP_OPs
from .tflite import Model

regular_opconverter = {
"ADD": TF_Parser.parse_add,
"AVERAGE_POOL_2D": TF_Parser.parse_avgpool,
"RESIZE_NEAREST_NEIGHBOR": TF_Parser.parse_upsample,
"MAX_POOL_2D": TF_Parser.parse_maxpool,
"FULLY_CONNECTED": TF_Parser.parse_fc,
"DIV": TF_Parser.parse_div,
"BATCH_MATMUL": TF_Parser.parse_batchmatmul,
"NOT_EQUAL": TF_Parser.parse_notequal,
"EQUAL": TF_Parser.parse_equal,
"CONCATENATION": TF_Parser.parse_concat,
"CAST": TF_Parser.parse_cast,
"SUB": TF_Parser.parse_sub,
"MUL": TF_Parser.parse_mul,
"SOFTMAX": TF_Parser.parse_softmax,
"SQUARED_DIFFERENCE": TF_Parser.parse_squarddiff,
"RSQRT": TF_Parser.parse_rsqrt,
"SLICE": TF_Parser.parse_slice,
"MEAN": TF_Parser.parse_mean1d,
"TRANSPOSE": TF_Parser.parse_transpose,
}


# Parse tflite model into TinyEngine IR format
class TfliteConvertor(object):
Expand All @@ -37,6 +61,7 @@ def __init__(self, filepath):
self.tmpPADIndice = None
self.skip_transpose = None
self.average_1D_to_2D_holder = MEAN2D() # For merging 1D to 2D
self.inplace_reshape_table: List[tensor_mapping] = [] # A list of tensor_mapping

# public functions
def loadTFmodel(self, filepath):
Expand Down Expand Up @@ -104,41 +129,62 @@ def parseOperatorInfo(self):

self.layer.append(SEelementmult_op)
continue
if i + 2 < operators_len - 2:
next_op = self.subgraph.Operators(i + 1)
next_next_op = self.subgraph.Operators(i + 2)
three_op_sequence = [op, next_op, next_next_op]

if self.checkIfMergeTransposeTwoMean1d(three_op_sequence):
logging.info("found target to merge transpose and two mean1d")
self._convert_TRANSPOSE(op)
skip_next_ops = 2
ret_op = TF_Parser.parse_mean1dto2d(next_op, self.model, self.average_1D_to_2D_holder)
ret_op = TF_Parser.parse_mean1dto2d(next_next_op, self.model, self.average_1D_to_2D_holder)
if ret_op is not None:
if self.skip_transpose is not None:
ret_op.params["input_idx"] = self.skip_transpose.input_idx
ret_op.input_tensors[0].graph_idx = self.skip_transpose.input_idx
self.layer.append(ret_op)
continue
if i + 1 < operators_len - 1:
next_op = self.subgraph.Operators(i + 1)
two_op_sequence = [op, next_op]

if self.checkIfMergeTwoMean1d(two_op_sequence):
logging.info("found target to merge two mean1d")
skip_next_ops = 1
ret_op = TF_Parser.parse_mean1dto2d(op, self.model, self.average_1D_to_2D_holder)
ret_op = TF_Parser.parse_mean1dto2d(next_op, self.model, self.average_1D_to_2D_holder)
if ret_op is not None:
if self.skip_transpose is not None:
ret_op.params["input_idx"] = self.skip_transpose.input_idx
ret_op.input_tensors[0].graph_idx = self.skip_transpose.input_idx
self.layer.append(ret_op)
continue

# parse the op
self._handleOperator(op)

# Handle inplace_reshape_table here
logging.error("Please handle inplace_reshape_table here for fused tensors.")

# handle one op and parse it into layers[] for supported operators
def _handleOperator(self, op):
op_code_str = getOpCodeStr(op, self.model)
if op_code_str == "CONV_2D":
self.layer.append(TF_Parser.parse_conv2d(op, self.model, self.tmpPADIndice))
self.tmpPADIndice = None
elif op_code_str == "ADD":
self.layer.append(TF_Parser.parse_add(op, self.model))
elif op_code_str == "AVERAGE_POOL_2D":
self.layer.append(TF_Parser.parse_avgpool(op, self.model))
elif op_code_str == "DEPTHWISE_CONV_2D":
self.layer.append(TF_Parser.parse_conv2d(op, self.model, self.tmpPADIndice))
self.tmpPADIndice = None
elif op_code_str == "PAD":
self._convert_PAD(op)
elif op_code_str == "RESIZE_NEAREST_NEIGHBOR":
self.layer.append(TF_Parser.parse_upsample(op, self.model))
elif op_code_str == "MAX_POOL_2D":
self.layer.append(TF_Parser.parse_maxpool(op, self.model))
elif op_code_str in "MEAN":
ret_op = TF_Parser.parse_mead1dto2d(op, self.model, self.average_1D_to_2D_holder)
if ret_op is not None:
# TODO: This only handle a specific graph: TRANSPOSE -> MEAN -> MEANS
if self.skip_transpose is not None:
ret_op.params["input_idx"] = self.skip_transpose.input_idx
ret_op.input_tensors[0].graph_idx = self.skip_transpose.input_idx
self.layer.append(ret_op)
elif op_code_str == "TRANSPOSE":
self._convert_TRANSPOSE(op)
elif op_code_str in "FULLY_CONNECTED":
self.layer.append(TF_Parser.parse_fc(op, self.model))
# elif op_code_str == "TRANSPOSE":
# self._convert_TRANSPOSE(op)
elif op_code_str == "RESHAPE":
self.inplace_reshape_table.append(TF_Parser.parse_reshape_fuse_tensor_tuple(op, self.model))
elif op_code_str in regular_opconverter:
self.layer.append(regular_opconverter[op_code_str](op, self.model))
elif op_code_str in SKIP_OPs:
pass
else:
Expand All @@ -156,6 +202,29 @@ def checkIfRequireSEelementmult(self, three_op_sequence):
return True
return False

# | TRANSPOSE -> MEAN -> MEAN -> |
# | -> AVG_POOL_2D |
# | Fuse Target |
def checkIfMergeTwoMean1d(self, two_op_sequence):
if (
getOpCodeStr(two_op_sequence[0], self.model) == "MEAN"
and getOpCodeStr(two_op_sequence[1], self.model) == "MEAN"
):
return True
return False

# | MEAN -> MEAN -> |
# | -> AVG_POOL_2D |
# | Fuse Target |
def checkIfMergeTransposeTwoMean1d(self, three_op_sequence):
if (
getOpCodeStr(three_op_sequence[1], self.model) == "TRANSPOSE"
and getOpCodeStr(three_op_sequence[1], self.model) == "MEAN"
and getOpCodeStr(three_op_sequence[2], self.model) == "MEAN"
):
return True
return False

def _convert_PAD(self, op):
# get input, weight, and output tensors
input_tensors = get_input_tensors(op, self.model)
Expand Down
17 changes: 16 additions & 1 deletion code_generator/converters/tflite_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
from .add import parse_add
from .avgpool import parse_avgpool
from .batchmatmul import parse_batchmatmul
from .cast import parse_cast
from .concat import parse_concat
from .conv2d import parse_conv2d
from .div import parse_div
from .equal import parse_equal
from .fc import parse_fc
from .maxpool import parse_maxpool
from .mean1dto2d import parse_mead1dto2d
from .mean1d import parse_mean1d
from .mean1dto2d import parse_mean1dto2d
from .mul import parse_mul
from .notequal import parse_notequal
from .reshape import parse_reshape_fuse_tensor_tuple
from .rsqrt import parse_rsqrt
from .SEelement import parse_SEelement
from .slice import parse_slice
from .softmax import parse_softmax
from .squarddiff import parse_squarddiff
from .sub import parse_sub
from .transpose import parse_transpose
from .upsample import parse_upsample
Loading