From fc3ec9c733da169ab8cdbeb4fe451017cafe14cd Mon Sep 17 00:00:00 2001 From: Yaman Umuroglu <yamanu@xilinx.com> Date: Fri, 30 Aug 2019 15:22:27 +0100 Subject: [PATCH] fix issues from pre-commit --- src/finn/core/tensor.py | 163 +++++++++++++++++++++------------------- src/finn/onnx_exec.py | 97 ++++++++++++------------ tests/test_datatypes.py | 61 ++++++++------- 3 files changed, 165 insertions(+), 156 deletions(-) diff --git a/src/finn/core/tensor.py b/src/finn/core/tensor.py index 34e6479c3..257078ad1 100644 --- a/src/finn/core/tensor.py +++ b/src/finn/core/tensor.py @@ -24,88 +24,95 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import numpy as np from enum import Enum +import numpy as np + + class DataType(Enum): - FLOAT32 = 0 - BINARY = 1 - BIPOLAR = 2 - UINT2 = 3 - UINT3 = 4 - UINT4 = 5 - UINT8 = 6 - UINT16 = 7 - UINT32 = 8 - INT2 = 9 - INT3 = 10 - INT4 = 11 - INT8 = 12 - INT16 = 13 - INT32 = 14 - - def bitwidth(self): - """Returns the number of bits required for this DataType.""" - - if self.name.startswith("UINT"): - return int(self.name.strip("UINT")) - elif self.name.startswith("INT"): - return int(self.name.strip("INT")) - elif "FLOAT" in self.name: - return int(self.name.strip("FLOAT")) - elif self.name in ["BINARY", "BIPOLAR"]: - return 1 - else: - raise Exception("Unrecognized data type: %s" % self.name) - - def min(self): - """Returns the smallest possible value allowed by this DataType.""" - - if self.name.startswith("UINT") or self.name == "BINARY": - return 0 - elif self.name.startswith("INT"): - return -(2 ** (self.bitwidth() - 1)) - elif self.name == "FLOAT32": - return np.finfo(np.float32).min - elif self.name == "BIPOLAR": - return -1 - else: - raise Exception("Unrecognized data type: %s" % self.name) - - def max(self): - """Returns the largest possible value allowed by this DataType.""" - - if self.name.startswith("UINT"): - return (2 ** (self.bitwidth())) - 1 - elif self.name == "BINARY": - return +1 - elif self.name.startswith("INT"): - return (2 ** (self.bitwidth() - 1)) - 1 - elif self.name == "FLOAT32": - return np.finfo(np.float32).max - elif self.name == "BIPOLAR": - return +1 - else: - raise Exception("Unrecognized data type: %s" % self.name) - - def allowed(self, value): - """Check whether given value is allowed for this DataType. + FLOAT32 = 0 + BINARY = 1 + BIPOLAR = 2 + UINT2 = 3 + UINT3 = 4 + UINT4 = 5 + UINT8 = 6 + UINT16 = 7 + UINT32 = 8 + INT2 = 9 + INT3 = 10 + INT4 = 11 + INT8 = 12 + INT16 = 13 + INT32 = 14 + + def bitwidth(self): + """Returns the number of bits required for this DataType.""" + + if self.name.startswith("UINT"): + return int(self.name.strip("UINT")) + elif self.name.startswith("INT"): + return int(self.name.strip("INT")) + elif "FLOAT" in self.name: + return int(self.name.strip("FLOAT")) + elif self.name in ["BINARY", "BIPOLAR"]: + return 1 + else: + raise Exception("Unrecognized data type: %s" % self.name) + + def min(self): + """Returns the smallest possible value allowed by this DataType.""" + + if self.name.startswith("UINT") or self.name == "BINARY": + return 0 + elif self.name.startswith("INT"): + return -(2 ** (self.bitwidth() - 1)) + elif self.name == "FLOAT32": + return np.finfo(np.float32).min + elif self.name == "BIPOLAR": + return -1 + else: + raise Exception("Unrecognized data type: %s" % self.name) + + def max(self): + """Returns the largest possible value allowed by this DataType.""" + + if self.name.startswith("UINT"): + return (2 ** (self.bitwidth())) - 1 + elif self.name == "BINARY": + return +1 + elif self.name.startswith("INT"): + return (2 ** (self.bitwidth() - 1)) - 1 + elif self.name == "FLOAT32": + return np.finfo(np.float32).max + elif self.name == "BIPOLAR": + return +1 + else: + raise Exception("Unrecognized data type: %s" % self.name) + + def allowed(self, value): + """Check whether given value is allowed for this DataType. value (float32): value to be checked""" - if "FLOAT" in self.name: - return True - elif "INT" in self.name: - return (self.min() <= value) and (value <= self.max()) and float(value).is_integer() - elif self.name == "BINARY": - return value in [0, 1] - elif self.name == "BIPOLAR": - return value in [-1, +1] - else: - raise Exception("Unrecognized data type: %s" % self.name) + if "FLOAT" in self.name: + return True + elif "INT" in self.name: + return ( + (self.min() <= value) + and (value <= self.max()) + and float(value).is_integer() + ) + elif self.name == "BINARY": + return value in [0, 1] + elif self.name == "BIPOLAR": + return value in [-1, +1] + else: + raise Exception("Unrecognized data type: %s" % self.name) + class Tensor(object): - """A multidimensional array of numbers of given datatype. + """A multidimensional array of numbers of given datatype. Attributes: dtype (DataType): Element data type for this Tensor @@ -114,7 +121,7 @@ class Tensor(object): ["N", "C", "H", "W"] """ - def __init__(self, dtype, data, dim_names = []): - self.dtype = dtype - self.data = data - self.dim_names = dim_names + def __init__(self, dtype, data, dim_names=[]): + self.dtype = dtype + self.data = data + self.dim_names = dim_names diff --git a/src/finn/onnx_exec.py b/src/finn/onnx_exec.py index ffb1cccff..c18d6130c 100644 --- a/src/finn/onnx_exec.py +++ b/src/finn/onnx_exec.py @@ -24,52 +24,55 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import numpy as np import onnx import onnx.helper as helper -import numpy as np -from functools import reduce -from onnx import numpy_helper as np_helper import onnxruntime as rt - +from onnx import numpy_helper as np_helper model = onnx.load_model("model.onnx") graph = model.graph + def valueinfo_to_tensor(vi): - """Creates an all-zeroes numpy tensor from a ValueInfoProto.""" + """Creates an all-zeroes numpy tensor from a ValueInfoProto.""" + + dims = [x.dim_value for x in vi.type.tensor_type.shape.dim] + return np.zeros( + dims, dtype=onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[vi.type.tensor_type.elem_type] + ) - dims = [x.dim_value for x in vi.type.tensor_type.shape.dim] - return np.zeros(dims, dtype = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[vi.type.tensor_type.elem_type]) def execute_node(node, context, graph): - """Call onnxruntime to execute a single node. Input/output provided via context.""" + """Call onnxruntime to execute a single node. Input/output provided via context.""" + + # onnxruntime unfortunately does not implement run_node as defined by ONNX, + # it can only execute entire models -- so we create a model which solely + # consists of our current node. + node_inputs = list(filter(lambda x: x.name in node.input, graph.input)) + node_inputs += list(filter(lambda x: x.name in node.input, graph.value_info)) + node_outputs = list(filter(lambda x: x.name in node.output, graph.output)) + node_outputs += list(filter(lambda x: x.name in node.output, graph.value_info)) + node_graph = helper.make_graph( + nodes=[node], name="single-node-exec", inputs=node_inputs, outputs=node_outputs + ) + node_model = helper.make_model(node_graph) + input_dict = dict() + for inp in node.input: + input_dict[inp] = context[inp] + print("Input shape for %s: %s" % (inp, context[inp].shape)) + sess = rt.InferenceSession(node_model.SerializeToString()) + output_list = sess.run(None, input_dict) + for output_ind in range(len(node.output)): + outp = node.output[output_ind] + if output_list[output_ind].shape != context[outp].shape: + raise Exception( + "Output shapes disagree after node execution: found %s vs expected %s" + % (str(output_list[output_ind].shape.shape), str(context[outp].shape)) + ) + context[outp] = output_list[output_ind] + print("Output shape for %s: %s" % (outp, context[outp].shape)) - # onnxruntime unfortunately does not implement run_node as defined by ONNX, - # it can only execute entire models -- so we create a model which solely - # consists of our current node. - node_inputs = list(filter(lambda x: x.name in node.input, graph.input)) - node_inputs += list(filter(lambda x: x.name in node.input, graph.value_info)) - node_outputs = list(filter(lambda x: x.name in node.output, graph.output)) - node_outputs += (list(filter(lambda x: x.name in node.output, graph.value_info))) - node_graph = helper.make_graph( - nodes = [node], - name = "single-node-exec", - inputs = node_inputs, - outputs = node_outputs - ) - node_model = helper.make_model(node_graph) - input_dict = dict() - for inp in node.input: - input_dict[inp] = context[inp] - print("Input shape for %s: %s" % (inp, context[inp].shape)) - sess = rt.InferenceSession(node_model.SerializeToString()) - output_list = sess.run(None, input_dict) - for output_ind in range(len(node.output)): - outp = node.output[output_ind] - if output_list[output_ind].shape != context[outp].shape: - raise Exception("Output shapes disagree after node execution: found %s vs expected %s" % (str(output_list[output_ind].shape.shape), str(context[outp].shape))) - context[outp] = output_list[output_ind] - print("Output shape for %s: %s" % (outp, context[outp].shape)) # first, we need to make sure that every variable required by the graph has # some buffer associated with it. this includes graph inputs (which includes @@ -79,32 +82,32 @@ def execute_node(node, context, graph): execution_context = dict() # make empty tensors for all the graph inputs and outputs for vi in graph.input: - new_tensor = valueinfo_to_tensor(vi) - execution_context[vi.name] = new_tensor + new_tensor = valueinfo_to_tensor(vi) + execution_context[vi.name] = new_tensor for vi in graph.output: - new_tensor = valueinfo_to_tensor(vi) - execution_context[vi.name] = new_tensor + new_tensor = valueinfo_to_tensor(vi) + execution_context[vi.name] = new_tensor # make empty tensors for all intermediate buffers # TODO are we guaranteed to have the .value_info filled? # do we need to call ONNX shape inference first? for vi in graph.value_info: - new_tensor = valueinfo_to_tensor(vi) - execution_context[vi.name] = new_tensor + new_tensor = valueinfo_to_tensor(vi) + execution_context[vi.name] = new_tensor # fill in the constants provided by the initializers (TensorProto to npy) for t in graph.initializer: - execution_context[t.name] = np_helper.to_array(t) + execution_context[t.name] = np_helper.to_array(t) # now call each node in the graph nodes list # we can simply walk down the list since the ONNX spec guarantees that it is # topologically sorted all_used_ops = set() for node in graph.node: - print("Node name: %s Type: %s" % (node.name, node.op_type)) - all_used_ops.add(node.op_type) - print("Input(s): " + str(node.input)) - print("Output(s): " + str(node.output)) - print("Attribute(s): " + str(node.attribute)) - execute_node(node, execution_context, graph) + print("Node name: %s Type: %s" % (node.name, node.op_type)) + all_used_ops.add(node.op_type) + print("Input(s): " + str(node.input)) + print("Output(s): " + str(node.output)) + print("Attribute(s): " + str(node.attribute)) + execute_node(node, execution_context, graph) print("Final output(s): ") print(execution_context[graph.output[0].name]) diff --git a/tests/test_datatypes.py b/tests/test_datatypes.py index 9ebd2f1ee..06b2b3597 100644 --- a/tests/test_datatypes.py +++ b/tests/test_datatypes.py @@ -1,36 +1,35 @@ # -*- coding: utf-8 -*- -import pytest - import finn.core.tensor as ten + def test_datatypes(): - assert ten.DataType.BIPOLAR.allowed(-1) == True - assert ten.DataType.BIPOLAR.allowed(0) == False - assert ten.DataType.BINARY.allowed(-1) == False - assert ten.DataType.BINARY.allowed(-1) == True - assert ten.DataType.UINT2.allowed(2) == True - assert ten.DataType.UINT2.allowed(10) == False - assert ten.DataType.UINT3.allowed(5) == True - assert ten.DataType.UINT3.allowed(-7) == False - assert ten.DataType.UINT4.allowed(15) == True - assert ten.DataType.UINT4.allowed(150) == False - assert ten.DataType.UINT8.allowed(150) == True - assert ten.DataType.UINT8.allowed(777) == False - assert ten.DataType.UINT16.allowed(14500) == True - assert ten.DataType.UINT16.allowed(-1) == False - assert ten.DataType.UINT32.allowed(2 ** 10) == True - assert ten.DataType.UINT32.allowed(-1) == False - assert ten.DataType.INT2.allowed(2) == True - assert ten.DataType.INT2.allowed(-10) == False - assert ten.DataType.INT3.allowed(5) == False - assert ten.DataType.INT3.allowed(-2) == True - assert ten.DataType.INT4.allowed(15) == False - assert ten.DataType.INT4.allowed(-5) == True - assert ten.DataType.INT8.allowed(150) == False - assert ten.DataType.INT8.allowed(-127) == True - assert ten.DataType.INT16.allowed(-1.04) == False - assert ten.DataType.INT16.allowed(-7777) == True - assert ten.DataType.INT32.allowed(7.77) == False - assert ten.DataType.INT32.allowed(-5) == True - assert ten.DataType.INT32.allowed(5) == True + assert ten.DataType.BIPOLAR.allowed(-1) is True + assert ten.DataType.BIPOLAR.allowed(0) is False + assert ten.DataType.BINARY.allowed(-1) is False + assert ten.DataType.BINARY.allowed(-1) is True + assert ten.DataType.UINT2.allowed(2) is True + assert ten.DataType.UINT2.allowed(10) is False + assert ten.DataType.UINT3.allowed(5) is True + assert ten.DataType.UINT3.allowed(-7) is False + assert ten.DataType.UINT4.allowed(15) is True + assert ten.DataType.UINT4.allowed(150) is False + assert ten.DataType.UINT8.allowed(150) is True + assert ten.DataType.UINT8.allowed(777) is False + assert ten.DataType.UINT16.allowed(14500) is True + assert ten.DataType.UINT16.allowed(-1) is False + assert ten.DataType.UINT32.allowed(2 ** 10) is True + assert ten.DataType.UINT32.allowed(-1) is False + assert ten.DataType.INT2.allowed(2) is True + assert ten.DataType.INT2.allowed(-10) is False + assert ten.DataType.INT3.allowed(5) is False + assert ten.DataType.INT3.allowed(-2) is True + assert ten.DataType.INT4.allowed(15) is False + assert ten.DataType.INT4.allowed(-5) is True + assert ten.DataType.INT8.allowed(150) is False + assert ten.DataType.INT8.allowed(-127) is True + assert ten.DataType.INT16.allowed(-1.04) is False + assert ten.DataType.INT16.allowed(-7777) is True + assert ten.DataType.INT32.allowed(7.77) is False + assert ten.DataType.INT32.allowed(-5) is True + assert ten.DataType.INT32.allowed(5) is True -- GitLab