diff --git a/docker/Dockerfile.finn_ci b/docker/Dockerfile.finn_ci index 4518fe564670364b1ad464099d880c958e780c24..b9c71df58b1869cec7a49d2e5f4dd19d09949097 100644 --- a/docker/Dockerfile.finn_ci +++ b/docker/Dockerfile.finn_ci @@ -47,6 +47,8 @@ RUN bash xrtdeps.sh RUN rm xrtdeps.sh # cloning dependency repos +# finn-base +RUN git clone https://github.com/maltanar/finn-base.git /workspace/finn-base # Brevitas RUN git clone https://github.com/Xilinx/brevitas.git /workspace/brevitas # CNPY diff --git a/docker/Dockerfile.finn_dev b/docker/Dockerfile.finn_dev index 4be975442665c680ccbb8aefd1d0bb8d07b81cc9..fe7f45b461f517ae736b11ab7871b56ec8f9061a 100644 --- a/docker/Dockerfile.finn_dev +++ b/docker/Dockerfile.finn_dev @@ -71,6 +71,8 @@ RUN chown -R $UNAME:$GNAME /home/$UNAME USER $UNAME # cloning dependency repos (as user) +# finn-base +RUN git clone https://github.com/maltanar/finn-base.git /workspace/finn-base # Brevitas RUN git clone https://github.com/Xilinx/brevitas.git /workspace/brevitas # CNPY diff --git a/docker/finn_entrypoint.sh b/docker/finn_entrypoint.sh index 4569e5be77e9d3f6906f94e279938af9abe28345..6dcf99e7589e4e0e6c50b626ea53948f6153ae3c 100644 --- a/docker/finn_entrypoint.sh +++ b/docker/finn_entrypoint.sh @@ -12,6 +12,7 @@ gecho () { # checkout the correct dependency repo commits # the repos themselves are cloned in the Dockerfile +FINN_BASE_COMMIT=ceb1219b5aba396dde41967a929e1f08887653ce BREVITAS_COMMIT=6ffefa8dbf37fdb0f44c994f34604c29fadb16b0 CNPY_COMMIT=4e8810b1a8637695171ed346ce68f6984e585ef4 HLSLIB_COMMIT=cfafe11a93b79ab1af7529d68f08886913a6466e @@ -19,6 +20,11 @@ PYVERILATOR_COMMIT=c97a5ba41bbc7c419d6f25c74cdf3bdc3393174f OMX_COMMIT=1bae737669901e762f581af73348332b5c4b2ada gecho "Setting up known-good commit versions for FINN dependencies" +# finn-base +gecho "finn-base @ $FINN_BASE_COMMIT" +git -C /workspace/finn-base pull --quiet +git -C /workspace/finn-base checkout $FINN_BASE_COMMIT --quiet +pip install --user -e /workspace/finn-base # Brevitas gecho "brevitas @ $BREVITAS_COMMIT" git -C /workspace/brevitas pull --quiet diff --git a/notebooks/advanced/1_custom_transformation_pass.ipynb b/notebooks/advanced/1_custom_transformation_pass.ipynb index a9345401e28b89ba3a206b81e7c3b022bedae023..9c54d6f26913e558867b2f800b424f4157f47491 100644 --- a/notebooks/advanced/1_custom_transformation_pass.ipynb +++ b/notebooks/advanced/1_custom_transformation_pass.ipynb @@ -120,7 +120,7 @@ } ], "source": [ - "from finn.transformation import Transformation\n", + "from finn.transformation.base import Transformation\n", "\n", "showSrc(Transformation)" ] @@ -199,7 +199,7 @@ "metadata": {}, "outputs": [], "source": [ - "from finn.transformation import Transformation\n", + "from finn.transformation.base import Transformation\n", "\n", "class ConvertSubToAdd(Transformation):\n", " def apply(self, model):\n", @@ -352,7 +352,7 @@ } ], "source": [ - "from finn.transformation import NodeLocalTransformation\n", + "from finn.transformation.base import NodeLocalTransformation\n", "\n", "showSrc(NodeLocalTransformation)" ] diff --git a/requirements.txt b/requirements.txt index ba7bc716b741911820e67f1455aeca4c05e6e005..71eaa3224213f0ff021107ce5090a9fb1901234a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,8 @@ docrep==0.2.7 future==0.18.2 gspread==3.6.0 numpy==1.18.0 -onnx==1.6.0 -onnxruntime==1.2.0 +onnx==1.7.0 +onnxruntime==1.4.0 pre-commit==2.6.0 scipy==1.5.2 toposort==1.5 diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 83c8e8bed70797f7d6c0138968f750f72e790386..0000000000000000000000000000000000000000 --- a/src/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/finn/__init__.py b/src/finn/__init__.py deleted file mode 100644 index 76b4dacddb18404218dcb842bddf79b5f72eeb8e..0000000000000000000000000000000000000000 --- a/src/finn/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# -*- coding: utf-8 -*- -from pkg_resources import get_distribution, DistributionNotFound - -try: - # Change here if project is renamed and does not equal the package name - dist_name = "FINN" - __version__ = get_distribution(dist_name).version -except DistributionNotFound: - __version__ = "unknown" -finally: - del get_distribution, DistributionNotFound diff --git a/src/finn/analysis/__init__.py b/src/finn/analysis/__init__.py deleted file mode 100644 index c3f810e6f658c248d3aee26a4e174baa5cf44ce5..0000000000000000000000000000000000000000 --- a/src/finn/analysis/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" -How to write an analysis pass for FINN --------------------------------------- - -An analysis pass traverses the graph structure and produces information about -certain properties. The convention is to take in a ModelWrapper, and return -a dictionary of named properties that the analysis extracts. -""" diff --git a/src/finn/analysis/topology.py b/src/finn/analysis/topology.py deleted file mode 100644 index acdb8ed7fcf41fd041c3601b2ee4fe67b6dc5f19..0000000000000000000000000000000000000000 --- a/src/finn/analysis/topology.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 - - -def is_linear(model): - """Checks whether the given model graph is linear. This is done by looking - at the fan-out of each tensor. All tensors have a fan-out <= 1 in a linear - graph. - - Returns {"is_linear": Bool}.""" - per_tensor_fanouts = get_per_tensor_fanouts(model) - # check for tensors that have fanout > 1 - multi_fanouts = list(filter(lambda x: x[1] > 1, per_tensor_fanouts.items())) - return {"is_linear": len(multi_fanouts) == 0} - - -def get_per_tensor_fanouts(model): - """Returns a dictionary of {tensor_name: tensor_fanout} for the model.""" - # make execution context to get a list of tensors - per_tensor_fanouts = model.make_empty_exec_context() - # replace every tensor with its fanout - for tensor_name in per_tensor_fanouts.keys(): - per_tensor_fanouts[tensor_name] = model.get_tensor_fanout(tensor_name) - return per_tensor_fanouts - - -def all_tensors_f32(model): - """Checks whether all tensors have a float32 dtype, extra quantization - annotations notwithstanding. - - Returns {"all_tensors_f32": Bool}.""" - all_tensors = model.make_empty_exec_context().items() - non_f32_tensors = filter(lambda x: x[1].dtype != np.float32, all_tensors) - return {"all_tensors_f32": len(list(non_f32_tensors)) == 0} - - -def node_inputs_in_expected_order(model): - """Verifies that the node inputs are ordered in the way that FINN expects - them. When a node has a mixture of static (= constant, initialized) inputs - and dynamic inputs, the dynamic input should come first, followed by the - static one. Only verifiable for a small subset of op_types for now. - - Returns {"node_inputs_in_expected_order": Bool}.""" - op_types = ["MatMul", "Conv", "Add", "Mul"] - nodes = filter(lambda x: x.op_type in op_types, model.graph.node) - all_OK = True - for n in nodes: - all_OK = all_OK and len(list(n.input)) == 2 - # input 0 should be dynamic, no initializer - all_OK = all_OK and (model.get_initializer(n.input[0]) is None) - # input 1 should be static (unless eltwise add) - if n.op_type != "Add": - all_OK = all_OK and (model.get_initializer(n.input[1]) is not None) - return {"node_inputs_in_expected_order": all_OK} - - -def nodes_topologically_sorted(model): - """Verifies that graph.node is topologically sorted. This is required by the - ONNX specification. - - Returns {"nodes_topologically_sorted": Bool}.""" - - # get successors of every node and check that - # successor index > current node index - - all_OK = True - for n in model.graph.node: - successors = model.find_direct_successors(n) - if successors is not None: - for successor in successors: - # check the condition by checking the antithesis - index_n = model.get_node_index(n) - index_suc = model.get_node_index(successor) - if index_n > index_suc: - all_OK = False - - return {"nodes_topologically_sorted": all_OK} diff --git a/src/finn/core/__init__.py b/src/finn/core/__init__.py deleted file mode 100644 index 83c8e8bed70797f7d6c0138968f750f72e790386..0000000000000000000000000000000000000000 --- a/src/finn/core/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/finn/core/data_layout.py b/src/finn/core/data_layout.py deleted file mode 100644 index 3971d221527d3862346c06cf415831c27e5cba8b..0000000000000000000000000000000000000000 --- a/src/finn/core/data_layout.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# predefined lists of strings to have a cannonical way of expresing data layout -# annotations - -NHWC = ["N", "H", "W", "C"] -NCHW = ["N", "C", "H", "W"] -NC = ["N", "C"] -UNKNOWN = [] diff --git a/src/finn/core/datatype.py b/src/finn/core/datatype.py deleted file mode 100644 index df895a1ad446d6b2cc3ebb24f1179944f4cfe9ab..0000000000000000000000000000000000000000 --- a/src/finn/core/datatype.py +++ /dev/null @@ -1,229 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -from enum import Enum, auto - -import numpy as np - - -class DataType(Enum): - """Enum class that contains FINN data types to set the quantization annotation. - ONNX does not support data types smaller than 8-bit integers, whereas in FINN we are - interested in smaller integers down to ternary and bipolar. - - Assignment of DataTypes to indices based on following ordering: - - * unsigned to signed - - * fewer to more bits - - Currently supported DataTypes: """ - - # important: the get_smallest_possible() member function is dependent on ordering. - BINARY = auto() - UINT2 = auto() - UINT3 = auto() - UINT4 = auto() - UINT5 = auto() - UINT6 = auto() - UINT7 = auto() - UINT8 = auto() - UINT9 = auto() - UINT10 = auto() - UINT11 = auto() - UINT12 = auto() - UINT13 = auto() - UINT14 = auto() - UINT15 = auto() - UINT16 = auto() - UINT17 = auto() - UINT18 = auto() - UINT19 = auto() - UINT20 = auto() - UINT21 = auto() - UINT22 = auto() - UINT23 = auto() - UINT24 = auto() - UINT25 = auto() - UINT26 = auto() - UINT27 = auto() - UINT28 = auto() - UINT29 = auto() - UINT30 = auto() - UINT31 = auto() - UINT32 = auto() - UINT64 = auto() - BIPOLAR = auto() - TERNARY = auto() - INT2 = auto() - INT3 = auto() - INT4 = auto() - INT5 = auto() - INT6 = auto() - INT7 = auto() - INT8 = auto() - INT9 = auto() - INT10 = auto() - INT11 = auto() - INT12 = auto() - INT13 = auto() - INT14 = auto() - INT15 = auto() - INT16 = auto() - INT17 = auto() - INT18 = auto() - INT19 = auto() - INT20 = auto() - INT21 = auto() - INT22 = auto() - INT23 = auto() - INT24 = auto() - INT25 = auto() - INT26 = auto() - INT27 = auto() - INT28 = auto() - INT29 = auto() - INT30 = auto() - INT31 = auto() - INT32 = auto() - INT64 = auto() - FLOAT32 = auto() - - 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 - elif self.name == "TERNARY": - return 2 - 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 - elif self.name == "TERNARY": - 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 - elif self.name == "TERNARY": - 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] - elif self.name == "TERNARY": - return value in [-1, 0, +1] - else: - raise Exception("Unrecognized data type: %s" % self.name) - - def get_num_possible_values(self): - """Returns the number of possible values this DataType can take. Only - implemented for integer types for now.""" - assert self.is_integer(), """This function only works for integers for now, - not for the DataType you used this function with.""" - if "INT" in self.name: - return abs(self.min()) + abs(self.max()) + 1 - elif self.name == "BINARY" or self.name == "BIPOLAR": - return 2 - elif self.name == "TERNARY": - return 3 - - def get_smallest_possible(value): - """Returns smallest (fewest bits) possible DataType that can represent - value. Prefers unsigned integers where possible.""" - if not int(value) == value: - return DataType["FLOAT32"] - for k in DataType.__members__: - dt = DataType[k] - if (dt.min() <= value) and (value <= dt.max()): - return dt - - def signed(self): - """Returns whether this DataType can represent negative numbers.""" - return self.min() < 0 - - def is_integer(self): - """Returns whether this DataType represents integer values only.""" - # only FLOAT32 is noninteger for now - return self != DataType.FLOAT32 - - def get_hls_datatype_str(self): - """Returns the corresponding Vivado HLS datatype name.""" - if self.is_integer(): - if self.signed(): - return "ap_int<%d>" % self.bitwidth() - else: - return "ap_uint<%d>" % self.bitwidth() - else: - return "float" diff --git a/src/finn/core/execute_custom_node.py b/src/finn/core/execute_custom_node.py deleted file mode 100644 index 86f7114a700c31e93d1d980693410c0e16dd128a..0000000000000000000000000000000000000000 --- a/src/finn/core/execute_custom_node.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.custom_op.registry as registry - - -def execute_custom_node(node, context, graph): - """Call custom implementation to execute a single custom node. - Input/output provided via context.""" - op_type = node.op_type - try: - # lookup op_type in registry of CustomOps - inst = registry.custom_op[op_type](node) - inst.execute_node(context, graph) - except KeyError: - # exception if op_type is not supported - raise Exception("Custom op_type %s is currently not supported." % op_type) diff --git a/src/finn/core/modelwrapper.py b/src/finn/core/modelwrapper.py deleted file mode 100644 index 11fda3da5a65c89f3e5d5b7bcb1da4cd4be056b9..0000000000000000000000000000000000000000 --- a/src/finn/core/modelwrapper.py +++ /dev/null @@ -1,585 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 copy -import os -import onnx -import onnx.helper as oh -import onnx.numpy_helper as np_helper -from onnx import TensorProto - -import finn.util.basic as util -import finn.util.onnx as onnxutil -from finn.core.datatype import DataType -from finn.transformation.general import ( - RemoveUnusedTensors, - RemoveStaticGraphInputs, - SortGraph, -) -from finn.transformation.double_to_single_float import DoubleToSingleFloat - - -class ModelWrapper: - """A wrapper around ONNX ModelProto that exposes some useful utility - functions for graph manipulation and exploration.""" - - def __init__(self, onnx_model_proto, make_deepcopy=False): - """Creates a ModelWrapper instance. - onnx_model_proto can be either a ModelProto instance, or a string - with the path to a stored .onnx file on disk, or serialized bytes. - - - make_deepcopy : controls whether a deep copy of the ModelProto - is made internally. - """ - if isinstance(onnx_model_proto, str): - assert os.path.isfile(onnx_model_proto) - self._model_proto = onnx.load(onnx_model_proto) - elif isinstance(onnx_model_proto, bytes): - self._model_proto = onnx.load_from_string(onnx_model_proto) - else: - if make_deepcopy: - self._model_proto = copy.deepcopy(onnx_model_proto) - else: - self._model_proto = onnx_model_proto - - @property - def graph(self): - """Returns the graph of the model.""" - return self._model_proto.graph - - @graph.setter - def graph(self, value): - """Sets the graph of the model according to value""" - self._model_proto.graph = value - - @property - def model(self): - """Returns the model.""" - return self._model_proto - - @model.setter - def model(self, value): - """Sets the model according to value.""" - self._model_proto = value - - def save(self, filename): - """Saves the wrapper ONNX ModelProto into a file with given name.""" - onnx.save(self._model_proto, filename) - - def analysis(self, analysis_fxn): - """Runs given anaylsis_fxn on this model and return resulting dict.""" - return analysis_fxn(self) - - def transform( - self, transformation, make_deepcopy=True, cleanup=True, fix_float64=True - ): - """Applies given Transformation repeatedly until no more changes can be made - and returns a transformed ModelWrapper instance. - - - make_deepcopy : operates on a new (deep)copy of model. - - fix_float64 : DoubleToSingleFloat correction before starting - - cleanup : execute cleanup transformations before returning - """ - transformed_model = self - if make_deepcopy: - transformed_model = copy.deepcopy(self) - if fix_float64: - (transformed_model, model_was_changed) = DoubleToSingleFloat().apply( - transformed_model - ) - model_was_changed = True - while model_was_changed: - (transformed_model, model_was_changed) = transformation.apply( - transformed_model - ) - if cleanup: - transformed_model.cleanup() - return transformed_model - - def cleanup(self): - "Run cleanup transformations on the model." - transformed_model = self - cleanup_transforms = [ - RemoveUnusedTensors(), - RemoveStaticGraphInputs(), - SortGraph(), - ] - for trn in cleanup_transforms: - transformed_model = transformed_model.transform( - trn, cleanup=False, make_deepcopy=False - ) - return transformed_model - - def check_compatibility(self): - """Checks this model for FINN compatibility: - - * no embedded subgraphs - - * all tensor shapes are specified, including activations - - * all constants are initializers - """ - # TODO check for no embedded subgraphs - # TODO check that all shapes are inferred - # TODO check that all constants are initializers - return True - - def get_tensor_datatype(self, tensor_name): - """Returns the FINN DataType of tensor with given name.""" - graph = self._model_proto.graph - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, tensor_name, "tensor_name") - if ret is not None: - ret = util.get_by_name( - ret.quant_parameter_tensor_names, "finn_datatype", "key" - ) - if ret is not None: - return DataType[ret.value] - # TODO maybe use native ONNX tensor type instead of assuming fp32? - return DataType["FLOAT32"] - - def set_tensor_datatype(self, tensor_name, datatype): - """Sets the FINN DataType of tensor with given name.""" - graph = self._model_proto.graph - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, tensor_name, "tensor_name") - if ret is not None: - ret_dt = util.get_by_name( - ret.quant_parameter_tensor_names, "finn_datatype", "key" - ) - if ret_dt is not None: - ret_dt.value = datatype.name - else: - dt = onnx.StringStringEntryProto() - dt.key = "finn_datatype" - dt.value = datatype.name - ret.quant_parameter_tensor_names.append(dt) - else: - qa = onnx.TensorAnnotation() - dt = onnx.StringStringEntryProto() - dt.key = "finn_datatype" - dt.value = datatype.name - qa.tensor_name = tensor_name - qa.quant_parameter_tensor_names.append(dt) - qnt_annotations.append(qa) - - def get_tensor_valueinfo(self, tensor_name): - """Returns ValueInfoProto of tensor with given name, if it has one.""" - graph = self._model_proto.graph - vi_names = [(x.name, x) for x in graph.input] - vi_names += [(x.name, x) for x in graph.output] - vi_names += [(x.name, x) for x in graph.value_info] - try: - vi_ind = [x[0] for x in vi_names].index(tensor_name) - vi = vi_names[vi_ind][1] - return vi - except ValueError: - return None - - def get_tensor_shape(self, tensor_name): - """Returns the shape of tensor with given name, if it has ValueInfoProto.""" - graph = self._model_proto.graph - vi_names = [(x.name, x) for x in graph.input] - vi_names += [(x.name, x) for x in graph.output] - vi_names += [(x.name, x) for x in graph.value_info] - try: - vi_ind = [x[0] for x in vi_names].index(tensor_name) - vi = vi_names[vi_ind][1] - dims = [x.dim_value for x in vi.type.tensor_type.shape.dim] - return dims - except ValueError: - return None - - def set_tensor_shape(self, tensor_name, tensor_shape, dtype=TensorProto.FLOAT): - """Assigns shape in ValueInfoProto for tensor with given name.""" - new_vi = oh.make_tensor_value_info(tensor_name, dtype, tensor_shape) - # find what container tis tensor's ValueInfo lives in - # if not found anywhere, we assume it's a new value_info - target_container = self.graph.value_info - if util.get_by_name(self.graph.input, tensor_name) is not None: - target_container = self.graph.input - if util.get_by_name(self.graph.output, tensor_name) is not None: - target_container = self.graph.output - # remove from target container and add new - util.remove_by_name(target_container, tensor_name) - target_container.append(new_vi) - - def set_initializer(self, tensor_name, tensor_value): - """Sets the initializer value for tensor with given name.""" - graph = self._model_proto.graph - # convert tensor_value (numpy array) into TensorProto w/ correct name - tensor_init_proto = np_helper.from_array(tensor_value) - tensor_init_proto.name = tensor_name - # first, remove if an initializer already exists - init_names = [x.name for x in graph.initializer] - try: - init_ind = init_names.index(tensor_name) - init_old = graph.initializer[init_ind] - graph.initializer.remove(init_old) - except ValueError: - pass - # create and insert new initializer - graph.initializer.append(tensor_init_proto) - # set shape - dtype = tensor_init_proto.data_type - self.set_tensor_shape(tensor_name, list(tensor_value.shape), dtype) - - def rename_tensor(self, old_name, new_name): - """Renames a tensor from old_name to new_name.""" - graph = self.graph - # sweep over inputs - if util.get_by_name(graph.input, old_name) is not None: - util.get_by_name(graph.input, old_name).name = new_name - # sweep over outputs - if util.get_by_name(graph.output, old_name) is not None: - util.get_by_name(graph.output, old_name).name = new_name - # sweep over value_info - if util.get_by_name(graph.value_info, old_name) is not None: - util.get_by_name(graph.value_info, old_name).name = new_name - # sweep over initializers - if util.get_by_name(graph.initializer, old_name) is not None: - util.get_by_name(graph.initializer, old_name).name = new_name - # sweep over quantization annotations - if ( - util.get_by_name(graph.quantization_annotation, old_name, "tensor_name") - is not None - ): - util.get_by_name( - graph.quantization_annotation, old_name, "tensor_name" - ).tensor_name = new_name - # sweep over node i/o - for n in graph.node: - if old_name in n.input: - n.input[list(n.input).index(old_name)] = new_name - if old_name in n.output: - n.output[list(n.output).index(old_name)] = new_name - - def get_initializer(self, tensor_name): - """Gets the initializer value for tensor with given name, if any.""" - graph = self._model_proto.graph - init_names = [x.name for x in graph.initializer] - try: - init_ind = init_names.index(tensor_name) - return np_helper.to_array(graph.initializer[init_ind]) - except ValueError: - return None - - def find_producer(self, tensor_name): - """Finds and returns the node that produces the tensor with given name.""" - for x in self._model_proto.graph.node: - if tensor_name in x.output: - return x - return None - - def find_upstream(self, tensor_name, finder_fxn): - """Follow the producer chain upstream, calling finder_fxn on each upstream - node until it returns True or there are no nodes left. Returns the list - of nodes visited, or None if finder_fxn did not return True.""" - visit_list = [] - current_tensor = tensor_name - while True: - current_producer = self.find_producer(current_tensor) - if current_producer is None: - return [] - else: - found = finder_fxn(current_producer) - visit_list.append(current_producer) - if found: - return visit_list - else: - current_tensor = current_producer.input[0] - - def find_consumer(self, tensor_name): - """Finds and returns the node that consumes the tensor with given name. - Currently only works for linear graphs.""" - all_inputs = [x.input[0] for x in self._model_proto.graph.node] - try: - consumer_ind = all_inputs.index(tensor_name) - return self._model_proto.graph.node[consumer_ind] - except ValueError: - return None - - def find_consumers(self, tensor_name): - """Finds and returns a list of the nodes that consume tensor with - given name.""" - consumers = [] - for n in self._model_proto.graph.node: - for inp_tensor in n.input: - if inp_tensor == tensor_name: - consumers.append(n) - if consumers != []: - return consumers - else: - return None - - def find_direct_successors(self, node): - """Finds and returns a list of the nodes that are successors of - given node.""" - successors = [] - for outp_tensor in node.output: - tensor_consumer_list = self.find_consumers(outp_tensor) - if tensor_consumer_list is not None: - for consumer in tensor_consumer_list: - successors.append(consumer) - if successors != []: - return successors - else: - return None - - def find_direct_predecessors(self, node): - """Finds and returns a list of the nodes that are predecessors of - given node.""" - predecessors = [] - for inp_tensor in node.input: - producer = self.find_producer(inp_tensor) - if producer is not None: - predecessors.append(producer) - if predecessors != []: - return predecessors - else: - return None - - def is_fork_node(self, node): - """Checks if the given node is a fork, that is, the node has multiple - direct successors""" - direct_successors = self.find_direct_successors(node) - is_fork = False if direct_successors is None else (len(direct_successors) > 1) - return is_fork - - def is_join_node(self, node): - """Checks if the given node is a join, that is, the node has multiple - direct predecessors""" - direct_predecessors = self.find_direct_predecessors(node) - is_join = ( - False if direct_predecessors is None else (len(direct_predecessors) > 1) - ) - return is_join - - def get_all_tensor_names(self): - """Returns a list of all (input, output and value_info) tensor names - in the graph.""" - graph = self.graph - names = [x.name for x in graph.value_info] - names += [x.name for x in graph.input] - names += [x.name for x in graph.output] - return names - - def make_new_valueinfo_name(self): - """Returns a name that can be used for a new value_info.""" - names = self.get_all_tensor_names() - candidate = util.random_string() - while candidate in names: - candidate = util.random_string() - return candidate - - def make_empty_exec_context(self): - """Creates an empty execution context for this model. - - The execution context is a dictionary of all tensors used for the - inference computation. Any initializer values will be taken into - account, all other tensors will be zero.""" - execution_context = dict() - graph = self._model_proto.graph - # make empty tensors for all the graph inputs and outputs - for vi in graph.input: - new_tensor = onnxutil.valueinfo_to_tensor(vi) - execution_context[vi.name] = new_tensor - for vi in graph.output: - new_tensor = onnxutil.valueinfo_to_tensor(vi) - execution_context[vi.name] = new_tensor - # make empty tensors for all intermediate buffers - for vi in graph.value_info: - new_tensor = onnxutil.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) - return execution_context - - def check_all_tensor_shapes_specified(self): - """Checks whether all tensors have a specified shape (ValueInfo). - The ONNX standard allows for intermediate activations to have no - associated ValueInfo, but FINN expects this.""" - graph = self._model_proto.graph - ret = True - for n in graph.node: - for i in n.input: - ret = ret and (self.get_tensor_shape(i) is not None) - for o in n.output: - ret = ret and (self.get_tensor_shape(o) is not None) - return ret - - def get_tensor_fanout(self, tensor_name): - """Returns the number of nodes for which the tensor with given name is - as input.""" - graph = self.graph - fanout = 0 - for n in graph.node: - if tensor_name in n.input: - fanout += 1 - return fanout - - def get_metadata_prop(self, key): - """Returns the value associated with metadata_prop with given key, - or None otherwise.""" - metadata_prop = util.get_by_name(self.model.metadata_props, key, "key") - if metadata_prop is None: - return None - else: - return metadata_prop.value - - def set_metadata_prop(self, key, value): - """Sets metadata property with given key to the given value.""" - metadata_prop = util.get_by_name(self.model.metadata_props, key, "key") - if metadata_prop is None: - metadata_prop = onnx.StringStringEntryProto() - metadata_prop.key = key - metadata_prop.value = value - self.model.metadata_props.append(metadata_prop) - else: - metadata_prop.value = value - - def get_nodes_by_name(self, op_name): - """Returns a list of nodes with specified name.""" - return list(filter(lambda x: x.name == op_name, self.graph.node)) - - def get_nodes_by_op_type(self, op_type): - """Returns a list of nodes with specified op_type.""" - return list(filter(lambda x: x.op_type == op_type, self.graph.node)) - - def get_finn_nodes(self): - """Returns a list of nodes where domain == 'finn'.""" - return list(filter(lambda x: x.domain == "finn", self.graph.node)) - - def get_non_finn_nodes(self): - """Returns a list of nodes where domain != 'finn'.""" - return list(filter(lambda x: x.domain != "finn", self.graph.node)) - - def get_node_index(self, node): - """Returns current index of given node.""" - n_ind = 0 - try: - for n in self.graph.node: - if n == node: - return n_ind - n_ind += 1 - except ValueError: - return None - - def get_tensor_layout(self, tensor_name): - """Returns the data layout annotation of tensor with given name. - The data layout is expressed as a list of strings with as many - elements as the number of dimensions in the tensor shape. Each - string annotates what is contained in that dimension. If there is no - data layout annotation, None will be returned. - Examples of data layout annotations: - ["N", "C"] is tensor[batch][channel] - ["N", "C", "H", "W"] is tensor[batch][channel][height][width] - ["N", "H", "W", "C"] is tensor[batch][height][width][channel] - """ - graph = self._model_proto.graph - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, tensor_name, "tensor_name") - if ret is not None: - ret = util.get_by_name( - ret.quant_parameter_tensor_names, "tensor_layout", "key" - ) - if ret is not None: - return eval(ret.value) - return None - - def set_tensor_layout(self, tensor_name, data_layout): - """Sets the data layout annotation of tensor with given name. See - get_tensor_layout for examples.""" - tensor_shape = self.get_tensor_shape(tensor_name) - assert type(data_layout) == list, "data_layout must be a list" - if tensor_shape is not None: - assert len(tensor_shape) == len( - data_layout - ), """Mismatch between number - of dimensions of tensor shape and data layout annotation.""" - graph = self._model_proto.graph - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, tensor_name, "tensor_name") - if ret is not None: - ret_tl = util.get_by_name( - ret.quant_parameter_tensor_names, "tensor_layout", "key" - ) - if ret_tl is not None: - ret_tl.value = str(data_layout) - else: - tl = onnx.StringStringEntryProto() - tl.key = "tensor_layout" - tl.value = str(data_layout) - ret.quant_parameter_tensor_names.append(tl) - else: - qa = onnx.TensorAnnotation() - dt = onnx.StringStringEntryProto() - dt.key = "tensor_layout" - dt.value = str(data_layout) - qa.tensor_name = tensor_name - qa.quant_parameter_tensor_names.append(dt) - qnt_annotations.append(qa) - - def get_tensor_sparsity(self, tensor_name): - """Returns the sparsity of a given tensor as dictionary.""" - graph = self._model_proto.graph - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, tensor_name, "tensor_name") - if ret is not None: - ret = util.get_by_name( - ret.quant_parameter_tensor_names, "tensor_sparsity", "key" - ) - if ret is not None: - return eval(ret.value) - return None - - def set_tensor_sparsity(self, tensor_name, sparsity_dict): - """Sets the sparsity annotation of a tensor with given name.""" - graph = self._model_proto.graph - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, tensor_name, "tensor_name") - if ret is not None: - ret_ts = util.get_by_name( - ret.quant_parameter_tensor_names, "tensor_sparsity", "key" - ) - if ret_ts is not None: - ret_ts.value = str(sparsity_dict) - else: - ts = onnx.StringStringEntryProto() - ts.key = "tensor_sparsity" - ts.value = str(sparsity_dict) - ret.quant_parameter_tensor_names.append(ts) - else: - qa = onnx.TensorAnnotation() - dt = onnx.StringStringEntryProto() - dt.key = "tensor_sparsity" - dt.value = str(sparsity_dict) - qa.tensor_name = tensor_name - qa.quant_parameter_tensor_names.append(dt) - qnt_annotations.append(qa) diff --git a/src/finn/core/onnx_exec.py b/src/finn/core/onnx_exec.py deleted file mode 100644 index 85b52c0f33baac609b4dad4df59f8442f737ffc2..0000000000000000000000000000000000000000 --- a/src/finn/core/onnx_exec.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 copy - -import numpy as np -import onnx.helper as helper -import onnxruntime as rt - -import finn.core.execute_custom_node as ex_cu_node -from finn.core.modelwrapper import ModelWrapper -from finn.core.remote_exec import remote_exec -from finn.core.rtlsim_exec import rtlsim_exec -from finn.custom_op.registry import getCustomOp -import finn.analysis.topology as ta -from finn.util.basic import sanitize_quant_values, get_sanitize_quant_tensors - - -def execute_node(node, context, graph, return_full_exec_context=False): - """Executes a single node by using onnxruntime, with custom function or - if dataflow partition by using remote execution or rtlsim. - - Input/output provided via context.""" - - if node.op_type == "StreamingDataflowPartition": - sdp_node = getCustomOp(node) - model = ModelWrapper(sdp_node.get_nodeattr("model")) - inp_ctx = dict(filter(lambda x: x[0] in node.input, context.items())) - # input may have been renamed in partition - assert len(inp_ctx) == 1 - old_iname = node.input[0] - new_iname = model.graph.input[0].name - if old_iname != new_iname: - inp_ctx[new_iname] = inp_ctx[old_iname] - del inp_ctx[old_iname] - ret = execute_onnx(model, inp_ctx, return_full_exec_context) - # if the model was in ip-stitched rtlsim mode, may get annotation - # for numbet of elapsed cycles, save again - if model.get_metadata_prop("exec_mode") == "rtlsim": - model.save(sdp_node.get_nodeattr("model")) - # output may have been renamed in partition - assert len(model.graph.output) == 1 - node_oname = node.output[0] - model_oname = model.graph.output[0].name - context[node_oname] = ret[model_oname] - # prefix and insert exec context entries - if return_full_exec_context: - for tname in ret.keys(): - if tname != model_oname: - context[node.name + "_" + tname] = ret[tname] - else: - if node.domain == "finn": - - ex_cu_node.execute_custom_node(node, context, graph) - - else: - - # 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. - # note: ensure that the same ValueInfo does not appear both in - # graph.value_info as well as graph.output or graph.input - # nodes with multiple outputs that are a mix of value_info and - # input/outputs may get them reordered below - 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] - - sess = rt.InferenceSession(node_model.SerializeToString()) - output_list = sess.run(None, input_dict) - - for output_ind in range(len(node.output)): - # get the name of the target buffer from node.output - outp = node.output[output_ind] - - # retrieve the index of that name in node_outputs - for i in range(len(node_outputs)): - if outp == node_outputs[i].name: - list_ind = i - - # use that index to index output_list - if output_list[list_ind].shape != context[outp].shape: - raise Exception( - """Output shapes disagree after node execution: - found %s vs expected %s""" - % (str(output_list[list_ind].shape), str(context[outp].shape)) - ) - context[outp] = output_list[list_ind] - - -def execute_onnx( - model, input_dict, return_full_exec_context=False, start_node=None, end_node=None -): - """Executes given ONNX ModelWrapper with given named inputs. - - If return_full_exec_context is False, a dict of named outputs is returned - as indicated by the model.graph.output. - - If return return_full_exec_context is True, the full set of tensors used by - the execution (including inputs, weights, activations and final outputs) - will be returned as a dict. - - When start_node and end_node are set to None, the whole graph is executed. - If they are set to particular ONNX nodes, only the subgraph between (and - including) those nodes is executed. - """ - - if not model.check_all_tensor_shapes_specified(): - raise Exception("Found unspecified tensor shapes, try infer_shapes") - ret = model.analysis(ta.nodes_topologically_sorted) - assert ( - ret["nodes_topologically_sorted"] is True - ), """Nodes must be - topologically sorted.""" - - graph = model.graph - # 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 - # the input data as well as the trained parameters) and the graph ValueInfo - # (intermediate tensors between layers) - # this is provided by the execution_context, which is a dict of np.ndarray - execution_context = model.make_empty_exec_context() - # fill in any inputs provided to this function - for inp_name in input_dict.keys(): - if inp_name in execution_context: - if execution_context[inp_name].shape == input_dict[inp_name].shape: - execution_context[inp_name] = input_dict[inp_name] - else: - raise Exception( - "Shape mismatch for provided input %s: found %s expected %s " - % ( - inp_name, - str(execution_context[inp_name].shape), - str(input_dict[inp_name].shape), - ) - ) - # else: - # raise Exception("Provided input not found in graph context: %s" % inp_name) - - # check if model has an execution mode set - # if None, execute model node by node using execute_node() - # if set to "remote_pynq" execute model on PYNQ board - # if set to "rtlsim" execute model using pyverilator - model_exec_mode = model.get_metadata_prop("exec_mode") - if (model_exec_mode is None) or (model_exec_mode == ""): - # execute the model node by node - # we can simply walk down the list since the ONNX spec guarantees that it is - # topologically sorted - subgraph = [] - if start_node is None: - start_node = model.graph.node[0] - if end_node is None: - end_node = model.graph.node[-1] - # select the nodes between specified start/end nodes - start_ind = model.get_node_index(start_node) - end_ind = model.get_node_index(end_node) + 1 - assert end_ind >= start_ind, "Start/end nodes must define valid subgraph" - subgraph = graph.node[start_ind:end_ind] - for node in subgraph: - if get_sanitize_quant_tensors() != 0: - # round input values to match quantization annotation - execution_context = sanitize_quant_values( - model, node.input, execution_context - ) - execute_node(node, execution_context, graph, return_full_exec_context) - if get_sanitize_quant_tensors() != 0: - # round output values to quantization annotation - execution_context = sanitize_quant_values( - model, node.output, execution_context - ) - elif model_exec_mode == "remote_pynq": - # use remote exec metadata built into model to execute on a remote PYNQ - remote_exec(model, execution_context) - elif model_exec_mode == "rtlsim": - # use stitched IP for rtlsim - rtlsim_exec(model, execution_context) - else: - raise Exception( - """Metadata property "exec_mode" is set to an unknown value. - Can be left unset or has to be set to "remote_pynq" for remote execution - on PYNQ board or "rtlsim" for execution using pyverilator!""" - ) - - if return_full_exec_context: - return execution_context - else: - # provide outputs as dict - output_dict = dict() - for out_tensor in graph.output: - out_name = out_tensor.name - output_dict[out_name] = execution_context[out_name] - return output_dict - - -def execute_onnx_and_make_model(model, input_dict): - """Executes given ONNX ModelWrapper with given named inputs and return a new - ModelWrapper where an initializer is provided for each tensor as taken from - the execution. This new model is useful for debugging, since it contains - all the intermediate activation values.""" - - # retrieve the full execution context - execution_context = execute_onnx(model, input_dict, True) - new_model = copy.deepcopy(model) - # create value_info entries and initializers for everything - for i in execution_context.keys(): - new_model.set_initializer(i, execution_context[i]) - for vi in new_model.graph.value_info: - new_model.graph.output.append(vi) - # import pdb; pdb.set_trace() - return new_model - - -def compare_execution( - model_a, - model_b, - input_dict, - compare_fxn=lambda x, y: np.isclose(x, y, atol=1e-3).all(), -): - """Executes two ONNX models and compare their outputs using given function. - - compare_fxn should take in two tensors and return a Boolean""" - # compare values from first output tensors produced - res_a = list(execute_onnx(model_a, input_dict).items())[0][1] - res_b = list(execute_onnx(model_b, input_dict).items())[0][1] - return compare_fxn(res_a, res_b) diff --git a/src/finn/core/remote_exec.py b/src/finn/core/remote_exec.py deleted file mode 100644 index 2e139065ec0eff8cdbdb402f80113b039deed4da..0000000000000000000000000000000000000000 --- a/src/finn/core/remote_exec.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -import subprocess -import warnings -import numpy as np - - -def remote_exec(model, execution_context): - """Executes the given model remotely on the pynq board. The metadata properties - related to the pynq board have to be set. The execution context contains the - input values.""" - # TODO fix for multi input-output - pynq_ip = model.get_metadata_prop("pynq_ip") - pynq_port = int(model.get_metadata_prop("pynq_port")) - pynq_username = model.get_metadata_prop("pynq_username") - pynq_password = model.get_metadata_prop("pynq_password") - pynq_target_dir = model.get_metadata_prop("pynq_target_dir") - deployment_dir = model.get_metadata_prop("pynq_deploy_dir") - platform = model.get_metadata_prop("platform") - assert platform in ["alveo", "zynq-iodma"] - bitfile = model.get_metadata_prop("bitfile") - bitfile = os.path.basename(bitfile) - if pynq_password == "": - if "zynq" in platform: - raise Exception("PYNQ board remote exec needs password for sudo") - else: - local_prefix = "" # assume we are using an ssh key - warnings.warn("Empty password, make sure you've set up an ssh key") - else: - local_prefix = "sshpass -p %s " % pynq_password - - if platform == "alveo": - # Alveo can run without sudo - remote_prefix = "" - elif "zynq" in platform: - # PYNQ Zynq boards need to execute with sudo - remote_prefix = "echo %s | sudo -S " % pynq_password - - inp = execution_context[model.graph.input[0].name] - # make copy of array before saving it - inp = inp.copy() - batchsize = inp.shape[0] - np.save(os.path.join(deployment_dir, "input.npy"), inp) - # extracting last folder of absolute path (deployment_dir) - deployment_folder = os.path.basename(os.path.normpath(deployment_dir)) - # copy input to PYNQ board - cmd = local_prefix + "scp -P{} -r {}/input.npy {}@{}:{}/{}".format( - pynq_port, - deployment_dir, - pynq_username, - pynq_ip, - pynq_target_dir, - deployment_folder, - ) - bash_command = ["/bin/bash", "-c", cmd] - process_scp_in = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_scp_in.communicate() - - # use platform attribute for correct remote execution - if platform == "alveo": - remote_cmd = "bash -ic 'bash alveo_run.sh execute %d' \"" % batchsize - else: - remote_cmd = ( - "python3.6 driver.py --exec_mode=execute --batchsize={} " - "--bitfile={} --inputfile=input.npy --outputfile=output.npy " - '--platform={} "' - ).format(batchsize, bitfile, platform) - cmd = ( - local_prefix + 'ssh {}@{} -p {} "cd {}/{}; ' + remote_prefix + remote_cmd - ).format(pynq_username, pynq_ip, pynq_port, pynq_target_dir, deployment_folder) - bash_command = ["/bin/bash", "-c", cmd] - process_exec_accel = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_exec_accel.communicate() - # remove stale output file from local dir, if any - try: - os.remove("{}/output.npy".format(deployment_dir)) - except FileNotFoundError: - pass - # copy generated output to local - cmd = local_prefix + "scp -P{} {}@{}:{}/{}/output.npy {}".format( - pynq_port, - pynq_username, - pynq_ip, - pynq_target_dir, - deployment_folder, - deployment_dir, - ) - bash_command = ["/bin/bash", "-c", cmd] - process_scp_out = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_scp_out.communicate() - outp = np.load("{}/output.npy".format(deployment_dir)) - execution_context[model.graph.output[0].name] = outp diff --git a/src/finn/core/rtlsim_exec.py b/src/finn/core/rtlsim_exec.py deleted file mode 100644 index d83bcd3a75dd0d2fc02315c72784e57348901a04..0000000000000000000000000000000000000000 --- a/src/finn/core/rtlsim_exec.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os - -from finn.custom_op.registry import getCustomOp -from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy -from finn.util.fpgadataflow import ( - pyverilate_get_liveness_threshold_cycles, - pyverilate_stitched_ip, -) - -try: - from pyverilator import PyVerilator -except ModuleNotFoundError: - PyVerilator = None - - -def rtlsim_exec(model, execution_context): - """Use PyVerilator to execute given model with stitched IP. The execution - context contains the input values.""" - - if PyVerilator is None: - raise ImportError("Installation of PyVerilator is required.") - # ensure stitched ip project already exists - assert os.path.isfile( - model.get_metadata_prop("wrapper_filename") - ), """The - file name from metadata property "wrapper_filename" doesn't exist.""" - assert os.path.isdir( - model.get_metadata_prop("vivado_stitch_proj") - ), """The - directory from metadata property "vivado_stitch_proj" doesn't exist""" - trace_file = model.get_metadata_prop("rtlsim_trace") - # extract input shape - # TODO extend for multiple inputs - i_name = model.graph.input[0].name - i_tensor = execution_context[i_name] - i_dt = model.get_tensor_datatype(i_name) - first_node = getCustomOp(model.find_consumer(i_name)) - i_stream_w = first_node.get_instream_width() - # convert input into time multiplexed shape - i_folded_shape = first_node.get_folded_input_shape() - batchsize = i_tensor.shape[0] - # override batch size for input - i_folded_shape = list(i_folded_shape) - i_folded_shape[0] = batchsize - i_folded_shape = tuple(i_folded_shape) - # TODO any other layout transformations need to happen here! - i_tensor = i_tensor.reshape(i_folded_shape) - # extract output shape - o_name = model.graph.output[0].name - o_shape = model.get_tensor_shape(o_name) - o_dt = model.get_tensor_datatype(o_name) - last_node = getCustomOp(model.find_producer(o_name)) - o_folded_shape = last_node.get_folded_output_shape() - # override batch size from actual input - o_shape = list(o_shape) - o_shape[0] = batchsize - o_shape = tuple(o_shape) - o_folded_shape = list(o_folded_shape) - o_folded_shape[0] = batchsize - o_folded_shape = tuple(o_folded_shape) - o_stream_w = last_node.get_outstream_width() - packedBits = o_stream_w - targetBits = o_dt.bitwidth() - # pack input - packed_input = npy_to_rtlsim_input(i_tensor, i_dt, i_stream_w) - num_out_values = last_node.get_number_output_values() - num_out_values *= batchsize - # prepare pyverilator model - rtlsim_so = model.get_metadata_prop("rtlsim_so") - if (rtlsim_so is None) or (not os.path.isfile(rtlsim_so)): - sim = pyverilate_stitched_ip(model) - model.set_metadata_prop("rtlsim_so", sim.lib._name) - else: - sim = PyVerilator(rtlsim_so, auto_eval=False) - ret = _run_rtlsim(sim, packed_input, num_out_values, trace_file) - packed_output = ret[0] - model.set_metadata_prop("cycles_rtlsim", str(ret[1])) - # unpack output and put into context - o_folded_tensor = rtlsim_output_to_npy( - packed_output, None, o_dt, o_folded_shape, packedBits, targetBits - ) - execution_context[o_name] = o_folded_tensor.reshape(o_shape) - - -# TODO move the rtlsim functions below into a common location such as utils -def _reset_rtlsim(sim): - """Sets reset input in pyverilator to zero, toggles the clock and set it - back to one""" - sim.io.ap_rst_n = 0 - _toggle_clk(sim) - _toggle_clk(sim) - sim.io.ap_rst_n = 1 - _toggle_clk(sim) - _toggle_clk(sim) - - -def _toggle_clk(sim): - """Toggles the clock input in pyverilator once.""" - sim.io.ap_clk = 0 - sim.eval() - sim.io.ap_clk = 1 - sim.eval() - - -def _run_rtlsim(sim, inp, num_out_values, trace_file=None, reset=True): - """Runs the pyverilator simulation by passing the input values to the simulation, - toggle the clock and observing the execution time. Argument num_out_values contains - the number of expected output values, so the simulation is closed after all - outputs are calculated. Function contains also an observation loop that can - abort the simulation if no output value is produced after a certain time - (liveness_threshold from function pyverilate_get_liveness_threshold_cycles() - from finn.util.fpgadataflow)""" - inputs = inp - outputs = [] - sim.io.m_axis_0_tready = 1 - - # observe if output is completely calculated - # observation_count will contain the number of cycles the calculation ran - output_observed = False - observation_count = 0 - - # avoid infinite looping of simulation by aborting when there is no change in - # output values after LIVENESS_THRESHOLD cycles - no_change_count = 0 - old_outputs = outputs - liveness_threshold = pyverilate_get_liveness_threshold_cycles() - - if trace_file is not None: - sim.start_vcd_trace(trace_file) - if reset: - _reset_rtlsim(sim) - - while not (output_observed): - sim.io.s_axis_0_tvalid = 1 if len(inputs) > 0 else 0 - sim.io.s_axis_0_tdata = inputs[0] if len(inputs) > 0 else 0 - if sim.io.s_axis_0_tready == 1 and sim.io.s_axis_0_tvalid == 1: - inputs = inputs[1:] - if sim.io.m_axis_0_tvalid == 1 and sim.io.m_axis_0_tready == 1: - outputs = outputs + [sim.io.m_axis_0_tdata] - _toggle_clk(sim) - - observation_count = observation_count + 1 - no_change_count = no_change_count + 1 - - if len(outputs) == num_out_values: - cycles_rtlsim = observation_count - output_observed = True - - if no_change_count == liveness_threshold: - if old_outputs == outputs: - if trace_file is not None: - sim.flush_vcd_trace() - sim.stop_vcd_trace() - raise Exception( - "Error in simulation! Takes too long to produce output." - "Consider setting the LIVENESS_THRESHOLD env.var. to a " - "larger value." - ) - else: - no_change_count = 0 - old_outputs = outputs - if trace_file is not None: - sim.flush_vcd_trace() - sim.stop_vcd_trace() - - return (outputs, cycles_rtlsim) diff --git a/src/finn/core/throughput_test.py b/src/finn/core/throughput_test.py deleted file mode 100644 index 1306edfa23a9b25de41d0592796b4a03ad4e6508..0000000000000000000000000000000000000000 --- a/src/finn/core/throughput_test.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -import subprocess -import numpy as np -import warnings -from finn.util.basic import gen_finn_dt_tensor -from finn.core.rtlsim_exec import rtlsim_exec - - -def throughput_test_remote(model, batchsize=1000): - """Runs the throughput test for the given model remotely on the pynq board. - The metadata properties related to the pynq board have to be set. - Returns a dictionary with results of the throughput test. Returns None - if the test fails.""" - - pynq_ip = model.get_metadata_prop("pynq_ip") - pynq_port = int(model.get_metadata_prop("pynq_port")) - pynq_username = model.get_metadata_prop("pynq_username") - pynq_password = model.get_metadata_prop("pynq_password") - pynq_target_dir = model.get_metadata_prop("pynq_target_dir") - deployment_dir = model.get_metadata_prop("pynq_deploy_dir") - # extracting last folder of absolute path (deployment_dir) - deployment_folder = os.path.basename(os.path.normpath(deployment_dir)) - platform = model.get_metadata_prop("platform") - assert platform in ["alveo", "zynq-iodma"] - bitfile = model.get_metadata_prop("bitfile") - bitfile = os.path.basename(bitfile) - if pynq_password == "": - if "zynq" in platform: - raise Exception("PYNQ board remote exec needs password for sudo") - else: - local_prefix = "" # assume we are using an ssh key - warnings.warn("Empty password, make sure you've set up an ssh key") - else: - local_prefix = "sshpass -p %s " % pynq_password - - if platform == "alveo": - # Alveo can run without sudo but needs correct environment - remote_prefix = "conda activate finn-pynq-alveo; " - elif "zynq" in platform: - # PYNQ Zynq boards need to execute with sudo - remote_prefix = "echo %s | sudo -S " % pynq_password - - # use platform attribute for correct remote execution - if platform == "alveo": - remote_cmd = "bash -ic 'bash alveo_run.sh throughput_test %d' \"" % batchsize - else: - remote_cmd = ( - "python3.6 driver.py --exec_mode=throughput_test --batchsize={} " - "--bitfile={} --inputfile=input.npy --outputfile=output.npy " - '--platform={} "' - ).format(batchsize, bitfile, platform) - cmd = ( - local_prefix + 'ssh {}@{} -p {} "cd {}/{}; ' + remote_prefix + remote_cmd - ).format(pynq_username, pynq_ip, pynq_port, pynq_target_dir, deployment_folder) - bash_command = ["/bin/bash", "-c", cmd] - process_throughput_test = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_throughput_test.communicate() - - # remove any pre-existing metrics file - try: - os.remove("{}/nw_metrics.txt".format(deployment_dir)) - except FileNotFoundError: - pass - - cmd = local_prefix + "scp -P{} {}@{}:{}/{}/nw_metrics.txt {}".format( - pynq_port, - pynq_username, - pynq_ip, - pynq_target_dir, - deployment_folder, - deployment_dir, - ) - bash_command = ["/bin/bash", "-c", cmd] - process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_compile.communicate() - - try: - with open("{}/nw_metrics.txt".format(deployment_dir), "r") as file: - res = eval(file.read()) - return res - except FileNotFoundError: - return None - - -def throughput_test_rtlsim(model, batchsize=100): - """Runs a throughput test for the given IP-stitched model. When combined - with tracing, useful to determine bottlenecks and required FIFO sizes.""" - - assert ( - model.get_metadata_prop("exec_mode") == "rtlsim" - ), """Top-level exec_mode - metadata_prop must be set to rtlsim""" - - # create random input - iname = model.graph.input[0].name - ishape = model.get_tensor_shape(iname) - ishape_batch = ishape - ishape_batch[0] = batchsize - idt = model.get_tensor_datatype(iname) - dummy_input = gen_finn_dt_tensor(idt, ishape_batch) - # compute input/output sizes - oname = model.graph.output[0].name - oshape = model.get_tensor_shape(oname) - oshape_batch = oshape - oshape_batch[0] = batchsize - odt = model.get_tensor_datatype(oname) - i_bytes = (np.prod(ishape_batch) * idt.bitwidth()) / 8 - o_bytes = (np.prod(oshape_batch) * odt.bitwidth()) / 8 - # make empty exec context and insert input - ctx = model.make_empty_exec_context() - ctx[iname] = dummy_input - # remove liveness threshold, launch rtlsim - os.environ["LIVENESS_THRESHOLD"] = "-1" - rtlsim_exec(model, ctx) - # extract metrics - cycles = int(model.get_metadata_prop("cycles_rtlsim")) - clk_ns = float(model.get_metadata_prop("clk_ns")) - fclk_mhz = 1 / (clk_ns * 0.001) - runtime_s = (cycles * clk_ns) * (10 ** -9) - res = dict() - res["cycles"] = cycles - res["runtime[ms]"] = runtime_s * 1000 - res["throughput[images/s]"] = batchsize / runtime_s - res["DRAM_in_bandwidth[Mb/s]"] = i_bytes * 0.000001 / runtime_s - res["DRAM_out_bandwidth[Mb/s]"] = o_bytes * 0.000001 / runtime_s - res["fclk[mhz]"] = fclk_mhz - res["N"] = batchsize - - return res diff --git a/src/finn/custom_op/__init__.py b/src/finn/custom_op/__init__.py index 4ae7b9ebffaab6ca6be04b8d73f647b2db22dc78..06fc7e5659d8f55f63fe40380abac70dc74c0a4d 100644 --- a/src/finn/custom_op/__init__.py +++ b/src/finn/custom_op/__init__.py @@ -26,101 +26,53 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from abc import ABC, abstractmethod -from finn.util.basic import get_by_name -import onnx.helper as helper +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) -class CustomOp(ABC): - """CustomOp class all custom op nodes are based on. Contains different functions - every custom node should have. Some as abstract methods, these have to be - filled when writing a new custom op node.""" +from finn.custom_op.registry import custom_op - def __init__(self, onnx_node): - super().__init__() - self.onnx_node = onnx_node +# make sure new CustomOp subclasses are imported here so that they get +# registered and plug in correctly into the infrastructure +from finn.custom_op.fpgadataflow.convolutioninputgenerator import ( + ConvolutionInputGenerator, +) +from finn.custom_op.fpgadataflow.downsampler import DownSampler +from finn.custom_op.fpgadataflow.streamingfclayer_batch import StreamingFCLayer_Batch +from finn.custom_op.fpgadataflow.streamingmaxpool_batch import StreamingMaxPool_Batch +from finn.custom_op.fpgadataflow.streamingfifo import StreamingFIFO +from finn.custom_op.fpgadataflow.tlastmarker import TLastMarker +from finn.custom_op.fpgadataflow.streamingdatawidthconverter_batch import ( + StreamingDataWidthConverter_Batch, +) +from finn.custom_op.fpgadataflow.globalaccpool_batch import GlobalAccPool_Batch +from finn.custom_op.fpgadataflow.pool_batch import Pool_Batch +from finn.custom_op.fpgadataflow.fmpadding_batch import FMPadding_Batch +from finn.custom_op.fpgadataflow.thresholding_batch import Thresholding_Batch +from finn.custom_op.fpgadataflow.addstreams_batch import AddStreams_Batch +from finn.custom_op.fpgadataflow.labelselect_batch import LabelSelect_Batch +from finn.custom_op.fpgadataflow.duplicatestreams_batch import DuplicateStreams_Batch +from finn.custom_op.fpgadataflow.vector_vector_activate_batch import ( + Vector_Vector_Activate_Batch, +) +from finn.custom_op.fpgadataflow.channelwise_op_batch import ChannelwiseOp_Batch +from finn.custom_op.fpgadataflow.iodma import IODMA - def get_nodeattr(self, name): - """Get a node attribute by name. Data is stored inside the ONNX node's - AttributeProto container. Attribute must be part of get_nodeattr_types. - Default value is returned if attribute is not set.""" - try: - (dtype, req, def_val) = self.get_nodeattr_types()[name] - attr = get_by_name(self.onnx_node.attribute, name) - if attr is not None: - # dtype indicates which ONNX Attribute member to use - # (such as i, f, s...) - ret = attr.__getattribute__(dtype) - if dtype == "s": - # decode string attributes - ret = ret.decode("utf-8") - return ret - else: - if req: - raise Exception( - """Required attribute %s unspecified in - a %s node""" - % (name, self.onnx_node.op_type) - ) - else: - # not set, return default value - return def_val - except KeyError: - raise AttributeError("Op has no such attribute: " + name) - def set_nodeattr(self, name, value): - """Set a node attribute by name. Data is stored inside the ONNX node's - AttributeProto container. Attribute must be part of get_nodeattr_types.""" - try: - (dtype, req, def_val) = self.get_nodeattr_types()[name] - attr = get_by_name(self.onnx_node.attribute, name) - if attr is not None: - # dtype indicates which ONNX Attribute member to use - # (such as i, f, s...) - if dtype == "s": - # encode string attributes - value = value.encode("utf-8") - attr.__setattr__(dtype, value) - else: - # not set, create and insert AttributeProto - attr_proto = helper.make_attribute(name, value) - self.onnx_node.attribute.append(attr_proto) - except KeyError: - raise AttributeError("Op has no such attribute: " + name) - - @abstractmethod - def get_nodeattr_types(self): - """Returns a dict of permitted attributes for node, where: - returned_dict[attribute_name] = (dtype, require, default_value) - - dtype indicates which member of the ONNX AttributeProto - will be utilized - - require indicates whether this attribute is required - - default_val indicates the default value that will be used if the - attribute is not set - """ - pass - - @abstractmethod - def make_shape_compatible_op(self, model): - """Returns a standard ONNX op which is compatible with this CustomOp - for performing shape inference.""" - pass - - @abstractmethod - def infer_node_datatype(self, model): - """Set the DataType annotations corresponding to the outputs of this - node.""" - pass - - @abstractmethod - def execute_node(self, context, graph): - """Execute this CustomOp instance, given the execution context and - ONNX graph.""" - pass - - @abstractmethod - def verify_node(self): - """Verifies that all attributes the node needs are there and - that particular attributes are set correctly. Also checks if - the number of inputs is equal to the expected number.""" - pass +custom_op["DownSampler"] = DownSampler +custom_op["StreamingMaxPool_Batch"] = StreamingMaxPool_Batch +custom_op["StreamingFCLayer_Batch"] = StreamingFCLayer_Batch +custom_op["ConvolutionInputGenerator"] = ConvolutionInputGenerator +custom_op["TLastMarker"] = TLastMarker +custom_op["StreamingDataWidthConverter_Batch"] = StreamingDataWidthConverter_Batch +custom_op["StreamingFIFO"] = StreamingFIFO +custom_op["GlobalAccPool_Batch"] = GlobalAccPool_Batch +custom_op["Pool_Batch"] = Pool_Batch +custom_op["FMPadding_Batch"] = FMPadding_Batch +custom_op["Thresholding_Batch"] = Thresholding_Batch +custom_op["AddStreams_Batch"] = AddStreams_Batch +custom_op["LabelSelect_Batch"] = LabelSelect_Batch +custom_op["DuplicateStreams_Batch"] = DuplicateStreams_Batch +custom_op["Vector_Vector_Activate_Batch"] = Vector_Vector_Activate_Batch +custom_op["ChannelwiseOp_Batch"] = ChannelwiseOp_Batch +custom_op["IODMA"] = IODMA diff --git a/src/finn/custom_op/debugmarker.py b/src/finn/custom_op/debugmarker.py deleted file mode 100644 index 6c02f0dc81295dfc5c3060d549b6853eac1d0bac..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/debugmarker.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from finn.custom_op import CustomOp -from onnx import helper - - -class DebugMarker(CustomOp): - def get_nodeattr_types(self): - return {"export_debug_name": ("s", True, "")} - - def make_shape_compatible_op(self, model): - node = self.onnx_node - return helper.make_node("Identity", [node.input[0]], [node.output[0]]) - - def infer_node_datatype(self, model): - node = self.onnx_node - # data type stays the same - dtype = model.get_tensor_datatype(node.input[0]) - model.set_tensor_datatype(node.output[0], dtype) - # create quantization annotation for debug marker - model.set_tensor_datatype(self.get_nodeattr("export_debug_name"), dtype) - - def execute_node(self, context, graph): - node = self.onnx_node - inp_name = node.input[0] - out_name = node.output[0] - inp = context[inp_name] - context[out_name] = inp - # insert debug marker output as separate tensor - context[self.get_nodeattr("export_debug_name")] = inp - - def verify_node(self): - info_messages = [] - # verify that "domain" is set to "finn" - domain_value = self.onnx_node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - return info_messages diff --git a/src/finn/custom_op/fpgadataflow/__init__.py b/src/finn/custom_op/fpgadataflow/__init__.py index 7de6cce936ee54d58d9a526e926ff79dcd35b90d..d093a410e5b30a039688ec1a5264e59b92edfd8a 100644 --- a/src/finn/custom_op/fpgadataflow/__init__.py +++ b/src/finn/custom_op/fpgadataflow/__init__.py @@ -25,12 +25,16 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# namespace package, extend path +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) from abc import abstractmethod import numpy as np import os import subprocess -from finn.custom_op import CustomOp +from finn.custom_op.base import CustomOp from finn.util.basic import ( CppBuilder, make_build_dir, @@ -321,7 +325,7 @@ class HLSCustomOp(CustomOp): builder = CppBuilder() # to enable additional debug features please uncommand the next line # builder.append_includes("-DDEBUG") - builder.append_includes("-I/workspace/finn/src/finn/data/cpp") + builder.append_includes("-I/workspace/finn/src/finn/qnn-data/cpp") builder.append_includes("-I/workspace/cnpy/") builder.append_includes("-I/workspace/finn-hlslib") builder.append_includes("-I{}/include".format(os.environ["VIVADO_PATH"])) diff --git a/src/finn/custom_op/im2col.py b/src/finn/custom_op/im2col.py deleted file mode 100644 index 8ed0041704d421dab587f08bcbcd9e739e8434e9..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/im2col.py +++ /dev/null @@ -1,203 +0,0 @@ -import numpy as np -from onnx import TensorProto, helper - -from finn.custom_op import CustomOp -import finn.util.basic as util -from finn.core.datatype import DataType - -# adapted from A. Karpathy's CS231 im2col code -# utilities to generate a patch matrix from a multichannel image -# of shape (batches, channels, height, width) - - -def compute_conv_output_dim(ifm_dim, k, stride, pad=0): - """Returns spatial output dimension size for convolution with given params.""" - return int(((ifm_dim + 2 * pad - k) / stride) + 1) - - -def get_im2col_indices_nchw( - x_shape, field_height, field_width, padding=0, stride_y=1, stride_x=1 -): - """Returns im2col indices.""" - # First figure out what the size of the output should be - N, C, H, W = x_shape - out_height = compute_conv_output_dim(H, field_height, stride_y, padding) - out_width = compute_conv_output_dim(W, field_width, stride_x, padding) - - i0 = np.repeat(np.arange(field_height), field_width) - i0 = np.tile(i0, C) - i1 = stride_y * np.repeat(np.arange(out_height), out_width) - j0 = np.tile(np.arange(field_width), field_height * C) - j1 = stride_x * np.tile(np.arange(out_width), out_height) - i = i0.reshape(-1, 1) + i1.reshape(1, -1) - j = j0.reshape(-1, 1) + j1.reshape(1, -1) - - k = np.repeat(np.arange(C), field_height * field_width).reshape(-1, 1) - - return (k, i, j) - - -def im2col_indices_nchw( - x, field_height, field_width, padding=0, stride_y=1, stride_x=1, pad_val=0 -): - """Performs im2col on x with given field height and width, as well as values - for padding and stride size. - Returns result of im2col.""" - # Zero-pad the input - p = padding - x_padded = np.pad( - x, ((0, 0), (0, 0), (p, p), (p, p)), mode="constant", constant_values=pad_val - ) - - k, i, j = get_im2col_indices_nchw( - x.shape, field_height, field_width, padding, stride_y, stride_x - ) - - cols = x_padded[:, k, i, j] - C = x.shape[1] - cols = cols.transpose(1, 2, 0).reshape(field_height * field_width * C, -1) - return cols - - -# ONNX i/o tensor shape assumptions for Im2Col: -# input 0 is the input vector, shape (1, ih, iw, ifm) -# output 0 is the output vector, shape (1, oh, ow, k*k*ifm) -# where: -# * ih, iw are the height and width of the input image -# * oh, ow are the height and width of the output (lowered) image -# * ifm is the number of input channels -# * k is the convolutional kernel size - -# note: for the innermost (dot product) dimension of k*k*ifm, we -# assume an internal ordering (k, k, ifm) - - -class Im2Col(CustomOp): - def get_nodeattr_types(self): - return { - "stride": ("i", True, 1), - "kernel_size": ("i", True, 1), - "input_shape": ("s", True, ""), - "pad_amount": ("i", False, 0), - "pad_value": ("i", False, 0), - # depthwise: if != 0, infer ConvolutionInputGenerator with depthwise == 1 - "depthwise": ("i", False, 0), - } - - def make_shape_compatible_op(self, model): - k = self.get_nodeattr("kernel_size") - stride = self.get_nodeattr("stride") - ishape = self.get_nodeattr("input_shape") - pad = self.get_nodeattr("pad_amount") - - # convert string into list of integers - ishape = ishape.strip("(") - ishape = ishape.strip(")") - ishape = ishape.split(",") - for i in range(0, len(ishape)): - ishape[i] = int(ishape[i]) - - # extract all necessary information and determine output dimensions - ifm_ch = ishape[-1] - assert len(ishape) == 4, "Unexpected input shape for Im2Col" - assert ishape[1] == ishape[2], "Im2Col for non-square images unsupported" - ifm_dim = ishape[1] - ofm_dim = compute_conv_output_dim(ifm_dim, k, stride, pad) - - # implement tensor with correct shape - values = np.random.randn(1, ofm_dim, ofm_dim, k * k * ifm_ch).astype(np.float32) - return helper.make_node( - "Constant", - inputs=[], - outputs=[self.onnx_node.output[0]], - value=helper.make_tensor( - name="const_tensor", - data_type=TensorProto.FLOAT, - dims=values.shape, - vals=values.flatten().astype(float), - ), - ) - - def infer_node_datatype(self, model): - node = self.onnx_node - # data type stays the same - dtype = model.get_tensor_datatype(node.input[0]) - model.set_tensor_datatype(node.output[0], dtype) - - def execute_node(self, context, graph): - node = self.onnx_node - k = self.get_nodeattr("kernel_size") - stride = self.get_nodeattr("stride") - pad = self.get_nodeattr("pad_amount") - pad_val = self.get_nodeattr("pad_value") - iname = node.input[0] - x = context[iname] - qnt_annotations = graph.quantization_annotation - ret = util.get_by_name(qnt_annotations, iname, "tensor_name") - ret = util.get_by_name(ret.quant_parameter_tensor_names, "finn_datatype", "key") - idt = DataType[ret.value] - if pad != 0: - assert idt.allowed(pad_val), "Im2Col dtype must allow pad_val" - # check that input is NHWC - assert x.ndim == 4, "Unexpected number of input dims for Im2Col" - N, H, W, C = x.shape - assert H == W, "Unexpected input shape for Im2Col" - out_dim = compute_conv_output_dim(H, k, stride, pad) - # internally convert input to NCHW - x = x.transpose(0, 3, 1, 2) - # call NCHW im2col implementation - ret = im2col_indices_nchw(x, k, k, pad, stride, stride, pad_val=pad_val) - # result shape is (k*k*N, out_dim*out_dim), convert to NCHW - ret = ret.reshape(N, C, k, k, out_dim, out_dim) - # (N=0,C=1,kh=2,kw=3,H=4,W=5) -> (N=0,H=4,W=5,kh=2,kw=3,C=1) - ret = ret.transpose(0, 4, 5, 2, 3, 1) - ret = ret.reshape(N, out_dim, out_dim, k * k * C) - - # ret = ret.reshape(N, k * k * C, out_dim, out_dim) - # convert output back to NHWC - # ret = ret.transpose(0, 2, 3, 1) - context[node.output[0]] = ret - - def verify_node(self): - node = self.onnx_node - - info_messages = [] - - # verify number of attributes - num_of_attr = 3 - if len(node.attribute) == num_of_attr: - info_messages.append("The number of attributes is correct") - else: - info_messages.append( - """The number of attributes is incorrect, - {} should have {} attributes""".format( - node.op_type, num_of_attr - ) - ) - - # verify that "domain" is set to "finn" - domain_value = node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - - # verify that all necessary attributes exist - try: - self.get_nodeattr("stride") - self.get_nodeattr("kernel_size") - info_messages.append("All necessary attributes exist") - except Exception: - info_messages.append( - """The necessary attributes do not exist. - Im2Col needs the following attributes: - stride, kernel_size""" - ) - - # verify the number of inputs - if len(node.input) == 1: - info_messages.append("The number of inputs is correct") - else: - info_messages.append("{} needs 1 data input".format(node.op_type)) - - return info_messages diff --git a/src/finn/custom_op/maxpoolnhwc.py b/src/finn/custom_op/maxpoolnhwc.py deleted file mode 100644 index c623e40075e0ed6836dc9494ee5effb4539a46af..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/maxpoolnhwc.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from finn.custom_op import CustomOp -import numpy as np -from onnx import helper, TensorProto -from finn.core.modelwrapper import ModelWrapper - - -def compute_pool_output_dim(ifm_dim, k, stride, pad=0): - "Return spatial output dimension size for pooling with given params." - return int(((ifm_dim + 2 * pad - k) / stride) + 1) - - -class MaxPoolNHWC(CustomOp): - # a MaxPool node, but using the NHWC data layout - - def get_nodeattr_types(self): - # no specific attributes for MaxPoolNHWC - return { - "kernel_shape": ("ints", True, []), - "pads": ("ints", True, []), - "strides": ("ints", True, []), - } - - def make_shape_compatible_op(self, model): - node = self.onnx_node - iname = node.input[0] - ishape = model.get_tensor_shape(iname) - kernel_shape = self.get_nodeattr("kernel_shape") - pads = self.get_nodeattr("pads") - strides = self.get_nodeattr("strides") - assert len(kernel_shape) == 2, "Non-2D MaxPoolNHWC not supported" - assert pads[0] == pads[2], "Uneven padding not supported" - assert pads[1] == pads[3], "Uneven padding not supported" - (n, hi, wi, c) = ishape - ho = compute_pool_output_dim(hi, kernel_shape[0], strides[0], pads[0]) - wo = compute_pool_output_dim(wi, kernel_shape[1], strides[1], pads[2]) - oshape = (n, ho, wo, c) - # implement tensor with correct shape - values = np.random.randn(*oshape).astype(np.float32) - return helper.make_node( - "Constant", - inputs=[], - outputs=[self.onnx_node.output[0]], - value=helper.make_tensor( - name="const_tensor", - data_type=TensorProto.FLOAT, - dims=values.shape, - vals=values.flatten().astype(float), - ), - ) - - def infer_node_datatype(self, model): - node = self.onnx_node - # data type stays the same - dtype = model.get_tensor_datatype(node.input[0]) - model.set_tensor_datatype(node.output[0], dtype) - - def execute_node(self, context, graph): - node = self.onnx_node - inp_name = node.input[0] - out_name = node.output[0] - inp = context[inp_name] - dummy_out = context[out_name] - # convert i/o NHWC -> NCHW - inp = np.transpose(inp, (0, 3, 1, 2)) - dummy_out = np.transpose(dummy_out, (0, 3, 1, 2)) - # execute as regular MaxPool - node.domain = "" - node.op_type = "MaxPool" - inp_vi = helper.make_tensor_value_info(inp_name, TensorProto.FLOAT, inp.shape) - out_vi = helper.make_tensor_value_info( - out_name, TensorProto.FLOAT, dummy_out.shape - ) - tmp_graph = helper.make_graph( - nodes=[node], name="tmp_graph", inputs=[inp_vi], outputs=[out_vi] - ) - tmp_model = helper.make_model(tmp_graph, producer_name="finn") - tmp_model = ModelWrapper(tmp_model) - new_ctx = {inp_name: inp} - from finn.core.onnx_exec import execute_onnx - - ret = execute_onnx(tmp_model, new_ctx) - # restore original node props - node.domain = "finn" - node.op_type = "MaxPoolNHWC" - outp = ret[out_name] - # convert output NCHW -> NHWC - outp = np.transpose(outp, (0, 2, 3, 1)) - context[out_name] = outp - - def verify_node(self): - info_messages = [] - # verify that "domain" is set to "finn" - domain_value = self.onnx_node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - return info_messages diff --git a/src/finn/custom_op/multithreshold.py b/src/finn/custom_op/multithreshold.py deleted file mode 100644 index bc0a454cdf847d124b12c940b029f51bf2d3e778..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/multithreshold.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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.helper as helper - -from finn.core.datatype import DataType -from finn.custom_op import CustomOp - - -def multithreshold(v, thresholds, out_scale=None, out_bias=None): - """Given a set of threshold values t={t_0, t_1 ... t_n} the successive - thresholding maps any real number x to an integer in the interval [0, n], - where the returned integer is the number of thresholds x is greater than - or equal to. - - The output tensor will be scaled by out_scale and biased by out_bias.""" - # the inputs are expected to be in the shape (N,C,H,W) or (N, C) - # the MultiThreshold node supports a data_layout attribute that can be set - # to 'NHWC' to support (N,H,W,C) data layout mode for in-out as well - # N : Batch size - # C : Number of channels - # H : Heigth of the input images - # W : Width of the input images - # - # the thresholds are expected to be in the shape (C, B) - # C : Number of channels (must be the same value as C in input tensor - # or 1 if all channels use the same threshold value) - # B : Desired activation steps => i.e. for 4-bit activation, - # B=7 (2^(n)-1 and n=4) - # the output tensor will be scaled by out_scale and biased by out_bias - # assert threshold shape - is_global_threshold = thresholds.shape[0] == 1 - assert ( - v.shape[1] == thresholds.shape[0] - ) or is_global_threshold, """"Threshold - shape incorrect""" - # save the required shape sizes for the loops (N, C and B) - num_batch = v.shape[0] - num_channel = v.shape[1] - num_act = thresholds.shape[1] - # reshape inputs to enable channel-wise reading - vr = v.reshape((v.shape[0], v.shape[1], -1)) - # initiate output tensor - ret = np.zeros_like(vr) - # iterate over thresholds channel-wise - for t in range(num_channel): - channel_thresh = thresholds[0] if is_global_threshold else thresholds[t] - # iterate over batches - for b in range(num_batch): - # iterate over the different thresholds for one channel - for a in range(num_act): - ret[b][t] += (vr[b][t] >= channel_thresh[a]).astype(int) - - if out_scale is None: - out_scale = 1.0 - if out_bias is None: - out_bias = 0.0 - return out_scale * ret.reshape(v.shape) + out_bias - - -class MultiThreshold(CustomOp): - """Class that corresponds to a multithresholding node.""" - - def get_nodeattr_types(self): - return { - "out_dtype": ("s", True, ""), - "out_scale": ("f", False, 1.0), - "out_bias": ("f", False, 0.0), - "data_layout": ("s", False, "NCHW"), - } - - def make_shape_compatible_op(self, model): - node = self.onnx_node - return helper.make_node("Relu", [node.input[0]], [node.output[0]]) - - def infer_node_datatype(self, model): - node = self.onnx_node - odt = self.get_nodeattr("out_dtype") - model.set_tensor_datatype(node.output[0], DataType[odt]) - - def execute_node(self, context, graph): - node = self.onnx_node - # save inputs - v = context[node.input[0]] - thresholds = context[node.input[1]] - # retrieve attributes if output scaling is used - out_scale = self.get_nodeattr("out_scale") - out_bias = self.get_nodeattr("out_bias") - # transpose input if NHWC data layout is chosen - data_layout = self.get_nodeattr("data_layout") - if data_layout == "NHWC": - if v.ndim == 4: - # NHWC -> NCHW - v = np.transpose(v, (0, 3, 1, 2)) - elif v.ndim == 2: - # no HW dimension means NHWC and NCHW layouts are equivalent - pass - else: - raise Exception( - "Unknown data_layout and input ndim" - " combination for MultiThreshold." - ) - # calculate output - output = multithreshold(v, thresholds, out_scale, out_bias) - # setting context according to output - if data_layout == "NHWC": - if output.ndim == 4: - # NCHW -> NHWC - output = np.transpose(output, (0, 2, 3, 1)) - elif output.ndim == 2: - # no HW dimension means NHWC and NCHW layouts are equivalent - pass - else: - raise Exception( - "Unknown data_layout and output ndim" - " combination for MultiThreshold." - ) - context[node.output[0]] = output - - def verify_node(self): - info_messages = [] - - # verify that "domain" is set to "finn" - domain_value = self.onnx_node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - - # verify that all necessary attributes exist - try: - self.get_nodeattr("out_dtype") - info_messages.append("All necessary attributes exist") - except Exception: - info_messages.append( - """The necessary attributes do not exist. - MultiThreshold needs the following attributes: - out_scale, out_bias, out_dtype""" - ) - - # verify the number of inputs - if len(self.onnx_node.input) == 2: - info_messages.append("The number of inputs is correct") - else: - info_messages.append( - """MultiThreshold needs 2 inputs - (data input and threshold values)""" - ) - - return info_messages diff --git a/src/finn/custom_op/quantavgpool2d.py b/src/finn/custom_op/quantavgpool2d.py deleted file mode 100644 index 28d01069264d883f3afc400808470f5f303be799..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/quantavgpool2d.py +++ /dev/null @@ -1,136 +0,0 @@ -import numpy as np -from onnx import TensorProto, helper -import onnxruntime as rt - -from finn.custom_op import CustomOp -from finn.core.datatype import DataType -from finn.custom_op.maxpoolnhwc import compute_pool_output_dim - - -class QuantAvgPool2d(CustomOp): - """Class that corresponds to the quantized average pooling - layer from brevitas""" - - def get_nodeattr_types(self): - return { - "stride": ("i", True, 1), - "kernel": ("i", True, 1), - "ibits": ("i", True, 1), - "obits": ("i", True, 1), - # determines if values are signed (set to "1") or unsigned ("0") - "signed": ("i", True, 0), - # data layout attribute can be set to "NCHW" or "NHWC" - "data_layout": ("s", False, "NCHW"), - } - - def make_shape_compatible_op(self, model): - node = self.onnx_node - k = self.get_nodeattr("kernel") - s = self.get_nodeattr("stride") - data_layout = self.get_nodeattr("data_layout") - if data_layout == "NCHW": - return helper.make_node( - "AveragePool", - inputs=[node.input[0]], - outputs=[node.output[0]], - kernel_shape=[k, k], - strides=[s, s], - ) - elif data_layout == "NHWC": - iname = node.input[0] - ishape = model.get_tensor_shape(iname) - (n, hi, wi, c) = ishape - ho = compute_pool_output_dim(hi, k, s) - wo = compute_pool_output_dim(wi, k, s) - oshape = (n, ho, wo, c) - # implement tensor with correct shape - values = np.random.randn(*oshape).astype(np.float32) - return helper.make_node( - "Constant", - inputs=[], - outputs=[node.output[0]], - value=helper.make_tensor( - name="const_tensor", - data_type=TensorProto.FLOAT, - dims=values.shape, - vals=values.flatten().astype(float), - ), - ) - - else: - raise Exception( - """Datalayout for QuantAvgPool2d is set to an invalid value. - Has to be set to "NCHW" or "NHWC".""" - ) - - def infer_node_datatype(self, model): - node = self.onnx_node - bw = self.get_nodeattr("obits") - if bw in [2, 4, 8, 16, 32]: - if self.get_nodeattr("signed") == 0: - dtype = DataType["UINT%d" % bw] - else: - dtype = DataType["INT%d" % bw] - else: - raise Exception("Unsupported output datatype for QuantAvgPool2d") - model.set_tensor_datatype(node.output[0], dtype) - - def get_accum_size(self): - ibits = self.get_nodeattr("ibits") - k = self.get_nodeattr("kernel") - max_value = 2 ** ibits - 1 - max_value = max_value * k * k - max_bit_width = int(max_value).bit_length() - return max_bit_width - - def get_shifts(self): - shift_bits = self.get_accum_size() - self.get_nodeattr("obits") - shift_bits = shift_bits if shift_bits >= 0 else 0 - return shift_bits - - def execute_node(self, context, graph): - # create a standard average pooling node to help calculate the result - node = self.onnx_node - k = self.get_nodeattr("kernel") - s = self.get_nodeattr("stride") - inp_values = context[node.input[0]] - oshape = context[node.output[0]].shape - if self.get_nodeattr("data_layout") == "NHWC": - inp_values = inp_values.transpose(0, 3, 1, 2) - oshape = (context[node.output[0]]).transpose(0, 3, 1, 2).shape - ishape = inp_values.shape - inp = helper.make_tensor_value_info(node.input[0], TensorProto.FLOAT, ishape) - outp = helper.make_tensor_value_info(node.output[0], TensorProto.FLOAT, oshape) - node_avgpool = helper.make_node( - "AveragePool", - inputs=[node.input[0]], - outputs=[node.output[0]], - kernel_shape=[k, k], - strides=[s, s], - ) - graph_avgpool = helper.make_graph( - nodes=[node_avgpool], - name="single-avgpool-exec", - inputs=[inp], - outputs=[outp], - ) - model_avgpool = helper.make_model(graph_avgpool) - idict = {node.input[0]: inp_values} - sess = rt.InferenceSession(model_avgpool.SerializeToString()) - result_temp = sess.run(None, idict) - # remove scaling introduced by average - result_temp = result_temp[0] * (k * k) - result = np.right_shift(result_temp.astype(int), self.get_shifts()) - if self.get_nodeattr("data_layout") == "NHWC": - result = result.transpose(0, 2, 3, 1) - context[node.output[0]] = result.astype(np.float32) - - def verify_node(self): - info_messages = [] - # verify that "domain" is set to "finn" - domain_value = self.onnx_node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - return info_messages diff --git a/src/finn/custom_op/registry.py b/src/finn/custom_op/registry.py deleted file mode 100644 index ecf2a711f17ac35c9bf8cb081fb4dc6d9bb6c01e..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/registry.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# make sure new CustomOp subclasses are imported here so that they get -# registered and plug in correctly into the infrastructure -from finn.custom_op.fpgadataflow.convolutioninputgenerator import ( - ConvolutionInputGenerator, -) -from finn.custom_op.fpgadataflow.downsampler import DownSampler -from finn.custom_op.fpgadataflow.streamingfclayer_batch import StreamingFCLayer_Batch -from finn.custom_op.fpgadataflow.streamingmaxpool_batch import StreamingMaxPool_Batch -from finn.custom_op.fpgadataflow.streamingfifo import StreamingFIFO -from finn.custom_op.im2col import Im2Col -from finn.custom_op.fpgadataflow.tlastmarker import TLastMarker -from finn.custom_op.multithreshold import MultiThreshold -from finn.custom_op.streamingdataflowpartition import StreamingDataflowPartition -from finn.custom_op.xnorpopcount import XnorPopcountMatMul -from finn.custom_op.maxpoolnhwc import MaxPoolNHWC -from finn.custom_op.fpgadataflow.streamingdatawidthconverter_batch import ( - StreamingDataWidthConverter_Batch, -) -from finn.custom_op.fpgadataflow.globalaccpool_batch import GlobalAccPool_Batch -from finn.custom_op.fpgadataflow.pool_batch import Pool_Batch -from finn.custom_op.fpgadataflow.fmpadding_batch import FMPadding_Batch -from finn.custom_op.fpgadataflow.thresholding_batch import Thresholding_Batch -from finn.custom_op.fpgadataflow.addstreams_batch import AddStreams_Batch -from finn.custom_op.fpgadataflow.labelselect_batch import LabelSelect_Batch -from finn.custom_op.quantavgpool2d import QuantAvgPool2d -from finn.custom_op.fpgadataflow.duplicatestreams_batch import DuplicateStreams_Batch -from finn.custom_op.fpgadataflow.vector_vector_activate_batch import ( - Vector_Vector_Activate_Batch, -) -from finn.custom_op.fpgadataflow.channelwise_op_batch import ChannelwiseOp_Batch -from finn.custom_op.fpgadataflow.iodma import IODMA -from finn.custom_op.debugmarker import DebugMarker - -# create a mapping of all known CustomOp names and classes -custom_op = {} - -custom_op["MultiThreshold"] = MultiThreshold -custom_op["DownSampler"] = DownSampler -custom_op["XnorPopcountMatMul"] = XnorPopcountMatMul -custom_op["Im2Col"] = Im2Col -custom_op["StreamingMaxPool_Batch"] = StreamingMaxPool_Batch -custom_op["StreamingFCLayer_Batch"] = StreamingFCLayer_Batch -custom_op["ConvolutionInputGenerator"] = ConvolutionInputGenerator -custom_op["TLastMarker"] = TLastMarker -custom_op["StreamingDataflowPartition"] = StreamingDataflowPartition -custom_op["MaxPoolNHWC"] = MaxPoolNHWC -custom_op["StreamingDataWidthConverter_Batch"] = StreamingDataWidthConverter_Batch -custom_op["StreamingFIFO"] = StreamingFIFO -custom_op["GlobalAccPool_Batch"] = GlobalAccPool_Batch -custom_op["Pool_Batch"] = Pool_Batch -custom_op["FMPadding_Batch"] = FMPadding_Batch -custom_op["Thresholding_Batch"] = Thresholding_Batch -custom_op["AddStreams_Batch"] = AddStreams_Batch -custom_op["LabelSelect_Batch"] = LabelSelect_Batch -custom_op["QuantAvgPool2d"] = QuantAvgPool2d -custom_op["DuplicateStreams_Batch"] = DuplicateStreams_Batch -custom_op["Vector_Vector_Activate_Batch"] = Vector_Vector_Activate_Batch -custom_op["ChannelwiseOp_Batch"] = ChannelwiseOp_Batch -custom_op["IODMA"] = IODMA -custom_op["DebugMarker"] = DebugMarker - - -def getCustomOp(node): - "Return a FINN CustomOp instance for the given ONNX node, if it exists." - op_type = node.op_type - try: - # lookup op_type in registry of CustomOps - inst = custom_op[op_type](node) - return inst - except KeyError: - # exception if op_type is not supported - raise Exception("Custom op_type %s is currently not supported." % op_type) diff --git a/src/finn/custom_op/streamingdataflowpartition.py b/src/finn/custom_op/streamingdataflowpartition.py deleted file mode 100644 index 31cd38fea3c5a9e88084c3332d46aebdb065f800..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/streamingdataflowpartition.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from finn.custom_op import CustomOp - - -class StreamingDataflowPartition(CustomOp): - """Class that corresponds to the meta/container node StreamingDataflowPartition - which is a placeholder for a group of fpgadataflow nodes that have been separated - out into a FINN-ONNX model of its own. Note that is does not produce any HLS or - bitfile by itself.""" - - def get_nodeattr_types(self): - return { - "model": ("s", True, ""), - "res_estimate": ("s", False, ""), - "res_hls": ("s", False, ""), - "res_synth": ("s", False, ""), - } - - def make_shape_compatible_op(self, model): - pass - - def infer_node_datatype(self, model): - pass - - def execute_node(self, context, graph): - # TODO add RPC execution with synthesized bitfile? - # whole-design rtlsim with PyVerilator may also be an alternative - pass - - def verify_node(self): - info_messages = [] - - # verify number of attributes - num_of_attr = 1 - if len(self.onnx_node.attribute) == num_of_attr: - info_messages.append("The number of attributes is correct") - else: - info_messages.append( - """The number of attributes is incorrect, - {} should have {} attributes""".format( - self.onnx_node.op_type, num_of_attr - ) - ) - - # verify that "domain" is set to "finn" - domain_value = self.onnx_node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - - # verify that all necessary attributes exist - try: - self.get_nodeattr("model") - info_messages.append("All necessary attributes exist") - except Exception: - info_messages.append( - """The necessary attributes do not exist. - StreamingDataflowPartition needs the following attribute(s): - model""" - ) - - # verify the number of inputs - if len(self.onnx_node.input) >= 1: - info_messages.append("The number of inputs is correct") - else: - info_messages.append("StreamingDataflowPartition needs 1 data input") - - return info_messages diff --git a/src/finn/custom_op/xnorpopcount.py b/src/finn/custom_op/xnorpopcount.py deleted file mode 100644 index 199b7a5d9938b662b8f23e30275768b427fd87a7..0000000000000000000000000000000000000000 --- a/src/finn/custom_op/xnorpopcount.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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.helper as helper - -from finn.core.datatype import DataType -from finn.custom_op import CustomOp - - -def xnorpopcountmatmul(inp0, inp1): - """Simulates XNOR-popcount matrix multiplication as a regular bipolar - matrix multiplication followed by some post processing.""" - # extract the operand shapes - # (M, K0) = inp0.shape - # (K1, N) = inp1.shape - K0 = inp0.shape[-1] - K1 = inp1.shape[0] - # make sure shapes are compatible with matmul - assert K0 == K1, "Matrix shapes are not compatible with matmul." - K = K0 - # convert binary inputs to bipolar - inp0_bipolar = 2.0 * inp0 - 1.0 - inp1_bipolar = 2.0 * inp1 - 1.0 - # call regular numpy matrix multiplication - out = np.matmul(inp0_bipolar, inp1_bipolar) - # XNOR-popcount does not produce the regular dot product result -- - # it returns the number of +1s after XNOR. let P be the number of +1s - # and N be the number of -1s. XNOR-popcount returns P, whereas the - # regular dot product result from numpy is P-N, so we need to apply - # some correction. - # out = P-N - # K = P+N - # out + K = 2P, so P = (out + K)/2 - return (out + K) * 0.5 - - -class XnorPopcountMatMul(CustomOp): - """Class that corresponds to a XNOR-popcount matrix - multiplication node.""" - - def get_nodeattr_types(self): - return {} - - def make_shape_compatible_op(self, model): - node = self.onnx_node - return helper.make_node( - "MatMul", [node.input[0], node.input[1]], [node.output[0]] - ) - - def infer_node_datatype(self, model): - node = self.onnx_node - # ensure inputs are binary - assert ( - model.get_tensor_datatype(node.input[0]) == DataType["BINARY"] - ), """FINN - DataType of first input is not set to BINARY as it should be.""" - assert ( - model.get_tensor_datatype(node.input[1]) == DataType["BINARY"] - ), """FINN - DataTypes of second input is not set to BINARY as it should be.""" - # XNOR-popcount produces unsigned integers, assume uint32 - model.set_tensor_datatype(node.output[0], DataType["UINT32"]) - - def execute_node(self, context, graph): - node = self.onnx_node - # save inputs - inp0 = context[node.input[0]] - inp1 = context[node.input[1]] - # calculate output - output = xnorpopcountmatmul(inp0, inp1) - # set context according to output name - context[node.output[0]] = output - - def verify_node(self): - info_messages = [] - - # verify number of attributes - num_of_attr = 0 - if len(self.onnx_node.attribute) == num_of_attr: - info_messages.append("The number of attributes is correct") - else: - info_messages.append( - """The number of attributes is incorrect, - {} should have {} attributes""".format( - self.onnx_node.op_type, num_of_attr - ) - ) - - # verify that "domain" is set to "finn" - domain_value = self.onnx_node.domain - if domain_value == "finn": - info_messages.append("Attribute domain is set correctly") - else: - info_messages.append('Attribute domain should be set to "finn"') - - # verify that all necessary attributes exist - info_messages.append("XnorPopcountMatMul should not have any attributes") - - # verify the number of inputs - if len(self.onnx_node.input) == 2: - info_messages.append("The number of inputs is correct") - else: - info_messages.append("XnorPopcountMatMul needs 2 data inputs") - - return info_messages diff --git a/src/finn/data/onnx/mnist-conv/model.onnx b/src/finn/data/onnx/mnist-conv/model.onnx deleted file mode 100644 index fc1a3f733c6e6243dd23dacb125b7a372de55a50..0000000000000000000000000000000000000000 Binary files a/src/finn/data/onnx/mnist-conv/model.onnx and /dev/null differ diff --git a/src/finn/data/onnx/mnist-conv/test_data_set_0/input_0.pb b/src/finn/data/onnx/mnist-conv/test_data_set_0/input_0.pb deleted file mode 100644 index f0072d51a480af615e92312608f75993be9f1136..0000000000000000000000000000000000000000 Binary files a/src/finn/data/onnx/mnist-conv/test_data_set_0/input_0.pb and /dev/null differ diff --git a/src/finn/data/onnx/mnist-conv/test_data_set_0/output_0.pb b/src/finn/data/onnx/mnist-conv/test_data_set_0/output_0.pb deleted file mode 100644 index a6f4cdf92e27aaab21e44098b5b28e16048098e2..0000000000000000000000000000000000000000 --- a/src/finn/data/onnx/mnist-conv/test_data_set_0/output_0.pb +++ /dev/null @@ -1,2 +0,0 @@ - -J(ãêsDU®ÄŒtÍEÚ'DWQeÄYôÐÄQôÄ3vÂNKBÄñ³Ä \ No newline at end of file diff --git a/src/finn/analysis/fpgadataflow/__init__.py b/src/finn/qnn-data/__init__.py similarity index 100% rename from src/finn/analysis/fpgadataflow/__init__.py rename to src/finn/qnn-data/__init__.py diff --git a/src/finn/data/cifar10/cifar10-test-data-class3.npz b/src/finn/qnn-data/cifar10/cifar10-test-data-class3.npz similarity index 100% rename from src/finn/data/cifar10/cifar10-test-data-class3.npz rename to src/finn/qnn-data/cifar10/cifar10-test-data-class3.npz diff --git a/src/finn/data/cpp/npy2apintstream.hpp b/src/finn/qnn-data/cpp/npy2apintstream.hpp similarity index 100% rename from src/finn/data/cpp/npy2apintstream.hpp rename to src/finn/qnn-data/cpp/npy2apintstream.hpp diff --git a/src/finn/data/onnx/finn-hls-model/finn-hls-onnx-model.onnx b/src/finn/qnn-data/onnx/finn-hls-model/finn-hls-onnx-model.onnx similarity index 100% rename from src/finn/data/onnx/finn-hls-model/finn-hls-onnx-model.onnx rename to src/finn/qnn-data/onnx/finn-hls-model/finn-hls-onnx-model.onnx diff --git a/src/finn/data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx b/src/finn/qnn-data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx similarity index 100% rename from src/finn/data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx rename to src/finn/qnn-data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx diff --git a/src/finn/transformation/__init__.py b/src/finn/transformation/__init__.py deleted file mode 100644 index e9f5fe15f6bdefe1e739394495f67a972ccff669..0000000000000000000000000000000000000000 --- a/src/finn/transformation/__init__.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" -Guide to writing FINN transformations -------------------------------------- - -* Your transformation must inherit the Transformation abstract base class. -* Your transformation's apply function should take in a ModelWrapper, and return - a tuple with (transformed_model: ModelWrapper, model_was_changed: Bool) -* The transformations are meant to be applied using the .transform function - in ModelWrapper. This makes a deep copy of the input model by default, so - you don't have to. -* model_was_changed indicates whether your transformation made any changes to - the model. If you know your transformation needs to be called only once and - repeated calls have no further effect, you can return False even if the model - was changed. -* You MUST return model_was_changed=False at some point when your transformation - is called multiple times, otherwise apply_repeated() will loop infinitely. -* If you cannot guarantee that the transformation will reach a fixed point, - you must declare this, return model_was_changed = False and let the user - manually re-apply the transform. -""" - -from abc import ABC, abstractmethod -from finn.util.basic import get_num_default_workers -import multiprocessing as mp - - -class Transformation(ABC): - """Transformation class all transformations are based on. Contains only - abstract method apply() every transformation has to fill.""" - - def __init__(self): - super().__init__() - - @abstractmethod - def apply(self, model): - pass - - -class NodeLocalTransformation(Transformation): - """ - Parent class for transformations, which can be executed locally to one node - by accessing and modifying the attributes of only that node. - This class can then automatically parallelize the transformation. - Transformations sublcassing NodeLocalTransformation must implement the - abstract method applyNodeLocal(). - - To control the degree of parallelization, specify the num_workers argument - in the constructor, using one of the following values: - * None: use NUM_DEFAULT_WORKERS environment variable - * 0: use all available CPU cores - * (any other int>0): set number of parallel workers - """ - - def __init__(self, num_workers=None): - super().__init__() - if num_workers is None: - self._num_workers = get_num_default_workers() - else: - self._num_workers = num_workers - assert self._num_workers >= 0, "Number of workers must be nonnegative." - if self._num_workers == 0: - self._num_workers = mp.cpu_count() - - @abstractmethod - def applyNodeLocal(self, node): - pass - - def apply(self, model): - # Remove old nodes from the current model - old_nodes = [] - for i in range(len(model.graph.node)): - old_nodes.append(model.graph.node.pop()) - - # Execute transformation in parallel - with mp.Pool(self._num_workers) as p: - new_nodes_and_bool = p.map(self.applyNodeLocal, old_nodes, chunksize=1) - - # extract nodes and check if the transformation needs to run again - # Note: .pop() had initially reversed the node order - run_again = False - for node, run in reversed(new_nodes_and_bool): - # Reattach new nodes to old model - model.graph.node.append(node) - if run is True: - run_again = True - - return (model, run_again) diff --git a/src/finn/transformation/batchnorm_to_affine.py b/src/finn/transformation/batchnorm_to_affine.py deleted file mode 100644 index 401c5916415cd327a52a43f89c076bd7abd40647..0000000000000000000000000000000000000000 --- a/src/finn/transformation/batchnorm_to_affine.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx import TensorProto -from onnx import helper as oh - -from finn.transformation import Transformation -from finn.transformation.infer_shapes import InferShapes - - -class BatchNormToAffine(Transformation): - """Replaces any test-time BatchNorm layers with Mul-Add layers.""" - - 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 == "BatchNormalization": - graph_modified = True - bn_input = n.input[0] - bn_output = n.output[0] - # extract batchnorm parameters as numpy arrays - scale = model.get_initializer(n.input[1]) - bias = model.get_initializer(n.input[2]) - mean = model.get_initializer(n.input[3]) - variance = model.get_initializer(n.input[4]) - epsilon = 1e-5 - # find A and B to compute batchnorm as affine transpose Ax+B - # TODO is a division by moving avg factor needed for variance? - A = scale / np.sqrt(epsilon + variance) - B = bias - (A * mean) - # see if we have surrounding Unsqueeze/Squeeze nodes we can remove - producer = model.find_producer(bn_input) - if producer is not None: - if producer.op_type == "Unsqueeze": - bn_input = producer.input[0] - consumer = model.find_consumer(bn_output) - if consumer is not None: - if consumer.op_type == "Squeeze": - bn_output = consumer.output[0] - data_shape = model.get_tensor_shape(bn_input) - assert A.ndim == B.ndim, "Unexpected mul/add dims in BatchNormToAffine" - assert ( - len(data_shape) >= A.ndim - ), "Unexpected number of dims found in BatchNormToAffine" - # reshape the mul/add constants to match the data shape/dims - # by adding (1,) dimensions to the right - n_spatial_dims = len(data_shape) - 2 - target_shape = (1, -1) + tuple(1 for i in range(n_spatial_dims)) - A = A.reshape(target_shape) - B = B.reshape(target_shape) - # create value_info and initializers for Mul and Add constants - mul_const = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, A.shape - ) - graph.value_info.append(mul_const) - model.set_initializer(mul_const.name, A) - mul_output = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, data_shape - ) - graph.value_info.append(mul_output) - add_const = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, B.shape - ) - graph.value_info.append(add_const) - model.set_initializer(add_const.name, B) - # create Mul and Add nodes to replace the batchnorm - mul_node = oh.make_node( - "Mul", [bn_input, mul_const.name], [mul_output.name] - ) - add_node = oh.make_node( - "Add", [mul_output.name, add_const.name], [bn_output] - ) - # insert where the batchnorm is to preserve topological ordering - graph.node.insert(node_ind, mul_node) - graph.node.insert(node_ind + 1, add_node) - # remove old nodes - graph.node.remove(n) - if consumer is not None: - if consumer.op_type == "Squeeze": - graph.node.remove(consumer) - if producer is not None: - if producer.op_type == "Unsqueeze": - graph.node.remove(producer) - model = model.transform(InferShapes()) - return (model, graph_modified) diff --git a/src/finn/transformation/bipolar_to_xnor.py b/src/finn/transformation/bipolar_to_xnor.py deleted file mode 100644 index 80f2a73351f8548c99efd8dedd8a04d44c8558a3..0000000000000000000000000000000000000000 --- a/src/finn/transformation/bipolar_to_xnor.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 warnings -from onnx import TensorProto -from onnx import helper as oh - -from finn.core.datatype import DataType -from finn.transformation import Transformation -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.infer_datatypes import InferDataTypes -from finn.util.basic import get_by_name -from finn.custom_op.registry import getCustomOp - - -class ConvertBipolarMatMulToXnorPopcount(Transformation): - """Convert MatMul nodes with all-bipolar inputs to XnorPopcountMatMul - and associated result correction.""" - - 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 == "MatMul": - mm_input = n.input[0] - mm_weight = n.input[1] - mm_output = n.output[0] - i_bp = model.get_tensor_datatype(mm_input) == DataType.BIPOLAR - w_bp = model.get_tensor_datatype(mm_weight) == DataType.BIPOLAR - if i_bp and w_bp: - # find producing threshold node and adjust output to binary - def find_prod_mt(x): - is_mt = x.op_type == "MultiThreshold" - is_bp = False - if is_mt: - dt = get_by_name(x.attribute, "out_dtype").s - is_bp = dt.decode("utf-8") == "BIPOLAR" - return is_mt and is_bp - - mt_chain = model.find_upstream(mm_input, find_prod_mt) - if len(mt_chain) == 0: - if mm_input == graph.input[0].name: - # change input datatype to BINARY - model.set_tensor_datatype(mm_input, DataType.BINARY) - graph_modified = True - warnings.warn( - """IMPORTANT: Changing graph input DataType - to BINARY instead of BIPOLAR. Ensure this is respected - when checking for correctness. - """ - ) - else: - raise Exception( - """Could not find upstream bipolar - MultiThreshold, and the MatMul is not the - first node on graph input. Unable to convert - input tensor from BIPOLAR to BINARY.""" - ) - else: - graph_modified = True - mt = mt_chain[-1] - mt_inst = getCustomOp(mt) - # ensure old scale/bias were correct for BIPOLAR - scale_ok = mt_inst.get_nodeattr("out_scale") == 2.0 - bias_ok = mt_inst.get_nodeattr("out_bias") == -1.0 - assert ( - scale_ok and bias_ok - ), """Unexpected scale/bias - attributes for BIPOLAR MultiThreshold node.""" - # start conversion, set MT output to binary - # (this is what XnorPopcountMatMul expects) - mt_inst.set_nodeattr("out_dtype", "BINARY") - mt_inst.set_nodeattr("out_scale", 1.0) - mt_inst.set_nodeattr("out_bias", 0.0) - model.set_tensor_datatype(mm_input, DataType.BINARY) - # change node type and domain - n.op_type = "XnorPopcountMatMul" - n.domain = "finn" - # convert weights into binary (-1,+1) -> (0,1) - Wbin = (model.get_initializer(mm_weight) + 1) / 2 - # extract vector length (common matrix dim) - K = Wbin.shape[0] - model.set_initializer(mm_weight, Wbin) - model.set_tensor_datatype(mm_weight, DataType.BINARY) - # make new output node with correct shape - mm_out_shape = model.get_tensor_shape(mm_output) - xnorpcout = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, mm_out_shape - ) - n.output[0] = xnorpcout.name - model.set_tensor_datatype(xnorpcout.name, DataType.UINT32) - # add mul-add nodes to produce correct dot product result - # need to derive P-N from P and K = P+N - # so we need 2*P-K - A = np.asarray([2.0], dtype=np.float32) - B = np.asarray([-K], dtype=np.float32) - # create value_info and initializers for Mul and Add constants - mul_const = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, A.shape - ) - graph.value_info.append(mul_const) - model.set_initializer(mul_const.name, A) - mul_output = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, mm_out_shape - ) - graph.value_info.append(mul_output) - add_const = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, B.shape - ) - graph.value_info.append(add_const) - model.set_initializer(add_const.name, B) - # create Mul and Add nodes to replace the batchnorm - mul_node = oh.make_node( - "Mul", [xnorpcout.name, mul_const.name], [mul_output.name] - ) - add_node = oh.make_node( - "Add", [mul_output.name, add_const.name], [mm_output] - ) - # insert where the batchnorm is to preserve topological ordering - graph.node.insert(node_ind, mul_node) - graph.node.insert(node_ind + 1, add_node) - if graph_modified: - model = model.transform(InferShapes()) - model = model.transform(InferDataTypes()) - return (model, graph_modified) diff --git a/src/finn/transformation/change_datalayout.py b/src/finn/transformation/change_datalayout.py deleted file mode 100644 index d5b393a25e57122b059a44f70904a6dbe5bbaa3f..0000000000000000000000000000000000000000 --- a/src/finn/transformation/change_datalayout.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from onnx import helper, TensorProto - -from finn.transformation import Transformation -from finn.transformation.infer_shapes import InferShapes -from finn.util.basic import get_by_name - - -class ChangeDataLayoutQuantAvgPool2d(Transformation): - """Replace QuantAvgPool2d with datalayout (N,C,H,W) with Transpose nodes - and QuantAvgPool2dNHWC with datalayout (N,H,W,C)""" - - 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 == "QuantAvgPool2d" and ( - get_by_name(n.attribute, "data_layout") is None - or get_by_name(n.attribute, "data_layout").s.decode("UTF-8") == "NCHW" - ): - graph_modified = True - node_input = n.input[0] - node_output = n.output[0] - s = get_by_name(n.attribute, "stride").i - k = get_by_name(n.attribute, "kernel").i - ibits = get_by_name(n.attribute, "ibits").i - obits = get_by_name(n.attribute, "obits").i - signed = get_by_name(n.attribute, "signed").i - batchsize = model.get_tensor_shape(n.input[0])[0] # assume NCHW - channels = model.get_tensor_shape(n.input[0])[1] # assume NCHW - idim = model.get_tensor_shape(n.input[0])[-1] # assume NCHW - odim = model.get_tensor_shape(n.output[0])[-1] # assume NCHW - - # create new nodes - # NCHW -> NHWC - # create new intermediate values - inp_trans_out = helper.make_tensor_value_info( - model.make_new_valueinfo_name(), - TensorProto.FLOAT, - (batchsize, idim, idim, channels), # NHWC - ) - graph.value_info.append(inp_trans_out) - inp_trans_out = inp_trans_out.name - quantavg_out = helper.make_tensor_value_info( - model.make_new_valueinfo_name(), - TensorProto.FLOAT, - (batchsize, odim, odim, channels), - ) - graph.value_info.append(quantavg_out) - quantavg_out = quantavg_out.name - inp_trans_node = helper.make_node( - "Transpose", [node_input], [inp_trans_out], perm=[0, 2, 3, 1] - ) - quantavg_node = helper.make_node( - "QuantAvgPool2d", - [inp_trans_out], - [quantavg_out], - domain="finn", - stride=s, - kernel=k, - ibits=ibits, - obits=obits, - signed=signed, - data_layout="NHWC", - ) - # NHWC -> NCHW - out_trans_node = helper.make_node( - "Transpose", [quantavg_out], [node_output], perm=[0, 3, 1, 2] - ) - # insert nodes - graph.node.insert(node_ind, inp_trans_node) - graph.node.insert(node_ind + 1, quantavg_node) - graph.node.insert(node_ind + 2, out_trans_node) - # remove old nodes - graph.node.remove(n) - - # set shapes - model.set_tensor_shape(inp_trans_out, (batchsize, idim, idim, channels)) - model.set_tensor_shape(quantavg_out, (batchsize, odim, odim, channels)) - model = model.transform(InferShapes()) - return (model, graph_modified) diff --git a/src/finn/transformation/double_to_single_float.py b/src/finn/transformation/double_to_single_float.py deleted file mode 100644 index 4f7eb1cc82b346321eb6ee711b87c2c98ccfdaee..0000000000000000000000000000000000000000 --- a/src/finn/transformation/double_to_single_float.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from finn.transformation import Transformation -import numpy as np - - -class DoubleToSingleFloat(Transformation): - """Convert any float64 initializers to float32.""" - - def apply(self, model): - graph_modified = False - init_names = [x.name for x in model.graph.initializer] - for nm in init_names: - init = model.get_initializer(nm) - if init.dtype == np.float64: - init_f32 = init.astype(np.float32) - model.set_initializer(nm, init_f32) - graph_modified = True - return (model, graph_modified) diff --git a/src/finn/transformation/fold_constants.py b/src/finn/transformation/fold_constants.py deleted file mode 100644 index a73035e571fe0ce0425c3d8da288f755984268d4..0000000000000000000000000000000000000000 --- a/src/finn/transformation/fold_constants.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.core.onnx_exec as oxe -from finn.transformation import Transformation -from finn.transformation.infer_shapes import InferShapes - - -class FoldConstants(Transformation): - """Replace the output of a node with const-only inputs with a precomputed - result.""" - - def apply(self, model): - graph = model.graph - node_ind = 0 - graph_modified = False - execution_context = model.make_empty_exec_context() - for n in graph.node: - node_ind += 1 - node_inp_inits = list(map(lambda x: model.get_initializer(x), n.input)) - node_inp_dyn = list(filter(lambda x: x is None, node_inp_inits)) - node_out = n.output[0] - is_all_constant_inputs = len(node_inp_dyn) == 0 - ishape = model.get_tensor_shape(n.input[0]) - is_const_shape = (n.op_type == "Shape") and (ishape is not None) - if is_all_constant_inputs or is_const_shape: - # this node has no dynamic inputs, only constant ones -- so we can - # do constant folding. - oxe.execute_node(n, execution_context, graph) - # use the execution result as an initializer - model.set_initializer(node_out, execution_context[node_out]) - # remove old node - graph.node.remove(n) - graph_modified = True - if graph_modified: - model = model.transform(InferShapes()) - return (model, graph_modified) diff --git a/src/finn/transformation/fpgadataflow/__init__.py b/src/finn/transformation/fpgadataflow/__init__.py deleted file mode 100644 index 83c8e8bed70797f7d6c0138968f750f72e790386..0000000000000000000000000000000000000000 --- a/src/finn/transformation/fpgadataflow/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/finn/transformation/fpgadataflow/annotate_cycles.py b/src/finn/transformation/fpgadataflow/annotate_cycles.py index 521c84952daf25982e574421dfba3ff0f7df91ae..2c547203df94308b929a7989f1a9102bd3002fed 100644 --- a/src/finn/transformation/fpgadataflow/annotate_cycles.py +++ b/src/finn/transformation/fpgadataflow/annotate_cycles.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import finn.custom_op.registry as registry -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.transformation.move_reshape import _is_fpgadataflow_node from finn.core.modelwrapper import ModelWrapper from finn.custom_op.registry import getCustomOp diff --git a/src/finn/transformation/fpgadataflow/annotate_resources.py b/src/finn/transformation/fpgadataflow/annotate_resources.py index d6ff058848700b50dadb7a6ed0ff6c07b7eeb4a3..4e501510110ef724c6a67f6214654b5454b30a77 100644 --- a/src/finn/transformation/fpgadataflow/annotate_resources.py +++ b/src/finn/transformation/fpgadataflow/annotate_resources.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import finn.custom_op.registry as registry -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.transformation.move_reshape import _is_fpgadataflow_node from finn.analysis.fpgadataflow.res_estimation import res_estimation from finn.analysis.fpgadataflow.hls_synth_res_estimation import hls_synth_res_estimation diff --git a/src/finn/transformation/fpgadataflow/cleanup.py b/src/finn/transformation/fpgadataflow/cleanup.py index 248a99b57aed7f38f63cc25ad7ecf93bd1930e63..f089317074eb2bded4675f6fd2e22fdaeb4b6a82 100644 --- a/src/finn/transformation/fpgadataflow/cleanup.py +++ b/src/finn/transformation/fpgadataflow/cleanup.py @@ -31,7 +31,7 @@ import shutil import finn.custom_op.registry as registry from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.transformation import Transformation +from finn.transformation.base import Transformation class CleanUp(Transformation): diff --git a/src/finn/transformation/fpgadataflow/compile_cppsim.py b/src/finn/transformation/fpgadataflow/compile_cppsim.py index ddf00c799b8a53c428d0854551d0078a6e264111..e17feb4683189ad2f8174f0564a877f84870b51d 100644 --- a/src/finn/transformation/fpgadataflow/compile_cppsim.py +++ b/src/finn/transformation/fpgadataflow/compile_cppsim.py @@ -28,7 +28,7 @@ import finn.custom_op.registry as registry from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.transformation import NodeLocalTransformation +from finn.transformation.base import NodeLocalTransformation class CompileCppSim(NodeLocalTransformation): diff --git a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py index d4d5b006493b8db1da0184e98ba35493d3e6ccbd..d6f2e04f762a6b67ace989fa802829fd3e5a6fb5 100644 --- a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py +++ b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py @@ -32,7 +32,7 @@ import numpy as np import warnings from finn.core.datatype import DataType -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.custom_op.registry import getCustomOp from finn.transformation.infer_shapes import InferShapes from finn.transformation.infer_datatypes import InferDataTypes diff --git a/src/finn/transformation/fpgadataflow/create_dataflow_partition.py b/src/finn/transformation/fpgadataflow/create_dataflow_partition.py index fb8b4358abd772d13c355f797649dc3b51975b4d..90a92d11ce621897e3e6c687f57b1cdf77d08fba 100644 --- a/src/finn/transformation/fpgadataflow/create_dataflow_partition.py +++ b/src/finn/transformation/fpgadataflow/create_dataflow_partition.py @@ -30,7 +30,7 @@ import copy from onnx import helper -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_by_name, make_build_dir diff --git a/src/finn/transformation/fpgadataflow/create_stitched_ip.py b/src/finn/transformation/fpgadataflow/create_stitched_ip.py index 0def25d8429f5d3f6c02a9db656650bc1baba6ee..3470e9525d590303eca1f8700fe4b79c4e03d38f 100644 --- a/src/finn/transformation/fpgadataflow/create_stitched_ip.py +++ b/src/finn/transformation/fpgadataflow/create_stitched_ip.py @@ -30,7 +30,7 @@ import os import warnings import subprocess -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_by_name, make_build_dir from finn.custom_op.registry import getCustomOp from finn.util.basic import get_num_default_workers diff --git a/src/finn/transformation/fpgadataflow/floorplan.py b/src/finn/transformation/fpgadataflow/floorplan.py index 1d9a51875499d77f384c03f54009a9dd1001dea0..eaade4a335258bb2ffd7ce0f87da8ae7c3fd00b3 100644 --- a/src/finn/transformation/fpgadataflow/floorplan.py +++ b/src/finn/transformation/fpgadataflow/floorplan.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_by_name diff --git a/src/finn/transformation/fpgadataflow/hlssynth_ip.py b/src/finn/transformation/fpgadataflow/hlssynth_ip.py index 8315b6ec11e3d0bc5e2bf97e7c11817ae8b5a5b1..e79d70544c5e8d2b9060e354d7713b8405ae9c7f 100644 --- a/src/finn/transformation/fpgadataflow/hlssynth_ip.py +++ b/src/finn/transformation/fpgadataflow/hlssynth_ip.py @@ -29,7 +29,7 @@ import os import finn.custom_op.registry as registry from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.transformation import NodeLocalTransformation +from finn.transformation.base import NodeLocalTransformation import warnings diff --git a/src/finn/transformation/fpgadataflow/insert_dwc.py b/src/finn/transformation/fpgadataflow/insert_dwc.py index 3fe60292e8f54a8cdf394b5e09f8a3d2bca7605c..195a005ff87b43c6b64017354895693cd811a48e 100644 --- a/src/finn/transformation/fpgadataflow/insert_dwc.py +++ b/src/finn/transformation/fpgadataflow/insert_dwc.py @@ -2,7 +2,7 @@ from onnx import TensorProto from onnx import helper as oh from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.fpgadataflow import is_fpgadataflow_node diff --git a/src/finn/transformation/fpgadataflow/insert_fifo.py b/src/finn/transformation/fpgadataflow/insert_fifo.py index 6f7fde0c4faba09e584eb578819f44c18639bc9d..1c26642888f29e8dd08046e4de01ae8fa62b10e7 100644 --- a/src/finn/transformation/fpgadataflow/insert_fifo.py +++ b/src/finn/transformation/fpgadataflow/insert_fifo.py @@ -2,7 +2,7 @@ from onnx import TensorProto from onnx import helper as oh from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.fpgadataflow import is_fpgadataflow_node import numpy as np diff --git a/src/finn/transformation/fpgadataflow/insert_iodma.py b/src/finn/transformation/fpgadataflow/insert_iodma.py index 72e5ec4fdd721ecf549adaf7ddd38db4636bce27..feaa534e1e9d2fb527293a617cc622a5f71c24cb 100644 --- a/src/finn/transformation/fpgadataflow/insert_iodma.py +++ b/src/finn/transformation/fpgadataflow/insert_iodma.py @@ -31,7 +31,7 @@ from onnx import helper as oh from finn.util.basic import get_by_name from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.transformation.general import SortGraph import finn.core.data_layout as DataLayout import math diff --git a/src/finn/transformation/fpgadataflow/insert_tlastmarker.py b/src/finn/transformation/fpgadataflow/insert_tlastmarker.py index bbb0e43fda464e919a7d8c9dcd25e08a49b33cec..8ffb083217bb3a7e379112b3da102487c0cd50c2 100644 --- a/src/finn/transformation/fpgadataflow/insert_tlastmarker.py +++ b/src/finn/transformation/fpgadataflow/insert_tlastmarker.py @@ -30,7 +30,7 @@ from onnx import TensorProto from onnx import helper as oh from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_by_name import numpy as np diff --git a/src/finn/transformation/fpgadataflow/make_deployment.py b/src/finn/transformation/fpgadataflow/make_deployment.py index 2880e4aba20564f50a0acdff5e8c728714c84b5c..6d37f567c9a20cf692df126c1c3560324b61d06d 100644 --- a/src/finn/transformation/fpgadataflow/make_deployment.py +++ b/src/finn/transformation/fpgadataflow/make_deployment.py @@ -32,7 +32,7 @@ import subprocess from distutils.dir_util import copy_tree from shutil import copy -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import make_build_dir import finn.transformation.fpgadataflow.templates as templates diff --git a/src/finn/transformation/fpgadataflow/make_pynq_driver.py b/src/finn/transformation/fpgadataflow/make_pynq_driver.py index 813b40698d1beec54e6ba3fa5344a8d0bb715a00..e8e3059240556296ca635ffcc36665fb18beda3b 100644 --- a/src/finn/transformation/fpgadataflow/make_pynq_driver.py +++ b/src/finn/transformation/fpgadataflow/make_pynq_driver.py @@ -28,9 +28,10 @@ import shutil -from finn.transformation import Transformation -from finn.util.basic import gen_finn_dt_tensor, get_finn_root, make_build_dir -from finn.util.data_packing import finnpy_to_packed_bytearray +from finn.transformation.base import Transformation +from finn.util.basic import gen_finn_dt_tensor, make_build_dir +import finn.util.data_packing as dpk +import finn.core.datatype as dtp from . import templates @@ -77,10 +78,10 @@ class MakePYNQDriver(Transformation): # generate dummy folded i/o tensors and their packed versions i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded) o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded) - i_tensor_dummy_packed = finnpy_to_packed_bytearray( + i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( i_tensor_dummy_folded, i_tensor_dt ) - o_tensor_dummy_packed = finnpy_to_packed_bytearray( + o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray( o_tensor_dummy_folded, o_tensor_dt ) i_tensor_shape_packed = i_tensor_dummy_packed.shape @@ -132,11 +133,17 @@ class MakePYNQDriver(Transformation): f.write(validate_src) # copy all the dependencies into the driver folder - shutil.copytree( - get_finn_root() + "/src/finn/util", pynq_driver_dir + "/finn/util" - ) - shutil.copytree( - get_finn_root() + "/src/finn/core", pynq_driver_dir + "/finn/core" - ) + # driver imports utils/data_packing and core/datatype + # both of which are in finn-base + # e.g. /workspace/finn-base/src/finn/util/data_packing.py + dpk_root = dpk.__file__ + # e.g. /workspace/finn-base/src/finn/util + dpk_root = dpk_root.replace("data_packing.py", "") + # e.g. /workspace/finn-base/src/finn/core/datatype.py + dtp_root = dtp.__file__ + # e.g. /workspace/finn-base/src/finn/core + dtp_root = dtp_root.replace("datatype.py", "") + shutil.copytree(dpk_root, pynq_driver_dir + "/finn/util") + shutil.copytree(dtp_root, pynq_driver_dir + "/finn/core") return (model, False) diff --git a/src/finn/transformation/fpgadataflow/make_zynq_proj.py b/src/finn/transformation/fpgadataflow/make_zynq_proj.py index e263c450af5d2dca09a79b7757a0c0d67bdf86ff..1a4b67d1e7adb86b5a7515eb0ff14b780eea0585 100644 --- a/src/finn/transformation/fpgadataflow/make_zynq_proj.py +++ b/src/finn/transformation/fpgadataflow/make_zynq_proj.py @@ -30,7 +30,7 @@ import os import subprocess from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.core.modelwrapper import ModelWrapper from finn.util.basic import get_by_name, make_build_dir from finn.util.basic import get_num_default_workers diff --git a/src/finn/transformation/fpgadataflow/minimize_accumulator_width.py b/src/finn/transformation/fpgadataflow/minimize_accumulator_width.py index 2c54a5efbd3b28f0fbfd074b512929edab234e78..0a0c45b6bedeabe7cfa1c7209ea74b6876b828b2 100644 --- a/src/finn/transformation/fpgadataflow/minimize_accumulator_width.py +++ b/src/finn/transformation/fpgadataflow/minimize_accumulator_width.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from finn.custom_op.registry import getCustomOp -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.fpgadataflow import is_fpgadataflow_node diff --git a/src/finn/transformation/fpgadataflow/prepare_cppsim.py b/src/finn/transformation/fpgadataflow/prepare_cppsim.py index 6eae560e1191642cfaf85d92c6d0fcf644630973..26354bdf70e10bfcddfbaf732a214865c6feb8f5 100644 --- a/src/finn/transformation/fpgadataflow/prepare_cppsim.py +++ b/src/finn/transformation/fpgadataflow/prepare_cppsim.py @@ -31,7 +31,7 @@ import os import finn.custom_op.registry as registry from finn.util.basic import make_build_dir from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_num_default_workers import multiprocessing as mp import copy diff --git a/src/finn/transformation/fpgadataflow/prepare_ip.py b/src/finn/transformation/fpgadataflow/prepare_ip.py index 21f8e0052d5f2d60f11f33846b483d3f556d1188..53cb0af163b853c0a0352d8562cca66b3ecf6068 100644 --- a/src/finn/transformation/fpgadataflow/prepare_ip.py +++ b/src/finn/transformation/fpgadataflow/prepare_ip.py @@ -28,7 +28,7 @@ import os import finn.custom_op.registry as registry -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import make_build_dir from finn.util.fpgadataflow import is_fpgadataflow_node import warnings diff --git a/src/finn/transformation/fpgadataflow/prepare_rtlsim.py b/src/finn/transformation/fpgadataflow/prepare_rtlsim.py index 8c28ab7e2376de392b0fdd628c70e854011dc406..d2ec5561a349d5fc83f02870c1a682dba8433e43 100644 --- a/src/finn/transformation/fpgadataflow/prepare_rtlsim.py +++ b/src/finn/transformation/fpgadataflow/prepare_rtlsim.py @@ -31,7 +31,7 @@ from finn.util.fpgadataflow import is_fpgadataflow_node from finn.transformation.fpgadataflow.replace_verilog_relpaths import ( ReplaceVerilogRelPaths, ) -from finn.transformation import NodeLocalTransformation +from finn.transformation.base import NodeLocalTransformation try: from pyverilator import PyVerilator diff --git a/src/finn/transformation/fpgadataflow/replace_verilog_relpaths.py b/src/finn/transformation/fpgadataflow/replace_verilog_relpaths.py index e63ae4e0203188d9664f432f75e36994e8a71ac5..c577704129fa564f5e0e1e256623ff10125cf5ac 100644 --- a/src/finn/transformation/fpgadataflow/replace_verilog_relpaths.py +++ b/src/finn/transformation/fpgadataflow/replace_verilog_relpaths.py @@ -30,7 +30,7 @@ import os import finn.custom_op.registry as registry from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.transformation import Transformation +from finn.transformation.base import Transformation class ReplaceVerilogRelPaths(Transformation): diff --git a/src/finn/transformation/fpgadataflow/set_exec_mode.py b/src/finn/transformation/fpgadataflow/set_exec_mode.py index 40996e5f64fb812ea3766b71a9a8275514dec4a0..6a76031f4c76831f514b77aee6cd3c560b3b9910 100644 --- a/src/finn/transformation/fpgadataflow/set_exec_mode.py +++ b/src/finn/transformation/fpgadataflow/set_exec_mode.py @@ -28,7 +28,7 @@ import finn.custom_op.registry as registry from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.transformation import Transformation +from finn.transformation.base import Transformation class SetExecMode(Transformation): diff --git a/src/finn/transformation/fpgadataflow/synth_ooc.py b/src/finn/transformation/fpgadataflow/synth_ooc.py index 8fd7e4724ef7f255b1435d5ab5e680d155d39487..acc20e4ad0d0f4a17b20fd77625e9954f09e69aa 100644 --- a/src/finn/transformation/fpgadataflow/synth_ooc.py +++ b/src/finn/transformation/fpgadataflow/synth_ooc.py @@ -29,7 +29,7 @@ import os from shutil import copy2 -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.vivado import out_of_context_synth from finn.util.basic import make_build_dir diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index 2b3789dc21cb4fe3f62d6b2a2ea0888329c9db66..253fd462fa6601bcc34dd3d2aa2f42463c0ae308 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -214,7 +214,7 @@ class FINNAccelDriver(): if __name__ == "__main__": parser = argparse.ArgumentParser(description='Set exec mode, batchsize N, bitfile name, inputfile name and outputfile name') parser.add_argument('--exec_mode', help='Please select functional verification ("execute") or throughput test ("throughput_test")', default="execute") - parser.add_argument('--platform', help='Target platform: zynq-iodma alveo', default="zynq") + parser.add_argument('--platform', help='Target platform: zynq-iodma alveo', default="$PLATFORM$") parser.add_argument('--batchsize', help='number of samples for inference', type=int, default=1) parser.add_argument('--bitfile', help='name of bitfile (i.e. "resizer.bit")', default="resizer.bit") parser.add_argument('--inputfile', help='name of input npy file (i.e. "input.npy")', default="input.npy") diff --git a/src/finn/transformation/fpgadataflow/vitis_build.py b/src/finn/transformation/fpgadataflow/vitis_build.py index 482dc8d784c66faef9392093c7c857630304eef3..2e1f3fee2a3ba06f1feb192b9c21e6a5671ad48b 100644 --- a/src/finn/transformation/fpgadataflow/vitis_build.py +++ b/src/finn/transformation/fpgadataflow/vitis_build.py @@ -30,7 +30,7 @@ import os import subprocess from finn.core.modelwrapper import ModelWrapper -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.custom_op.registry import getCustomOp from finn.transformation.fpgadataflow.create_dataflow_partition import ( diff --git a/src/finn/transformation/general.py b/src/finn/transformation/general.py deleted file mode 100644 index 02f95b14e7944b828ae4c71ebf26851dc90b755d..0000000000000000000000000000000000000000 --- a/src/finn/transformation/general.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.util.basic as util -from finn.transformation import Transformation -from toposort import toposort_flatten - - -class RemoveUnusedTensors(Transformation): - """Remove any unused tensors in the graph by removing any initializers, - ValueInfo and tensor annotations associated with it. Unused tensors do not - appear as any input/output for any graph nodes. - """ - - def apply(self, model): - graph_modified = False - onnx_graph = model.model.graph - # build a set of tensors that we actually use in the graph nodes - used_tensors = set() - for node in model.graph.node: - for i in node.input: - used_tensors.add(i) - for o in node.output: - used_tensors.add(o) - # remove initializers, value_info and annotations that are not in the - # used set of tensors, as determined by the graph node i/o - for init in onnx_graph.initializer: - if init.name not in used_tensors: - onnx_graph.initializer.remove(init) - graph_modified = True - for vi in onnx_graph.value_info: - if vi.name not in used_tensors: - onnx_graph.value_info.remove(vi) - graph_modified = True - for qa in onnx_graph.quantization_annotation: - if qa.tensor_name not in used_tensors: - onnx_graph.quantization_annotation.remove(qa) - graph_modified = True - - return (model, graph_modified) - - -class RemoveStaticGraphInputs(Transformation): - "Remove any top-level graph inputs that have initializers." - - def apply(self, model): - graph_modified = False - for i in model.graph.input: - if model.get_initializer(i.name) is not None: - # move ValueInfo to internal (value_info) container - model.graph.value_info.append(i) - model.graph.input.remove(i) - graph_modified = True - - return (model, graph_modified) - - -class GiveUniqueNodeNames(Transformation): - """Give unique names to each node in the graph using enumeration, starting - with given prefix (if specified in the constructor).""" - - def __init__(self, prefix=""): - super().__init__() - self.prefix = prefix - - def apply(self, model): - optype_count = {} - for n in model.graph.node: - if n.op_type not in optype_count.keys(): - optype_count[n.op_type] = 0 - n.name = "%s%s_%d" % (self.prefix, n.op_type, optype_count[n.op_type]) - optype_count[n.op_type] += 1 - # return model_was_changed = False as single iteration is always enough - return (model, False) - - -class GiveRandomTensorNames(Transformation): - """Give random tensor names to all tensors.""" - - def apply(self, model): - names = model.get_all_tensor_names() - for name in names: - model.rename_tensor(name, util.random_string()) - # return model_was_changed = False as single iteration is always enough - return (model, False) - - -class GiveReadableTensorNames(Transformation): - """Give more human-readable names to all internal tensors. You should - apply GiveUniqueNodeNames prior to this transform to avoid empty node names, - as the readable names are based on the node names.""" - - def apply(self, model): - # to ensure we can use rename_tensor safely (without renaming existing - # tensors) we start by giving random names to all tensors - model = model.transform(GiveRandomTensorNames()) - graph = model.graph - for n in graph.node: - assert n.name != "", "Found empty node name" - out_num = 0 - for o in n.output: - model.rename_tensor(o, "%s_out%d" % (n.name, out_num)) - out_num += 1 - init_in_num = 0 - for i in n.input: - if model.get_initializer(i) is not None: - model.rename_tensor(i, "%s_param%d" % (n.name, init_in_num)) - init_in_num += 1 - # give special names to the main model input and output - model.rename_tensor(model.graph.input[0].name, "global_in") - model.rename_tensor(model.graph.output[0].name, "global_out") - # return model_was_changed = False as single iteration is always enough - return (model, False) - - -class GiveUniqueParameterTensors(Transformation): - """Make every parameter tensor unique. The aim is to avoid affecting - other nodes apart from the one the system is currently operating on.""" - - def apply(self, model): - graph = model.graph - graph_modified = False - seen_parameters = [] - for n in graph.node: - # copy inputs since they may be modified - node_inputs_list = [x for x in n.input] - for input_idx, node_input in enumerate(node_inputs_list): - # check if it's a parameter - input_init = model.get_initializer(node_input) - if input_init is None: - # dynamic input - continue - - # check if repeated - if node_input not in seen_parameters: - # first occurance - seen_parameters += [node_input] - continue - - new_param_name = model.make_new_valueinfo_name() - - model.set_initializer(new_param_name, input_init) - model.set_tensor_datatype( - new_param_name, model.get_tensor_datatype(node_input) - ) - - # point node input to new tensor - n.input[input_idx] = new_param_name - - return (model, graph_modified) - - -class SortGraph(Transformation): - """ Returns the model with its node list sorted topologically. - Any ONNX graph to be executed must have a topologically sorted node list, - as dictated by the ONNX standard. - """ - - # Notes on SortGraph performance: - # benchmark in tests/transformation/test_sort_graph.py - # The algorithm doesn't move initializers so its performance should only depend on - # the number of nodes - # - # Relative order of magnitudes for time per step: - # - Gather graph structure: base - # - Sort nodes: 0.1 of base - # - Remove and insert in order : 0.001 of base - # - # Notes: - # Remove nodes and insert them in order: - # Probably this is faster than copying initializers and more robust in general - - def apply(self, model): - if len(model.graph.node) == 1: - # single-node graph, nothing to sort - return (model, False) - # Gather graph structure - graph_dependencies = {} - node_list = [ - n for n in model.graph.node - ] # I also need the list to remove the nodes - for node_idx, n in enumerate(node_list): - node_pred = model.find_direct_predecessors(n) - if node_pred is None: - # Will also eliminate nodes that are floating around for some reason - continue - - node_dependencies = [node_list.index(pred) for pred in node_pred] - graph_dependencies[node_idx] = set(node_dependencies) - - # Sort nodes - sorted_node_indexes = toposort_flatten(graph_dependencies) - - # Remove nodes and insert them in order - # Can't remove nodes before if I want to use model.find_direct_predecessors() - for n in node_list: - model.graph.node.remove(n) - - for new_idx, sorted_idx in enumerate(sorted_node_indexes): - model.graph.node.insert(new_idx, node_list[sorted_idx]) - - return (model, False) - - -class ConvertSubToAdd(Transformation): - """Convert subtract-a-constant nodes to add-a-constant nodes.""" - - def apply(self, model): - graph = model.graph - for n in graph.node: - if n.op_type == "Sub": - A = model.get_initializer(n.input[1]) - if A is not None: - n.op_type = "Add" - model.set_initializer(n.input[1], -A) - # return model_was_changed = False as single iteration is always enough - return (model, False) - - -class ConvertDivToMul(Transformation): - """Convert divide by constant nodes to multiply by constant nodes.""" - - def apply(self, model): - graph = model.graph - for n in graph.node: - if n.op_type == "Div": - A = model.get_initializer(n.input[1]) - if A is not None: - n.op_type = "Mul" - model.set_initializer(n.input[1], 1.0 / A) - # return model_was_changed = False as single iteration is always enough - return (model, False) diff --git a/src/finn/transformation/infer_data_layouts.py b/src/finn/transformation/infer_data_layouts.py deleted file mode 100644 index d07162fa049bd016e91b8c5b01ea56eda6267655..0000000000000000000000000000000000000000 --- a/src/finn/transformation/infer_data_layouts.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.custom_op.registry as registry -import finn.core.data_layout as DataLayout -from finn.transformation import Transformation -import warnings -from finn.util.basic import get_by_name - - -def _dims_to_layout(model, node, ndims): - if ndims == 2: - return DataLayout.NC - else: - if node.domain == "finn": - if node.op_type == "MultiThreshold" or node.op_type == "QuantAvgPool2d": - mt_inst = registry.getCustomOp(node) - layout = mt_inst.get_nodeattr("data_layout") - if layout == "NHWC" and ndims == 4: - return DataLayout.NHWC - elif layout == "NCHW" and ndims == 4: - return DataLayout.NCHW - else: - return DataLayout.UNKNOWN - else: - if ndims == 4: - return DataLayout.NHWC - else: - return DataLayout.UNKNOWN - else: - # propagate input layout to output - # TODO this won't work for concat, squeeze/unsqueeze/reshape... - return model.get_tensor_layout(node.input[0]) - - -def _infer_node_data_layout(model, node): - """Infer output data layout annotation(s) for a particular node. - Returns True if any changes were made.""" - old_layouts = list(map(lambda x: model.get_tensor_layout(x), node.output)) - if node.domain == "finn": - # try to guess based on number of output dims - for o in node.output: - ndims = len(model.get_tensor_shape(o)) - new_layout = _dims_to_layout(model, node, ndims) - model.set_tensor_layout(o, new_layout) - else: - if node.op_type == "Transpose": - # grab input annotation and switch it around using perm - perm = get_by_name(node.attribute, "perm").ints - inp_layout = model.get_tensor_layout(node.input[0]) - out_layout = [inp_layout[i] for i in perm] - model.set_tensor_layout(node.output[0], out_layout) - elif node.op_type == "Unsqueeze": - inp_layout = model.get_tensor_layout(node.input[0]) - # add dummy dimension at the output - out_layout = inp_layout + ["x"] - model.set_tensor_layout(node.output[0], out_layout) - elif node.op_type == "Squeeze": - inp_layout = model.get_tensor_layout(node.input[0]) - assert inp_layout[-1] == "x" - # remove dummy dimension - out_layout = inp_layout[:-1] - model.set_tensor_layout(node.output[0], out_layout) - else: - # try to guess based on number of output dims - for o in node.output: - ndims = len(model.get_tensor_shape(o)) - model.set_tensor_layout(o, _dims_to_layout(model, node, ndims)) - # compare old and new output dtypes to see if anything changed - new_layouts = list(map(lambda x: model.get_tensor_layout(x), node.output)) - graph_modified = new_layouts != old_layouts - return graph_modified - - -class InferDataLayouts(Transformation): - """Try to infer data layout annotations info for all input/intermediate/output - tensors based on inputs and node type.""" - - def apply(self, model): - graph = model.graph - graph_modified = False - # first, make sure that the global input has an annotation - # this is really hard to do in general, so we do some bad guesswork - inp_name = graph.input[0].name - if model.get_tensor_layout(inp_name) is None: - inp_shape = model.get_tensor_shape(inp_name) - if len(inp_shape) == 4: - warnings.warn("Assuming 4D input is NCHW") - model.set_tensor_layout(inp_name, DataLayout.NCHW) - graph_modified = True - elif len(inp_shape) == 2: - graph_modified = True - warnings.warn("Assuming 2D input is NC") - model.set_tensor_layout(inp_name, DataLayout.NC) - else: - raise Exception( - """Unknown number of dims for input, don't know - how to annotate""" - ) - for node in graph.node: - graph_modified |= _infer_node_data_layout(model, node) - return (model, graph_modified) diff --git a/src/finn/transformation/infer_datatypes.py b/src/finn/transformation/infer_datatypes.py deleted file mode 100644 index 39b7a787be8c725e7b6d474757dd96fc4848dfe0..0000000000000000000000000000000000000000 --- a/src/finn/transformation/infer_datatypes.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.custom_op.registry as registry -from finn.core.datatype import DataType -from finn.transformation import Transformation - - -def _infer_node_datatype(model, node): - """Infer output datatype(s) for a particular node. Returns True if any - changes were made.""" - dt_identity_optypes = ["Reshape", "Transpose"] - idtypes = list(map(lambda x: model.get_tensor_datatype(x), node.input)) - odtypes = list(map(lambda x: model.get_tensor_datatype(x), node.output)) - op_type = node.op_type - if node.domain == "finn": - # handle DataType inference for CustomOp - try: - # lookup op_type in registry of CustomOps - inst = registry.custom_op[op_type](node) - inst.infer_node_datatype(model) - except KeyError: - # exception if op_type is not supported - raise Exception("Custom op_type %s is currently not supported." % op_type) - else: - if node.op_type == "Sign": - # always produces bipolar outputs - model.set_tensor_datatype(node.output[0], DataType.BIPOLAR) - elif node.op_type == "MatMul": - if len(list(filter(lambda x: x == DataType.FLOAT32, idtypes))) != 0: - # node has at least one float input, output is also float - model.set_tensor_datatype(node.output[0], DataType.FLOAT32) - else: - # TODO compute minimum / maximum result to minimize bitwidth - # use (u)int32 accumulators for now - has_signed_inp = len(list(filter(lambda x: x.signed(), idtypes))) != 0 - if has_signed_inp: - odtype = DataType.INT32 - else: - odtype = DataType.UINT32 - model.set_tensor_datatype(node.output[0], odtype) - elif node.op_type in dt_identity_optypes: - # set output dtype = input dtype - idtype = model.get_tensor_datatype(node.input[0]) - model.set_tensor_datatype(node.output[0], idtype) - else: - # unknown, assume node produces float32 outputs - for o in node.output: - # check if output datatype is already set to a value != FLOAT32 - odtype = model.get_tensor_datatype(o) - if odtype is not None and odtype != DataType.FLOAT32: - # don't change data type - model.set_tensor_datatype(o, odtype) - else: - model.set_tensor_datatype(o, DataType.FLOAT32) - # compare old and new output dtypes to see if anything changed - new_odtypes = list(map(lambda x: model.get_tensor_datatype(x), node.output)) - graph_modified = new_odtypes != odtypes - return graph_modified - - -class InferDataTypes(Transformation): - """Infer FINN DataType info for all intermediate/output tensors based on - inputs and node type.""" - - def apply(self, model): - graph = model.graph - graph_modified = False - for node in graph.node: - graph_modified |= _infer_node_datatype(model, node) - return (model, graph_modified) diff --git a/src/finn/transformation/infer_shapes.py b/src/finn/transformation/infer_shapes.py deleted file mode 100644 index 361ef7f6ad46c23faa48d50b34d78e38d0823796..0000000000000000000000000000000000000000 --- a/src/finn/transformation/infer_shapes.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx.shape_inference as si - -import finn.custom_op.registry as registry -from finn.core.modelwrapper import ModelWrapper -from finn.transformation import Transformation - - -def _make_shape_compatible_op(node, model): - """Return a shape-compatible non-FINN op for a given FINN op. Used for - shape inference with custom ops.""" - assert node.domain == "finn", 'Node domain is not set to "finn".' - op_type = node.op_type - try: - # lookup op_type in registry of CustomOps - inst = registry.custom_op[op_type](node) - return inst.make_shape_compatible_op(model) - except KeyError: - # exception if op_type is not supported - raise Exception("Custom op_type %s is currently not supported." % op_type) - - -def _hide_finn_ops(model): - """Replace any FINN ops by shape-compatible ones, and return a dict that - can be used to map the string representations of the new (shape-compatible) - ops back to the old ops.""" - hidden_ops = {} - node_ind = 0 - for node in model.graph.node: - node_ind += 1 - if node.domain == "finn": - new_node = _make_shape_compatible_op(node, model) - hidden_ops[str(new_node)] = node - model.graph.node.insert(node_ind, new_node) - model.graph.node.remove(node) - return hidden_ops - - -def _restore_finn_ops(model, hidden_ops): - """Replace any shape-compatible ops with the FINN ops that originally - generated them.""" - node_ind = 0 - for node in model.graph.node: - node_ind += 1 - try: - old_node = hidden_ops[str(node)] - model.graph.node.insert(node_ind, old_node) - model.graph.node.remove(node) - except KeyError: - pass - - -class InferShapes(Transformation): - """Ensure every tensor in the model has a specified shape (ValueInfo).""" - - def apply(self, model): - # hide your riches! - hidden_ops = _hide_finn_ops(model) - # call regular ONNX shape inference - model = ModelWrapper(si.infer_shapes(model.model)) - # bring back hidden ops - _restore_finn_ops(model, hidden_ops) - return (model, False) diff --git a/src/finn/transformation/insert_topk.py b/src/finn/transformation/insert_topk.py deleted file mode 100644 index 213d2cedf92c0276e33fcf2b50e6966aeee8c847..0000000000000000000000000000000000000000 --- a/src/finn/transformation/insert_topk.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx import TensorProto -from onnx import helper as oh - -from finn.transformation import Transformation -from finn.core.datatype import DataType - - -class InsertTopK(Transformation): - """Add TopK node at the network output and replace the graph output with - the TopK indices.""" - - def __init__(self, k=5, axis=-1, largest=1, sorted=1): - super().__init__() - self.k = k - self.axis = axis - self.largest = largest - self.sorted = sorted - - def apply(self, model): - # get name of output tensor - graph_out_name = model.graph.output[0].name - # find final node - final_node = model.find_producer(graph_out_name) - # if a top-select op is already present, do nothing - if final_node.op_type == "TopK": - return (model, False) - else: - out_shape = model.get_tensor_shape(graph_out_name) - out_dtype = model.get_tensor_datatype(graph_out_name) - # adjust shape - out_shape[self.axis] = self.k - # make new buffer - k_tensor = np.array([self.k]).astype(np.int64) - k_value = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.INT64, [1] - ) - topk_values = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.FLOAT, out_shape - ) - topk_indices = oh.make_tensor_value_info( - model.make_new_valueinfo_name(), TensorProto.INT64, out_shape - ) - model.graph.value_info.append(k_value) - model.set_tensor_datatype(k_value.name, out_dtype) # TODO set to int64 - model.graph.value_info.append(topk_values) - model.set_tensor_datatype(topk_values.name, out_dtype) - # create and append topk node - model.set_initializer(k_value.name, k_tensor) - topk_node = oh.make_node( - "TopK", - inputs=[graph_out_name, k_value.name], - outputs=[topk_values.name, topk_indices.name], - axis=self.axis, - largest=self.largest, - sorted=self.sorted, - ) - model.graph.node.append(topk_node) - # replace the existing output definition with topk indices - model.graph.output.insert(0, topk_indices) - model.graph.output.pop(1) - # set quantization annotation for indices - # minimal output dtype for TopK indices dependens on num. classes - # assuming UINT32 is large enough for now (FINN has currently no - # DataType.INT64) - model.set_tensor_datatype(topk_indices.name, DataType.UINT32) - return (model, True) diff --git a/src/finn/transformation/lower_convs_to_matmul.py b/src/finn/transformation/lower_convs_to_matmul.py deleted file mode 100644 index e5a1f778d0cac48925ecd97ae8b970f7bdab9c4f..0000000000000000000000000000000000000000 --- a/src/finn/transformation/lower_convs_to_matmul.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx import TensorProto -from onnx import helper - -from finn.transformation import Transformation -from finn.transformation.infer_shapes import InferShapes -from finn.util.basic import get_by_name - - -class LowerConvsToMatMul(Transformation): - """Replace Conv layers with pairs of Im2Col-MatMul layers, plus Transpose - layers to keep the original data layout.""" - - 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 == "Conv": - graph_modified = True - cnv_input = n.input[0] - cnv_output = n.output[0] - idt = model.get_tensor_datatype(cnv_input) - odt = model.get_tensor_datatype(cnv_output) - # extract conv parameters - k = get_by_name(n.attribute, "kernel_shape").ints[-1] - pad = get_by_name(n.attribute, "pads").ints[-1] - stride = get_by_name(n.attribute, "strides").ints[-1] - group = get_by_name(n.attribute, "group").i - weight_name = n.input[1] - W_conv = model.get_initializer(weight_name) - ifm_ch = model.get_tensor_shape(n.input[0])[1] # assume NCHW - ofm_ch = model.get_tensor_shape(n.output[0])[1] # assume NCHW - ifm_dim = model.get_tensor_shape(n.input[0])[-1] # assume NCHW - ofm_dim = model.get_tensor_shape(n.output[0])[-1] # assume NCHW - - # if depthwise conv create sparse matrix and variable "dw" - # to store as attribute in Im2Col that indicates that the created - # Im2Col node belongs to a depthwise convolution - dw = False - if group == ifm_ch and ofm_ch == ifm_ch: - W_sparse = np.zeros((ofm_ch, ifm_ch, k, k)) - for ch in range(ifm_ch): - W_sparse[ch][ch] = W_conv[ch][0] - W_conv = W_sparse.astype(np.float32) - # we need to store information of the - # sparsity of the weight matrix. For this - # we use the sparsity annotation of the - # weight tensor - sparsity = {"dw": {"kernel_shape": k}} - model.set_tensor_sparsity(weight_name, sparsity) - # additionally create variable "dw" to store - # as attribute in Im2Col that indicates that the created - # Im2Col node belongs to a depthwise convolution - dw = True - - # reuse conv weights for new matmul weights - # conv weights are [OFM][IFM][k][k] - # first convert to [OFM][k][k][IFM] (to remain compatible with - # finn-hlslib and how it does im2col/sliding window) - W_matmul = W_conv.transpose(0, 2, 3, 1) - # reshape into [OFM][k*k*IFM] matrix - W_matmul = W_matmul.reshape(ofm_ch, ifm_ch * k * k) - # transpose to get ONNX-compatible [k*k*IFM][OFM] matrix - W_matmul = W_matmul.T - model.set_initializer(weight_name, W_matmul) - - # create new intermediate values - inp_trans_out = helper.make_tensor_value_info( - model.make_new_valueinfo_name(), - TensorProto.FLOAT, - (1, ifm_dim, ifm_dim, ifm_ch), # NHWC - ) - graph.value_info.append(inp_trans_out) - inp_trans_out = inp_trans_out.name - model.set_tensor_datatype(inp_trans_out, idt) - - need_im2col = True - if k == 1 and pad == 0 and stride == 1: - need_im2col = False - - if need_im2col: - im2col_out = helper.make_tensor_value_info( - model.make_new_valueinfo_name(), - TensorProto.FLOAT, - (1, ofm_dim, ofm_dim, ifm_ch * k * k), - ) - graph.value_info.append(im2col_out) - im2col_out = im2col_out.name - model.set_tensor_datatype(im2col_out, idt) - - matmul_out = helper.make_tensor_value_info( - model.make_new_valueinfo_name(), - TensorProto.FLOAT, - (1, ofm_dim, ofm_dim, ofm_ch), - ) - graph.value_info.append(matmul_out) - matmul_out = matmul_out.name - model.set_tensor_datatype(matmul_out, odt) - - # create new nodes - # NCHW -> NHWC - inp_trans_node = helper.make_node( - "Transpose", [cnv_input], [inp_trans_out], perm=[0, 2, 3, 1] - ) - # lower input tensor - matmul_input = inp_trans_out - if need_im2col: - matmul_input = im2col_out - im2col_node = helper.make_node( - "Im2Col", - [inp_trans_out], - [im2col_out], - domain="finn", - stride=stride, - kernel_size=k, - pad_amount=pad, - input_shape="(1,{},{},{})".format(ifm_dim, ifm_dim, ifm_ch), - depthwise=dw, - ) - - # do matmul - matmul_node = helper.make_node( - "MatMul", [matmul_input, weight_name], [matmul_out] - ) - # NHWC -> NCHW - out_trans_node = helper.make_node( - "Transpose", [matmul_out], [cnv_output], perm=[0, 3, 1, 2] - ) - # insert nodes where the conv is to preserve topological ordering - graph.node.insert(node_ind, inp_trans_node) - if need_im2col: - graph.node.insert(node_ind + 1, im2col_node) - graph.node.insert(node_ind + 2, matmul_node) - graph.node.insert(node_ind + 3, out_trans_node) - else: - graph.node.insert(node_ind + 1, matmul_node) - graph.node.insert(node_ind + 2, out_trans_node) - # remove old nodes - graph.node.remove(n) - model = model.transform(InferShapes()) - return (model, graph_modified) diff --git a/src/finn/transformation/merge_onnx_models.py b/src/finn/transformation/merge_onnx_models.py deleted file mode 100644 index 2a84910832fc55b9383b2d731c05c53f42368631..0000000000000000000000000000000000000000 --- a/src/finn/transformation/merge_onnx_models.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 copy -import warnings -from onnx import helper - -from finn.transformation import Transformation -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.infer_datatypes import InferDataTypes -from finn.transformation.infer_data_layouts import InferDataLayouts -from finn.transformation.general import ( - GiveReadableTensorNames, - GiveRandomTensorNames, - GiveUniqueNodeNames, - GiveUniqueParameterTensors, -) - - -class MergeONNXModels(Transformation): - """Merges two models. The model passed in the transformation will be inserted before - the model the transformation is applied on, the resulting model is returned. - This transformation will try to connect graph.output[0] of the pre model and - graph.input[0] of the post model. - If more than one input or output exists, a warning is raised.""" - - def __init__(self, pre_model): - super().__init__() - # use deep copy of model that should be inserted in the beginning of - # the other model to ensure that it stays unchanged - self.pre_model = copy.deepcopy(pre_model) - - def apply(self, model): - graph_modified = False - pre_model = self.pre_model - post_model = copy.deepcopy(model) - # to avoid mix-ups, start by giving all tensors random names - pre_model = pre_model.transform(GiveRandomTensorNames()) - post_model = post_model.transform(GiveRandomTensorNames()) - - # check for dynamic outputs of pre model - dyn_outp = [] - for outp in pre_model.graph.output: - init_val = pre_model.get_initializer(outp.name) - if init_val is None: - dyn_outp.append(outp) - - if len(dyn_outp) != 1: - warnings.warn( - "The pre model has more than one dynamic output! The transformation " - "tries to connect the first dynamic output to the first dynamic input " - "of the post model." - ) - - # check for dynamic inputs of post model - dyn_inp = [] - for inp in post_model.graph.input: - init_val = post_model.get_initializer(inp.name) - if init_val is None: - dyn_inp.append(inp) - - if len(dyn_inp) != 1: - warnings.warn( - "The post model has more than one dynamic input! The transformation " - "tries to connect the first dynamic input to the first dynamic output " - "of the pre model." - ) - - # erase all node names to avoid conflict - for n in pre_model.graph.node: - n.name = "" - for n in post_model.graph.node: - n.name = "" - - # check if models can be merged - output_model_a = dyn_outp[0].name - input_model_b = dyn_inp[0].name - output_a_shape = pre_model.get_tensor_shape(output_model_a) - input_b_shape = post_model.get_tensor_shape(input_model_b) - assert ( - output_a_shape == input_b_shape - ), "Models can't be merged! Shapes don't match." - - # connect output of one model to input of the other - for n in pre_model.graph.node: - if output_model_a == n.output[0]: - n.output[0] = input_model_b - - # extract information for new model - - # nodes - node_pre = [node for node in pre_model.graph.node] - node_post = [node for node in post_model.graph.node] - node_new = node_pre + node_post - - # in and output - inp = pre_model.graph.input[0] - outp = post_model.graph.output[0] - - vi_pre = [x for x in pre_model.graph.value_info] - out_pre = [x for x in pre_model.graph.output] - qa_pre = [x for x in pre_model.graph.quantization_annotation] - init_pre = [x for x in pre_model.graph.initializer] - - vi_post = [x for x in post_model.graph.value_info] - qa_post = [x for x in post_model.graph.quantization_annotation] - init_post = [x for x in post_model.graph.initializer] - - vi_new = vi_pre + vi_post + out_pre - qa_new = qa_pre + qa_post - init_new = init_pre + init_post - - # create new graph and model - new_graph = helper.make_graph( - nodes=node_new, - name="fuse-graph", - inputs=[inp], - outputs=[outp], - value_info=vi_new, - ) - - new_model = helper.make_model(new_graph, producer_name="fuse_model") - new_model = ModelWrapper(new_model) - - for i in init_new: - new_model.graph.initializer.append(i) - for qa in qa_new: - new_model.graph.quantization_annotation.append(qa) - - # tidy-up new model - model = new_model - model = model.transform(InferShapes()) - model = model.transform(InferDataTypes()) - model = model.transform(InferDataLayouts()) - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveUniqueParameterTensors()) - model = model.transform(GiveReadableTensorNames()) - - return (model, graph_modified) diff --git a/src/finn/transformation/move_reshape.py b/src/finn/transformation/move_reshape.py index 9943d371dad79a977b61810bcddafdcba505d6cc..a07eaf142293487237b3f2b93460ba492eb5368d 100644 --- a/src/finn/transformation/move_reshape.py +++ b/src/finn/transformation/move_reshape.py @@ -1,4 +1,4 @@ -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_by_name diff --git a/src/finn/transformation/streamline/__init__.py b/src/finn/transformation/streamline/__init__.py index d7686eaadcbc800542ab96c5f45145857412b773..e78b798ff6f31ff705b733d47dcfe7bcdc6aa127 100644 --- a/src/finn/transformation/streamline/__init__.py +++ b/src/finn/transformation/streamline/__init__.py @@ -25,8 +25,12 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# namespace package, extend path +from pkgutil import extend_path -from finn.transformation import Transformation +__path__ = extend_path(__path__, __name__) + +from finn.transformation.base import Transformation from finn.transformation.infer_datatypes import InferDataTypes from finn.transformation.general import ( ConvertSubToAdd, diff --git a/src/finn/transformation/streamline/absorb.py b/src/finn/transformation/streamline/absorb.py index 0f2c5525d91263b44002677b505087d38408333a..fa2d7a714ad894ebb19099c7ed73e42e12ffdf44 100644 --- a/src/finn/transformation/streamline/absorb.py +++ b/src/finn/transformation/streamline/absorb.py @@ -32,7 +32,7 @@ import warnings from finn.core.datatype import DataType import finn.core.data_layout as DataLayout -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.util.basic import get_by_name from finn.custom_op.registry import getCustomOp from finn.transformation.infer_shapes import InferShapes diff --git a/src/finn/transformation/streamline/collapse_repeated.py b/src/finn/transformation/streamline/collapse_repeated.py index 769bed841ce07c1c9c62f762de4b2c0937a6d68f..19f1ec3e836f3ec38aa4b716f5d6a25ca0782197 100644 --- a/src/finn/transformation/streamline/collapse_repeated.py +++ b/src/finn/transformation/streamline/collapse_repeated.py @@ -28,7 +28,7 @@ from onnx import helper as oh -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.transformation.infer_shapes import InferShapes from finn.core.datatype import DataType diff --git a/src/finn/transformation/streamline/remove.py b/src/finn/transformation/streamline/remove.py index ddc4233ddafbc70c4d20d316ea72ea6bba1b82a8..12c6984c6e66e1917d2a1e0a74c8620ccb6afabc 100644 --- a/src/finn/transformation/streamline/remove.py +++ b/src/finn/transformation/streamline/remove.py @@ -27,10 +27,11 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from finn.transformation import Transformation +from finn.transformation.base import Transformation from finn.transformation.infer_shapes import InferShapes import numpy as np + class RemoveIdentityOps(Transformation): """Remove identity ops like Add/Sub with zero or Mul/Div with one""" diff --git a/src/finn/transformation/streamline/reorder.py b/src/finn/transformation/streamline/reorder.py index f4c1dc1306b67e5807c25cfb08c961729dbfbdf6..bae3c9f22f4e5b2a525f15d1d948e42a4087953a 100644 --- a/src/finn/transformation/streamline/reorder.py +++ b/src/finn/transformation/streamline/reorder.py @@ -31,7 +31,7 @@ import warnings from onnx import helper as oh from onnx import TensorProto -from finn.transformation import Transformation +from finn.transformation.base import Transformation import finn.core.data_layout as DataLayout from finn.transformation.infer_shapes import InferShapes from finn.transformation.infer_datatypes import InferDataTypes diff --git a/src/finn/transformation/streamline/round_thresholds.py b/src/finn/transformation/streamline/round_thresholds.py index 8626ef40619b067c6672c9017ddcb747998c3f2c..ba476504a4213a7d004113c39e2285beeecdddec 100644 --- a/src/finn/transformation/streamline/round_thresholds.py +++ b/src/finn/transformation/streamline/round_thresholds.py @@ -28,7 +28,7 @@ import numpy as np -from finn.transformation import Transformation +from finn.transformation.base import Transformation class RoundAndClipThresholds(Transformation): diff --git a/src/finn/transformation/streamline/sign_to_thres.py b/src/finn/transformation/streamline/sign_to_thres.py index d2b51df7a43c830516897e6bf7d2210698269da8..4e35012ceb4f84284ff2a96a60e4a9bd58a65cce 100644 --- a/src/finn/transformation/streamline/sign_to_thres.py +++ b/src/finn/transformation/streamline/sign_to_thres.py @@ -30,7 +30,7 @@ import numpy as np from onnx import helper as oh from finn.core.datatype import DataType -from finn.transformation import Transformation +from finn.transformation.base import Transformation class ConvertSignToThres(Transformation): diff --git a/src/finn/util/__init__.py b/src/finn/util/__init__.py deleted file mode 100644 index 83c8e8bed70797f7d6c0138968f750f72e790386..0000000000000000000000000000000000000000 --- a/src/finn/util/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/finn/util/basic.py b/src/finn/util/basic.py deleted file mode 100644 index cc759bebb1b856a84e25978d442e460332092d23..0000000000000000000000000000000000000000 --- a/src/finn/util/basic.py +++ /dev/null @@ -1,442 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -import random -import string -import subprocess -import tempfile -import warnings - -import numpy as np - -from finn.core.datatype import DataType - -# mapping from PYNQ board names to FPGA part names -pynq_part_map = dict() -pynq_part_map["Ultra96"] = "xczu3eg-sbva484-1-e" -pynq_part_map["Pynq-Z1"] = "xc7z020clg400-1" -pynq_part_map["Pynq-Z2"] = "xc7z020clg400-1" -pynq_part_map["ZCU102"] = "xczu9eg-ffvb1156-2-e" -pynq_part_map["ZCU104"] = "xczu7ev-ffvc1156-2-e" - -# native AXI HP port width (in bits) for PYNQ boards -pynq_native_port_width = dict() -pynq_native_port_width["Pynq-Z1"] = 64 -pynq_native_port_width["Pynq-Z2"] = 64 -pynq_native_port_width["Ultra96"] = 128 -pynq_native_port_width["ZCU102"] = 128 -pynq_native_port_width["ZCU104"] = 128 - -# Alveo device and platform mappings -alveo_part_map = dict() -alveo_part_map["U50"] = "xcu50-fsvh2104-2L-e" -alveo_part_map["U200"] = "xcu200-fsgd2104-2-e" -alveo_part_map["U250"] = "xcu250-figd2104-2L-e" -alveo_part_map["U280"] = "xcu280-fsvh2892-2L-e" - -alveo_default_platform = dict() -alveo_default_platform["U50"] = "xilinx_u50_gen3x16_xdma_201920_3" -alveo_default_platform["U200"] = "xilinx_u200_xdma_201830_2" -alveo_default_platform["U250"] = "xilinx_u250_xdma_201830_2" -alveo_default_platform["U280"] = "xilinx_u280_xdma_201920_3" - - -def get_rtlsim_trace_depth(): - """Return the trace depth for rtlsim via PyVerilator. Controllable - via the RTLSIM_TRACE_DEPTH environment variable. If the env.var. is - undefined, the default value of 1 is returned. A trace depth of 1 - will only show top-level signals and yield smaller .vcd files. - - The following depth values are of interest for whole-network stitched IP - rtlsim: - - level 1 shows top-level input/output streams - - level 2 shows per-layer input/output streams - - level 3 shows per full-layer I/O including FIFO count signals - """ - - try: - return int(os.environ["RTLSIM_TRACE_DEPTH"]) - except KeyError: - return 1 - - -def get_remote_vivado(): - """Return the address of the remote Vivado synthesis server as set by the, - REMOTE_VIVADO environment variable, otherwise return None""" - - try: - return os.environ["REMOTE_VIVADO"] - except KeyError: - return None - - -def get_num_default_workers(): - """Return the number of workers for parallel transformations. Controllable - via the NUM_DEFAULT_WORKERS environment variable. If the env.var. is - undefined, the default value of 1 is returned. - """ - - try: - return int(os.environ["NUM_DEFAULT_WORKERS"]) - except KeyError: - return 1 - - -def get_finn_root(): - "Return the root directory that FINN is cloned into." - - try: - return os.environ["FINN_ROOT"] - except KeyError: - raise Exception( - """Environment variable FINN_ROOT must be set - correctly. Please ensure you have launched the Docker contaier correctly. - """ - ) - - -def get_execution_error_thresh(): - "Return the max error that is allowed for rounding in FINN execution." - try: - return float(os.environ["ERROR_THRESH"]) - except KeyError: - return 1e-2 - - -def get_sanitize_quant_tensors(): - """Return whether tensors with quantization annotations should be sanitized. - Enabled by default, disabling will yield faster ONNX execution but may give - incorrect results. Use with caution.""" - try: - return int(os.environ["SANITIZE_QUANT_TENSORS"]) - except KeyError: - # enabled by default - return 1 - - -def make_build_dir(prefix=""): - """Creates a temporary folder with given prefix to be used as a build dir. - Use this function instead of tempfile.mkdtemp to ensure any generated files - will survive on the host after the FINN Docker container exits.""" - try: - inst_prefix = os.environ["FINN_INST_NAME"] + "/" - return tempfile.mkdtemp(prefix=inst_prefix + prefix) - except KeyError: - raise Exception( - """Environment variable FINN_INST_NAME must be set - correctly. Please ensure you have launched the Docker contaier correctly. - """ - ) - - -def get_by_name(container, name, name_field="name"): - """Return item from container by .name field if it exists, None otherwise. - Will throw an Exception if multiple items are found, since this violates the - ONNX standard.""" - names = [getattr(x, name_field) for x in container] - - inds = [i for i, e in enumerate(names) if e == name] - if len(inds) > 1: - raise Exception("Found multiple get_by_name matches, undefined behavior") - elif len(inds) == 0: - return None - else: - ind = inds[0] - return container[ind] - - -def remove_by_name(container, name, name_field="name"): - """Remove item from container by .name field if it exists.""" - item = get_by_name(container, name, name_field) - if item is not None: - container.remove(item) - - -def random_string(stringLength=6): - """Randomly generate a string of letters and digits.""" - lettersAndDigits = string.ascii_letters + string.digits - return "".join(random.choice(lettersAndDigits) for i in range(stringLength)) - - -def interleave_matrix_outer_dim_from_partitions(matrix, n_partitions): - """Interleave the outermost dimension of a matrix from given - partitions (n_partitions).""" - if type(matrix) != np.ndarray or matrix.dtype != np.float32: - # try to convert to a float numpy array (container dtype is float) - matrix = np.asarray(matrix, dtype=np.float32) - shp = matrix.shape - ndim = matrix.ndim - # ensure # partitions evenly divide the outermost dimension - assert ( - shp[0] % n_partitions == 0 - ), """The outermost dimension is not divisable - by the number of partitions.""" - # only tested for matrices - assert ( - ndim == 2 - ), """The dimension of the matrix is not 2. Currently this function - only works for matrices.""" - # interleave rows between PEs using reshape + transpose - matrix_r = matrix.reshape(-1, n_partitions, shp[1]).transpose((1, 0, 2)) - matrix_r = matrix_r.reshape(n_partitions, -1, shp[1]) - return matrix_r - - -def roundup_to_integer_multiple(x, factor): - """Round up integer x to the nearest integer multiple of integer factor. - Returns x if factor is set to -1. Both x and factor must otherwise be - positive.""" - # ensure integers - assert int(x) == x, "The input x is not an integer." - assert int(factor) == factor, "The input factor is not an integer." - # use -1 to indicate no padding needed - if factor == -1: - return x - # ensure positive values - assert factor > 0 and x > 0, "Factor and x are <= 0." - if x < factor: - return factor - else: - if x % factor == 0: - return x - else: - return x + (factor - (x % factor)) - - -def pad_tensor_to_multiple_of(ndarray, pad_to_dims, val=0, distr_pad=False): - """Pad each dimension of given NumPy ndarray using val, so that each - dimension is a multiple of the respective value in pad_to_dims. -1 means - do not pad that particular dimension. If distr_pad is False, all padding - will be inserted after the existing values; otherwise it will be split - evenly between before and after the existing values, with one extra value - inserted after if the padding amount is not divisible by two.""" - if type(ndarray) != np.ndarray or ndarray.dtype != np.float32: - # try to convert to a float numpy array (container dtype is float) - ndarray = np.asarray(ndarray, dtype=np.float32) - assert ndarray.ndim == len( - pad_to_dims - ), """The dimensions of the input - array don't match the length of the pad_to_dims value.""" - # compute the desired shape - desired = zip(list(ndarray.shape), list(pad_to_dims)) - desired = map(lambda x: roundup_to_integer_multiple(x[0], x[1]), desired) - desired = np.asarray(list(desired), dtype=np.int32) - current = np.asarray(ndarray.shape, dtype=np.int32) - pad_amt = desired - current - # add padding to get to the desired shape - if distr_pad: - pad_before = (pad_amt // 2).astype(np.int32) - pad_after = pad_amt - pad_before - pad_amt = list(zip(pad_before, pad_after)) - else: - # all padding is added after the existing values - pad_amt = list(map(lambda x: (0, x), pad_amt)) - ret = np.pad(ndarray, pad_amt, mode="constant", constant_values=val) - assert ( - np.asarray(ret.shape, dtype=np.int32) == desired - ).all(), """The - calculated output array doesn't match the desired/expected one.""" - return ret - - -def calculate_matvec_accumulator_range(matrix, vec_dt): - """Calculate the minimum and maximum possible result (accumulator) values - for a dot product x * A, given matrix A of dims (MW, MH), and vector (1, MW) - with datatype vec_dt. Returns (acc_min, acc_max). - """ - min_weight = matrix.min() - max_weight = matrix.max() - perceptive_field_elems = matrix.shape[0] - min_input = vec_dt.min() - max_input = vec_dt.max() - # calculate minimum and maximum values of accumulator - # assume inputs span the whole range of the input datatype - acc_min = perceptive_field_elems * min( - min_weight * max_input, - min_weight * min_input, - max_weight * max_input, - max_weight * min_input, - ) - acc_max = perceptive_field_elems * max( - min_weight * max_input, - min_weight * min_input, - max_weight * max_input, - max_weight * min_input, - ) - return (acc_min, acc_max) - - -def gen_finn_dt_tensor(finn_dt, tensor_shape): - """Generates random tensor in given shape and with given FINN DataType.""" - if type(tensor_shape) == list: - tensor_shape = tuple(tensor_shape) - if finn_dt == DataType.BIPOLAR: - tensor_values = np.random.randint(2, size=tensor_shape) - tensor_values = 2 * tensor_values - 1 - elif finn_dt == DataType.BINARY: - tensor_values = np.random.randint(2, size=tensor_shape) - elif "INT" in finn_dt.name or finn_dt == DataType.TERNARY: - tensor_values = np.random.randint( - finn_dt.min(), high=finn_dt.max() + 1, size=tensor_shape - ) - else: - raise ValueError( - "Datatype {} is not supported, no tensor could be generated".format(finn_dt) - ) - # always use float type as container - return tensor_values.astype(np.float32) - - -def calculate_signed_dot_prod_range(dt_a, dt_b, len): - """Returns the (min,max) values a dot product between two signed vectors of - types dt_a and dt_b of len elements can take.""" - assert ( - dt_a.signed() and dt_b.signed() - ), """The input values are not both - signed vectors.""" - min_prod = 2 ** 30 - max_prod = -(2 ** 30) - for a_val in [dt_a.min(), dt_a.max()]: - for b_val in [dt_b.min(), dt_b.max()]: - prod = a_val * b_val * len - if prod < min_prod: - min_prod = prod - if prod > max_prod: - max_prod = prod - return (min_prod, max_prod) - - -def sanitize_quant_values(model, node_tensors, execution_context, check_values=False): - """ Sanitize given list of tensors in execution_context by rounding values - that are supposed to be integers (as indicated by their quantization - annotation). Will raise an assertion if the amount of rounding is too large. - Returns the sanitized execution context. - - If check_values is specified, an extra DataType.allowed() check will be - performed on any rounded tensors. - - Background: - FINN uses floating point tensors as a carrier data type to represent - integers. Floating point arithmetic can introduce rounding errors, e.g. - (int_num * float_scale) / float_scale is not always equal to int_num. - We use this function to ensure that the values that are supposed to be - integers are indeed integers. - """ - - for tensor in node_tensors: - dtype = model.get_tensor_datatype(tensor) - # floats don't need sanitization, skip to next - # introduces less quicker runtime - if dtype == DataType.FLOAT32: - continue - current_values = execution_context[tensor] - updated_values = current_values - has_to_be_rounded = False - # TODO: vectorize with numpy - for value in np.nditer(current_values): - if not dtype.allowed(value): - has_to_be_rounded = True - break - if has_to_be_rounded: - updated_values = np.round(current_values) - warnings.warn( - "The values of tensor {} can't be represented " - "with the set FINN datatype ({}), they will be rounded to match the " - "FINN datatype.".format(tensor, dtype) - ) - # check if rounded values are not too far from original values - max_error = max(np.abs(current_values - updated_values).flatten()) - if max_error <= get_execution_error_thresh(): - if check_values is True: - # check again if values can now be represented with set finn datatype - # TODO: vectorize with numpy - for value in np.nditer(updated_values): - if not dtype.allowed(value): - raise Exception( - """Values can't be represented with set - finn datatype ({}) for input {}""".format( - dtype, tensor - ) - ) - execution_context[tensor] = updated_values - else: - raise Exception( - """Rounding error is too high to match set FINN - datatype ({}) for input {}""".format( - dtype, tensor - ) - ) - return execution_context - - -class CppBuilder: - """Builds the g++ compiler command to produces the executable of the c++ code - in code_gen_dir which is passed to the function build() of this class.""" - - def __init__(self): - self.include_paths = [] - self.cpp_files = [] - self.executable_path = "" - self.code_gen_dir = "" - self.compile_components = [] - self.compile_script = "" - - def append_includes(self, library_path): - """Adds given library path to include_paths list.""" - self.include_paths.append(library_path) - - def append_sources(self, cpp_file): - """Adds given c++ file to cpp_files list.""" - self.cpp_files.append(cpp_file) - - def set_executable_path(self, path): - """Sets member variable "executable_path" to given path.""" - self.executable_path = path - - def build(self, code_gen_dir): - """Builds the g++ compiler command according to entries in include_paths - and cpp_files lists. Saves it in bash script in given folder and - executes it.""" - # raise error if includes are empty - self.code_gen_dir = code_gen_dir - self.compile_components.append("g++ -o " + str(self.executable_path)) - for cpp_file in self.cpp_files: - self.compile_components.append(cpp_file) - for lib in self.include_paths: - self.compile_components.append(lib) - bash_compile = "" - for component in self.compile_components: - bash_compile += str(component) + " " - self.compile_script = str(self.code_gen_dir) + "/compile.sh" - with open(self.compile_script, "w") as f: - f.write("#!/bin/bash \n") - f.write(bash_compile + "\n") - bash_command = ["bash", self.compile_script] - process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_compile.communicate() diff --git a/src/finn/util/create.py b/src/finn/util/create.py deleted file mode 100644 index 853cdd0d44a05426b34bf1db3caa58d9289b2e9e..0000000000000000000000000000000000000000 --- a/src/finn/util/create.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.core.modelwrapper import ModelWrapper -from onnx import TensorProto, helper -from finn.core.datatype import DataType -from finn.util.basic import calculate_signed_dot_prod_range, gen_finn_dt_tensor - - -def hls_random_mlp_maker(layer_spec): - """Create an MLP of given specification using HLSCustomOp instances. - Generate random weights/thresholds of appropriate size.""" - ret = [] - for l in layer_spec: - idt = l["idt"] - wdt = l["wdt"] - mw = l["mw"] - mh = l["mh"] - act = l["act"] - l["W"] = gen_finn_dt_tensor(wdt, (mw, mh)) - if act is None: - # no activation, produce accumulators - T = None - tdt = None - if wdt == DataType.BIPOLAR and idt == DataType.BIPOLAR: - odt = DataType.UINT32 - else: - odt = DataType.INT32 - else: - odt = act - (min, max) = calculate_signed_dot_prod_range(idt, wdt, mw) - n_steps = act.get_num_possible_values() - 1 - T = np.random.randint(min, max - 1, (mh, n_steps)).astype(np.float32) - # provide non-decreasing thresholds - T = np.sort(T, axis=1) - # generate thresholds for activation - if wdt == DataType.BIPOLAR and idt == DataType.BIPOLAR: - tdt = DataType.UINT32 - # bias thresholds to be positive - T = np.ceil((T + mw) / 2) - assert (T >= 0).all() - else: - tdt = DataType.INT32 - l["T"] = T - l["tdt"] = tdt - l["odt"] = odt - ret.append(l) - - return hls_mlp_maker(ret) - - -def hls_mlp_maker(layer_spec): - """Create an MLP of given specification using HLSCustomOp instances.""" - - current_in_name = "" - current_out_name = "" - i = 0 - - graph = helper.make_graph(nodes=[], name="mlp", inputs=[], outputs=[]) - - model = helper.make_model(graph, producer_name="finn") - model = ModelWrapper(model) - - for l in layer_spec: - current_W_name = "W_%d" % i - current_T_name = "T_%d" % i - current_in_name = "act_%d" % i - current_out_name = "act_%d" % (i + 1) - - W = l["W"] - (mw, mh) = W.shape - T = l["T"] - pe = l["pe"] - simd = l["simd"] - wdt = l["wdt"] - idt = l["idt"] - tdt = l["tdt"] - odt = l["odt"] - - if i == 0: - global_in = helper.make_tensor_value_info( - current_in_name, TensorProto.FLOAT, [1, mw] - ) - model.graph.input.append(global_in) - - if i == len(layer_spec) - 1: - global_out = helper.make_tensor_value_info( - current_out_name, TensorProto.FLOAT, [1, mh] - ) - model.graph.output.append(global_out) - - # there are two ways to implement bipolar weights and inputs for - # StreamingFC: - # - specify their datatypes as such - # - specify their datatypes as BINARY as use binaryXnorMode - if wdt == DataType.BIPOLAR and idt == DataType.BIPOLAR: - # we'll internally convert weights/inputs to binary and specify the - # datatypes as such, and also set the binaryXnorMode attribute to 1 - export_wdt = DataType.BINARY - export_idt = DataType.BINARY - binary_xnor_mode = 1 - else: - export_wdt = wdt - export_idt = idt - binary_xnor_mode = 0 - - if T is not None: - no_act = 0 - node_inp_list = [current_in_name, current_W_name, current_T_name] - if odt == DataType.BIPOLAR: - actval = 0 - else: - actval = odt.min() - else: - # no thresholds - node_inp_list = [current_in_name, current_W_name] - actval = 0 - no_act = 1 - FCLayer_node = helper.make_node( - "StreamingFCLayer_Batch", - node_inp_list, - [current_out_name], - domain="finn", - backend="fpgadataflow", - resType="ap_resource_lut()", - MW=mw, - MH=mh, - SIMD=simd, - PE=pe, - inputDataType=export_idt.name, - weightDataType=export_wdt.name, - outputDataType=odt.name, - ActVal=actval, - binaryXnorMode=binary_xnor_mode, - noActivation=no_act, - ) - - model.graph.node.append(FCLayer_node) - model.set_tensor_datatype(current_in_name, idt) - model.set_tensor_datatype(current_out_name, odt) - model.set_tensor_datatype(current_W_name, wdt) - if binary_xnor_mode: - # convert bipolar to binary - model.set_initializer(current_W_name, (W + 1) / 2) - else: - model.set_initializer(current_W_name, W) - if T is not None: - model.set_tensor_datatype(current_T_name, tdt) - model.set_initializer(current_T_name, T) - i += 1 - - return model diff --git a/src/finn/util/data_packing.py b/src/finn/util/data_packing.py deleted file mode 100644 index a087fd2ff2f8a3db8a6caa8332810be9b6188367..0000000000000000000000000000000000000000 --- a/src/finn/util/data_packing.py +++ /dev/null @@ -1,397 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 binascii -import os -import sys - -import numpy as np -from bitstring import BitArray - -from finn.core.datatype import DataType -from finn.util.basic import roundup_to_integer_multiple - - -def array2hexstring(array, dtype, pad_to_nbits, prefix="0x", reverse=False): - """ - Pack given one-dimensional NumPy array with FINN DataType dtype into a hex - string. - Any BIPOLAR values will be converted to a single bit with a 0 representing - -1. - pad_to_nbits is used to prepend leading zeros to ensure packed strings of - fixed width. The minimum value for pad_to_nbits is 4, since a single hex - digit is four bits. reverse can be used to reverse the array prior to - packing. - - Examples: - - array2hexstring([1, 1, 1, 0], DataType.BINARY, 4) = "0xe" - - array2hexstring([1, 1, 1, 0], DataType.BINARY, 8) = "0x0e" - - array2hexstring([1, 1, 0, 1], DataType.BINARY, 4, reverse=True) = "0xb" - - array2hexstring([1, 1, 1, 0], DataType.BINARY, 8, reverse=True) = "0x07" - """ - if pad_to_nbits < 4: - pad_to_nbits = 4 - # ensure input is a numpy array with float values - if type(array) != np.ndarray or array.dtype != np.float32: - # try to convert to a float numpy array (container dtype is float) - array = np.asarray(array, dtype=np.float32) - # ensure one-dimensional array to pack - assert array.ndim == 1, "The given array is not one-dimensional." - if dtype == DataType.BIPOLAR: - # convert bipolar values to binary - array = (array + 1) / 2 - dtype = DataType.BINARY - # reverse prior to packing, if desired - if reverse: - array = np.flip(array, -1) - lineval = BitArray(length=0) - bw = dtype.bitwidth() - for val in array: - # ensure that this value is permitted by chosen dtype - assert dtype.allowed(val), "This value is not permitted by chosen dtype." - if dtype.is_integer(): - if dtype.signed(): - lineval.append(BitArray(int=int(val), length=bw)) - else: - lineval.append(BitArray(uint=int(val), length=bw)) - else: - lineval.append(BitArray(float=val, length=bw)) - if pad_to_nbits >= lineval.len: - # extend to the desired output width (a minimum of 4 bits) - lineval.prepend(BitArray(length=pad_to_nbits - lineval.len)) - else: - raise Exception("Number of bits is greater than pad_to_nbits") - # represent as hex - return prefix + lineval.hex - - -def hexstring2npbytearray(hexstring, remove_prefix="0x"): - """Convert a hex string into a NumPy array of dtype uint8. - - Example: - - hexstring2npbytearray("0f01") = array([15, 1], dtype=uint8) - """ - # remove prefix if found - if hexstring.startswith(remove_prefix): - lrp = len(remove_prefix) - hexstring = hexstring[lrp:] - # use Python's built-in bytearray - return np.asarray(bytearray.fromhex(hexstring), dtype=np.uint8) - - -def npbytearray2hexstring(npbytearray, prefix="0x"): - """Convert a NumPy array of uint8 dtype into a hex string. - - Example: - - npbytearray2hexstring(array([15, 1], dtype=uint8)) = "0x0f01" - """ - return prefix + binascii.hexlify(bytearray(npbytearray)).decode("utf-8") - - -def pack_innermost_dim_as_hex_string( - ndarray, dtype, pad_to_nbits, reverse_inner=False, prefix="0x" -): - """Pack the innermost dimension of the given numpy ndarray into hex - strings using array2hexstring. - - Examples: - - A = [[1, 1, 1, 0], [0, 1, 1, 0]] - - eA = ["0e", "06"] - - pack_innermost_dim_as_hex_string(A, DataType.BINARY, 8) == eA - - B = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] - - eB = [[ "0f", "0f"], ["07", "0d"]] - - pack_innermost_dim_as_hex_string(B, DataType.UINT2, 8) == eB - """ - - if type(ndarray) != np.ndarray or ndarray.dtype != np.float32: - # try to convert to a float numpy array (container dtype is float) - ndarray = np.asarray(ndarray, dtype=np.float32) - - def fun(x): - return array2hexstring( - x, dtype, pad_to_nbits, reverse=reverse_inner, prefix=prefix - ) - - return np.apply_along_axis(fun, ndarray.ndim - 1, ndarray) - - -def unpack_innermost_dim_from_hex_string( - ndarray, dtype, out_shape, packedBits, reverse_inner=False -): - """Convert a NumPy array of hex strings into a FINN NumPy array by unpacking - the hex strings into the specified data type. out_shape can be specified - such that any padding in the packing dimension is removed. If reverse_inner - is set, the innermost unpacked dimension will be reversed.""" - - if type(ndarray) != np.ndarray: - raise Exception( - """unpack_innermost_dim_from_hex_string needs ndarray - as input""" - ) - if ndarray.dtype.kind not in {"U", "S"}: - raise Exception( - """unpack_innermost_dim_from_hex_string needs ndarray of - hex strings as input""" - ) - # convert ndarray into flattened list - data = ndarray.flatten().tolist() - targetBits = dtype.bitwidth() - # calculate outer and inner dim shapes - outer_dim_elems = 1 - for dim in range(len(out_shape) - 1): - outer_dim_elems = outer_dim_elems * out_shape[dim] - inner_dim_elems = out_shape[-1] - - array = [] - for outer_elem in range(outer_dim_elems): - ar_list = [] - ar_elem = data[0] - data.pop(0) - ar_elem = ar_elem.split("x") - ar_elem_bin = bin(int(ar_elem[1], 16))[2:].zfill(packedBits) - ar_elem_bin = [int(x) for x in ar_elem_bin] - - ar_elem_bin.reverse() - for i in range(inner_dim_elems): - upper_limit = (i + 1) * targetBits - lower_limit = i * targetBits - elem = ar_elem_bin[lower_limit:upper_limit] - elem.reverse() - elem_str = "".join(map(str, elem)) - ar_list.append(int(elem_str, 2)) - # reverse inner dimension back to "normal" positions - if reverse_inner is False: - ar_list.reverse() - - # interpret output values correctly - - # interpret values as bipolar - if dtype == DataType.BIPOLAR: - ar_list = [2 * x - 1 for x in ar_list] - # interpret values as signed values - elif dtype.name.startswith("INT"): - mask = 2 ** (dtype.bitwidth() - 1) - ar_list = [-(x & mask) + (x & ~mask) for x in ar_list] - - array.append(ar_list) - array = np.asarray(array, dtype=np.float32).reshape(out_shape) - return array - - -def numpy_to_hls_code( - ndarray, dtype, hls_var_name, pack_innermost_dim=True, no_decl=False -): - """Return C++ code representation of a numpy ndarray with FINN DataType - dtype, using hls_var_name as the resulting C++ variable name. If - pack_innermost_dim is specified, the innermost dimension of the ndarray - will be packed into a hex string using array2hexstring. If no_decl is - set to True, no variable name and type will be generated as part of the - emitted string. - """ - hls_dtype = dtype.get_hls_datatype_str() - if type(ndarray) != np.ndarray or ndarray.dtype != np.float32: - # try to convert to a float numpy array (container dtype is float) - ndarray = np.asarray(ndarray, dtype=np.float32) - if pack_innermost_dim: - idimlen = ndarray.shape[-1] - idimbits = idimlen * dtype.bitwidth() - idimbits = roundup_to_integer_multiple(idimbits, 4) - ndarray = pack_innermost_dim_as_hex_string(ndarray, dtype, idimbits) - hls_dtype = "ap_uint<%d>" % idimbits - ndims = ndarray.ndim - # add type string and variable name - # e.g. "const ap_uint<64>" "weightMem0" - ret = "%s %s" % (hls_dtype, hls_var_name) - # add dimensions - for d in range(ndims): - ret += "[%d]" % ndarray.shape[d] - orig_printops = np.get_printoptions() - np.set_printoptions(threshold=sys.maxsize) - - # define a function to convert a single element into a C++ init string - # a single element can be a hex string if we are using packing - def elem2str(x): - if type(x) == str or type(x) == np.str_ or type(x) == np.str: - return '%s("%s", 16)' % (hls_dtype, x) - elif type(x) == np.float32: - if dtype == DataType.FLOAT32: - return str(x) - else: - return str(int(x)) - else: - raise Exception("Unsupported type for numpy_to_hls_code") - - strarr = np.array2string(ndarray, separator=", ", formatter={"all": elem2str}) - np.set_printoptions(**orig_printops) - strarr = strarr.replace("[", "{").replace("]", "}") - if no_decl: - ret = strarr + ";" - else: - ret = ret + " = \n" + strarr + ";" - return ret - - -def npy_to_rtlsim_input(input_file, input_dtype, pad_to_nbits, reverse_inner=True): - """Convert the multidimensional NumPy array of integers (stored as floats) - from input_file into a flattened sequence of Python arbitrary-precision - integers, packing the innermost dimension. See - finn.util.basic.pack_innermost_dim_as_hex_string() for more info on how the - packing works. If reverse_inner is set, the innermost dimension will be - reversed prior to packing.""" - pad_to_nbits = roundup_to_integer_multiple(pad_to_nbits, 4) - if issubclass(type(input_file), np.ndarray): - inp = input_file - elif os.path.isfile(input_file): - inp = np.load(input_file) - else: - raise Exception("input_file must be ndarray or filename for .npy") - packed_data = pack_innermost_dim_as_hex_string( - inp, input_dtype, pad_to_nbits, reverse_inner=reverse_inner - ) - packed_data = packed_data.flatten() - packed_data = [int(x[2:], 16) for x in packed_data] - return packed_data - - -def rtlsim_output_to_npy( - output, path, dtype, shape, packedBits, targetBits, reverse_inner=True -): - """Convert a flattened sequence of Python arbitrary-precision integers - output into a NumPy array, saved as npy file at path. Each arbitrary-precision - integer is assumed to be a packed array of targetBits-bit elements, which - will be unpacked as the innermost dimension of the NumPy array. If path is - not None it will also be saved as a npy file.""" - - # TODO should have its own testbench? - output = np.asarray([hex(int(x)) for x in output]) - out_array = unpack_innermost_dim_from_hex_string( - output, dtype, shape, packedBits=packedBits, reverse_inner=reverse_inner - ) - # make copy before saving the array - out_array = out_array.copy() - if path is not None: - np.save(path, out_array) - return out_array - - -def finnpy_to_packed_bytearray( - ndarray, dtype, reverse_inner=False, reverse_endian=False -): - """Given a numpy ndarray with FINN DataType dtype, pack the innermost - dimension and return the packed representation as an ndarray of uint8. - The packed innermost dimension will be padded to the nearest multiple - of 8 bits. The returned ndarray has the same number of dimensions as the - input. - """ - - if (not issubclass(type(ndarray), np.ndarray)) or ndarray.dtype != np.float32: - # try to convert to a float numpy array (container dtype is float) - ndarray = np.asarray(ndarray, dtype=np.float32) - # pack innermost dim to hex strings padded to 8 bits - bits = dtype.bitwidth() * ndarray.shape[-1] - bits_padded = roundup_to_integer_multiple(bits, 8) - packed_hexstring = pack_innermost_dim_as_hex_string( - ndarray, dtype, bits_padded, reverse_inner=reverse_inner - ) - - def fn(x): - return np.asarray(list(map(hexstring2npbytearray, x))) - - if packed_hexstring.ndim == 0: - # scalar, call hexstring2npbytearray directly - ret = hexstring2npbytearray(np.asscalar(packed_hexstring)) - else: - # convert ndarray of hex strings to byte array - ret = np.apply_along_axis(fn, packed_hexstring.ndim - 1, packed_hexstring) - if reverse_endian: - # reverse the endianness of packing dimension - ret = np.flip(ret, axis=-1) - return ret - - -def packed_bytearray_to_finnpy( - packed_bytearray, - dtype, - output_shape=None, - reverse_inner=False, - reverse_endian=False, -): - """Given a packed numpy uint8 ndarray, unpack it into a FINN array of - given DataType. - - output_shape can be specified to remove padding from the - packed dimension, or set to None to be inferred from the input.""" - - if ( - not issubclass(type(packed_bytearray), np.ndarray) - ) or packed_bytearray.dtype != np.uint8: - raise Exception("packed_bytearray_to_finnpy needs NumPy uint8 arrays") - if packed_bytearray.ndim == 0: - raise Exception("packed_bytearray_to_finnpy expects at least 1D ndarray") - packed_dim = packed_bytearray.ndim - 1 - packed_bits = packed_bytearray.shape[packed_dim] * 8 - target_bits = dtype.bitwidth() - if output_shape is None: - # determine output shape from input shape - assert ( - packed_bits % target_bits == 0 - ), """packed_bits are not divisable by - target_bits.""" - n_target_elems = packed_bits // target_bits - output_shape = packed_bytearray.shape[:-1] + (n_target_elems,) - # if reverse_endian and target_bits > 8: - # # revse the endianness of each element - # orig_shape = packed_bytearray.shape - # assert target_bits % 8 == 0, "target_bits are not a multiple of 8." - # target_bytes = target_bits // 8 - # new_shape = orig_shape[:-1] + (-1, target_bytes) - # packed_bytearray = np.flip(packed_bytearray.reshape(new_shape), axis=-1) - # packed_bytearray = packed_bytearray.reshape(orig_shape) - if reverse_endian: - packed_bytearray = np.flip(packed_bytearray, axis=-1) - # convert innermost dim of byte array to hex strings - packed_hexstring = np.apply_along_axis( - npbytearray2hexstring, packed_dim, packed_bytearray - ) - ret = unpack_innermost_dim_from_hex_string( - packed_hexstring, dtype, output_shape, packed_bits, reverse_inner - ) - - return ret diff --git a/src/finn/util/fpgadataflow.py b/src/finn/util/fpgadataflow.py deleted file mode 100644 index 3fe747a84985b2702ffb1e5855d9071362efebda..0000000000000000000000000000000000000000 --- a/src/finn/util/fpgadataflow.py +++ /dev/null @@ -1,218 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -import subprocess - -try: - from pyverilator import PyVerilator -except ModuleNotFoundError: - PyVerilator = None -from finn.util.basic import get_by_name, make_build_dir, get_rtlsim_trace_depth - - -class IPGenBuilder: - """Builds the bash script to generate IP blocks using Vivado HLS.""" - - def __init__(self): - self.tcl_script = "" - self.ipgen_path = "" - self.code_gen_dir = "" - self.ipgen_script = "" - - def append_tcl(self, tcl_script): - """Sets member variable "tcl_script" to given tcl script.""" - self.tcl_script = tcl_script - - def set_ipgen_path(self, path): - """Sets member variable ipgen_path to given path.""" - self.ipgen_path = path - - def build(self, code_gen_dir): - """Builds the bash script with given parameters and saves it in given folder. - To guarantee the generation in the correct folder the bash script contains a - cd command.""" - self.code_gen_dir = code_gen_dir - self.ipgen_script = str(self.code_gen_dir) + "/ipgen.sh" - working_dir = os.environ["PWD"] - f = open(self.ipgen_script, "w") - f.write("#!/bin/bash \n") - f.write("cd {}\n".format(code_gen_dir)) - f.write("vivado_hls {}\n".format(self.tcl_script)) - f.write("cd {}\n".format(working_dir)) - f.close() - bash_command = ["bash", self.ipgen_script] - process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_compile.communicate() - - -def pyverilate_stitched_ip(model): - "Given a model with stitched IP, return a PyVerilator sim object." - if PyVerilator is None: - raise ImportError("Installation of PyVerilator is required.") - - vivado_stitch_proj_dir = model.get_metadata_prop("vivado_stitch_proj") - with open(vivado_stitch_proj_dir + "/all_verilog_srcs.txt", "r") as f: - all_verilog_srcs = f.read().split() - - def file_to_dir(x): - return os.path.dirname(os.path.realpath(x)) - - def file_to_basename(x): - return os.path.basename(os.path.realpath(x)) - - all_verilog_dirs = list(map(file_to_dir, all_verilog_srcs)) - all_verilog_files = list( - set( - filter( - lambda x: x.endswith(".v"), - list(map(file_to_basename, all_verilog_srcs)), - ) - ) - ) - top_module_name = model.get_metadata_prop("wrapper_filename") - top_module_name = file_to_basename(top_module_name).strip(".v") - build_dir = make_build_dir("pyverilator_ipstitched_") - sim = PyVerilator.build( - all_verilog_files, - verilog_path=all_verilog_dirs, - build_dir=build_dir, - trace_depth=get_rtlsim_trace_depth(), - top_module_name=top_module_name, - auto_eval=False, - ) - return sim - - -def pyverilate_get_liveness_threshold_cycles(): - """Return the number of no-output cycles rtlsim will wait before assuming - the simulation is not finishing and throwing an exception.""" - - return int(os.getenv("LIVENESS_THRESHOLD", 10000)) - - -def is_fpgadataflow_node(node): - """Returns True if given node is fpgadataflow node. Otherwise False.""" - is_node = False - if node is not None: - if node.domain == "finn": - n_backend = get_by_name(node.attribute, "backend") - if n_backend is not None: - backend_value = n_backend.s.decode("UTF-8") - if backend_value == "fpgadataflow": - is_node = True - - return is_node - - -def rtlsim_multi_io(sim, io_dict, num_out_values, trace_file=""): - """Runs the pyverilator simulation by passing the input values to the simulation, - toggle the clock and observing the execution time. Function contains also an - observation loop that can abort the simulation if no output value is produced - after a set number of cycles. Can handle multiple i/o streams. See function - implementation for details on how the top-level signals should be named. - - sim: the PyVerilator object for simulation - io_dict: a dict of dicts in the following format: - {"inputs" : {"in0" : <input_data>, "in1" : <input_data>}, - "outputs" : {"out0" : [], "out1" : []} } - <input_data> is a list of Python arbitrary-precision ints indicating - what data to push into the simulation, and the output lists are - similarly filled when the simulation is complete - num_out_values: number of total values to be read from the simulation to - finish the simulation and return. - - returns: number of clock cycles elapsed for completion - - """ - - if trace_file != "": - sim.start_vcd_trace(trace_file) - - for outp in io_dict["outputs"]: - sim.io[outp + "_V_V_TREADY"] = 1 - - # observe if output is completely calculated - # total_cycle_count will contain the number of cycles the calculation ran - output_done = False - total_cycle_count = 0 - output_count = 0 - old_output_count = 0 - - # avoid infinite looping of simulation by aborting when there is no change in - # output values after 100 cycles - no_change_count = 0 - liveness_threshold = pyverilate_get_liveness_threshold_cycles() - - while not (output_done): - for inp in io_dict["inputs"]: - inputs = io_dict["inputs"][inp] - sim.io[inp + "_V_V_TVALID"] = 1 if len(inputs) > 0 else 0 - sim.io[inp + "_V_V_TDATA"] = inputs[0] if len(inputs) > 0 else 0 - if sim.io[inp + "_V_V_TREADY"] == 1 and sim.io[inp + "_V_V_TVALID"] == 1: - inputs = inputs[1:] - io_dict["inputs"][inp] = inputs - - for outp in io_dict["outputs"]: - outputs = io_dict["outputs"][outp] - if sim.io[outp + "_V_V_TVALID"] == 1 and sim.io[outp + "_V_V_TREADY"] == 1: - outputs = outputs + [sim.io[outp + "_V_V_TDATA"]] - output_count += 1 - io_dict["outputs"][outp] = outputs - - sim.io.ap_clk = 1 - sim.io.ap_clk = 0 - - total_cycle_count = total_cycle_count + 1 - - if output_count == old_output_count: - no_change_count = no_change_count + 1 - else: - no_change_count = 0 - old_output_count = output_count - - # check if all expected output words received - if output_count == num_out_values: - output_done = True - - # end sim on timeout - if no_change_count == liveness_threshold: - if trace_file != "": - sim.flush_vcd_trace() - sim.stop_vcd_trace() - raise Exception( - "Error in simulation! Takes too long to produce output. " - "Consider setting the LIVENESS_THRESHOLD env.var. to a " - "larger value." - ) - - if trace_file != "": - sim.flush_vcd_trace() - sim.stop_vcd_trace() - - return total_cycle_count diff --git a/src/finn/util/onnx.py b/src/finn/util/onnx.py deleted file mode 100644 index 4d7cdd126ededac887639a932c2021ef5f081c02..0000000000000000000000000000000000000000 --- a/src/finn/util/onnx.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.core.data_layout as DataLayout - - -def valueinfo_to_tensor(vi): - """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] - ) - - -def nchw_to_nhwc(t, model, idx, reverse=False): - """Converts between NCHW <-> NHWC layouts for tensor t by inserting a transpose. - If reverse=False, t is assumed NCHW and we insert transpose to convert NCHW -> NHWC - If reverse=True, t is assumed NHWC and we insert transpose to convert NHWC -> NCHW. - """ - graph = model.graph - # create new NHWC tensor - t_shape = model.get_tensor_shape(t) - bs = t_shape[0] - ch = t_shape[1] - height = t_shape[2] - width = t_shape[3] - t_trans = onnx.helper.make_tensor_value_info( - model.make_new_valueinfo_name(), - onnx.TensorProto.FLOAT, - (bs, height, width, ch), # NHWC - ) - graph.value_info.append(t_trans) - dt = model.get_tensor_datatype(t) - t_trans = t_trans.name - model.set_tensor_datatype(t_trans, dt) - model.set_tensor_layout(t_trans, DataLayout.NHWC) - # NCHW <-> NHWC transpose - if reverse: - t_trans_node = onnx.helper.make_node( - "Transpose", [t_trans], [t], perm=[0, 3, 1, 2] - ) - else: - t_trans_node = onnx.helper.make_node( - "Transpose", [t], [t_trans], perm=[0, 2, 3, 1] - ) - graph.node.insert(idx, t_trans_node) - return t_trans diff --git a/src/finn/util/test.py b/src/finn/util/test.py index 32c6a0a3a3bb19b95590181dbe447e82cf9966a2..03a9d435a6d04659b2891815b33172586a7f0a96 100644 --- a/src/finn/util/test.py +++ b/src/finn/util/test.py @@ -139,11 +139,13 @@ def get_example_input(topology): "Get example numpy input tensor for given topology." if "fc" in topology: - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") + raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") onnx_tensor = onnx.load_tensor_from_string(raw_i) return nph.to_array(onnx_tensor) elif topology == "cnv": - fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") + fn = pk.resource_filename( + "finn.qnn-data", "cifar10/cifar10-test-data-class3.npz" + ) input_tensor = np.load(fn)["arr_0"].astype(np.float32) return input_tensor else: diff --git a/tests/analysis/test_is_linear.py b/tests/analysis/test_is_linear.py deleted file mode 100644 index 6afe9bb9c57f5f20486a9a35bab9902e2d952b02..0000000000000000000000000000000000000000 --- a/tests/analysis/test_is_linear.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx.helper as oh -from onnx import TensorProto - -import finn.analysis.topology as ta -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_shapes import InferShapes - - -def test_is_linear_linear(): - 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.is_linear) - assert ret["is_linear"] is True - - -def test_is_linear_forked_node_output(): - top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, [2]) - add_param = oh.make_tensor_value_info("add_param", TensorProto.FLOAT, [2]) - mul0_param = oh.make_tensor_value_info("mul0_param", TensorProto.FLOAT, [2]) - mul1_param = oh.make_tensor_value_info("mul1_param", TensorProto.FLOAT, [2]) - mul0_res = oh.make_tensor_value_info("mul0_res", TensorProto.FLOAT, [2]) - mul1_res = oh.make_tensor_value_info("mul1_res", 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, mul0_param, mul1_param, mul0_res, mul1_res], - nodes=[ - oh.make_node("Add", ["top_in", "add_param"], ["middle"]), - oh.make_node("Mul", ["middle", "mul0_param"], ["mul0_res"]), - oh.make_node("Mul", ["middle", "mul1_param"], ["mul1_res"]), - oh.make_node("Add", ["mul0_res", "mul1_res"], ["top_out"]), - ], - ) - ) - model = ModelWrapper(modelproto) - model = model.transform(InferShapes()) - ret = model.analysis(ta.is_linear) - assert ret["is_linear"] is False diff --git a/tests/analysis/test_topology_checks.py b/tests/analysis/test_topology_checks.py deleted file mode 100644 index 7f7f800da05e38fefa9350928ab6ddc94acbe2b6..0000000000000000000000000000000000000000 --- a/tests/analysis/test_topology_checks.py +++ /dev/null @@ -1,205 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -from pkgutil import get_data - -import onnx.helper as oh -from onnx import TensorProto -import brevitas.onnx as bo -from finn.util.test import get_test_model_trained -import finn.analysis.topology as ta -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_shapes import InferShapes - - -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 test_node_inputs_in_expected_order(): - raw_m = get_data("finn", "data/onnx/mnist-conv/model.onnx") - model = ModelWrapper(raw_m) - model = model.transform(InferShapes()) - ret = model.analysis(ta.node_inputs_in_expected_order) - # this model has an (unnecessary) dynamic reshape for its weight tensor - # and so it fails the check - assert ret["node_inputs_in_expected_order"] is False - - -def test_nodes_topologically_sorted(): - # test analysis pass (nodes_topologically_sorted) with different models - - # test with data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx - raw_m = get_data( - "finn", "data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx" - ) - model = ModelWrapper(raw_m) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is True - - # remove first node and add it at the end - graph = model.graph - first_node = graph.node[0] - graph.node.remove(first_node) - graph.node.append(first_node) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is False - - # test with data/onnx/mnist-conv/model.onnx - raw_m = get_data("finn", "data/onnx/mnist-conv/model.onnx") - model = ModelWrapper(raw_m) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is True - - # remove first node and add it at the end - graph = model.graph - first_node = graph.node[0] - graph.node.remove(first_node) - graph.node.append(first_node) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is False - - # test with manually created small network - Neg_node = oh.make_node("Neg", inputs=["in1"], outputs=["neg1"]) - Round_node = oh.make_node("Round", inputs=["neg1"], outputs=["round1"]) - - Ceil_node = oh.make_node("Ceil", inputs=["neg1"], outputs=["ceil1"]) - Add_node = oh.make_node("Add", inputs=["round1", "ceil1"], outputs=["out1"]) - - in1 = oh.make_tensor_value_info("in1", TensorProto.FLOAT, [4, 4]) - out1 = oh.make_tensor_value_info("out1", TensorProto.FLOAT, [4, 4]) - - graph = oh.make_graph( - nodes=[Neg_node, Round_node, Ceil_node, Add_node], - name="simple_graph", - inputs=[in1], - outputs=[out1], - value_info=[ - oh.make_tensor_value_info("neg1", TensorProto.FLOAT, [4, 4]), - oh.make_tensor_value_info("round1", TensorProto.FLOAT, [4, 4]), - oh.make_tensor_value_info("ceil1", TensorProto.FLOAT, [4, 4]), - ], - ) - - onnx_model = oh.make_model(graph, producer_name="simple-model") - model = ModelWrapper(onnx_model) - - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is True - - # create same graph but with "wrong" node order - graph = oh.make_graph( - nodes=[Round_node, Ceil_node, Neg_node, Add_node], - name="simple_graph", - inputs=[in1], - outputs=[out1], - value_info=[ - oh.make_tensor_value_info("neg1", TensorProto.FLOAT, [4, 4]), - oh.make_tensor_value_info("round1", TensorProto.FLOAT, [4, 4]), - oh.make_tensor_value_info("ceil1", TensorProto.FLOAT, [4, 4]), - ], - ) - - onnx_model = oh.make_model(graph, producer_name="simple-model") - model = ModelWrapper(onnx_model) - - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is False - - # test with data/onnx/finn-hls-model/finn-hls-onnx-model.onnx - raw_m = get_data("finn", "data/onnx/finn-hls-model/finn-hls-onnx-model.onnx") - model = ModelWrapper(raw_m) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is True - - # remove first node and add it at the end - graph = model.graph - first_node = graph.node[0] - graph.node.remove(first_node) - graph.node.append(first_node) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is False - - # test with cnv_w1a1 - build_dir = "/tmp/" + os.environ["FINN_INST_NAME"] - cnv = get_test_model_trained("CNV", 1, 1) - bo.export_finn_onnx( - cnv, (1, 3, 32, 32), build_dir + "/end2end_cnv_w1a1_export.onnx" - ) - model = ModelWrapper(build_dir + "/end2end_cnv_w1a1_export.onnx") - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is True - - # remove first node and add it at the end - graph = model.graph - first_node = graph.node[0] - graph.node.remove(first_node) - graph.node.append(first_node) - ret = model.analysis(ta.nodes_topologically_sorted) - assert ret["nodes_topologically_sorted"] is False diff --git a/tests/brevitas/test_brevitas_cnv.py b/tests/brevitas/test_brevitas_cnv.py index 120c67646de08a1a9875b76bedd3a0130792b487..4b072535bdfe102a6c59ebd4c730de9ae827c00e 100644 --- a/tests/brevitas/test_brevitas_cnv.py +++ b/tests/brevitas/test_brevitas_cnv.py @@ -58,7 +58,7 @@ def test_brevitas_cnv_export_exec(wbits, abits): model = model.transform(RemoveStaticGraphInputs()) assert len(model.graph.input) == 1 assert len(model.graph.output) == 1 - fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") + fn = pk.resource_filename("finn.qnn-data", "cifar10/cifar10-test-data-class3.npz") input_tensor = np.load(fn)["arr_0"].astype(np.float32) input_tensor = input_tensor / 255 assert input_tensor.shape == (1, 3, 32, 32) diff --git a/tests/brevitas/test_brevitas_debug.py b/tests/brevitas/test_brevitas_debug.py index 50d0ca44cd0befe5d08b5c1b45edf602457bda19..9115352796b0b90257d64ce9b14163ad372c9c98 100644 --- a/tests/brevitas/test_brevitas_debug.py +++ b/tests/brevitas/test_brevitas_debug.py @@ -55,7 +55,7 @@ def test_brevitas_debug(): assert len(model.graph.input) == 1 assert len(model.graph.output) == 1 # load one of the test vectors - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") + 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) # run using FINN-based execution input_dict = {"0": nph.to_array(input_tensor)} diff --git a/tests/brevitas/test_brevitas_fc.py b/tests/brevitas/test_brevitas_fc.py index 9369b25385080875efcb286c02291fc579a15a34..24a453007515ba2eba4369a6b76829099f722168 100644 --- a/tests/brevitas/test_brevitas_fc.py +++ b/tests/brevitas/test_brevitas_fc.py @@ -68,7 +68,7 @@ def test_brevitas_fc_onnx_export_and_exec(size, wbits, abits): assert len(model.graph.input) == 1 assert len(model.graph.output) == 1 # load one of the test vectors - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") + 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) # run using FINN-based execution input_dict = {"0": nph.to_array(input_tensor)} diff --git a/tests/core/test_basic_onnx_exec.py b/tests/core/test_basic_onnx_exec.py deleted file mode 100644 index ddb2cbfc40c7647970f0c51ecb95340e7d1dddae..0000000000000000000000000000000000000000 --- a/tests/core/test_basic_onnx_exec.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from pkgutil import get_data - -import numpy as np -import onnx -import onnx.numpy_helper as np_helper - -import finn.core.onnx_exec as oxe -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_shapes import InferShapes -from finn.core.datatype import DataType -from finn.util.basic import gen_finn_dt_tensor - - -def test_mnist_onnx_download_extract_run(): - # load the onnx model - raw_m = get_data("finn", "data/onnx/mnist-conv/model.onnx") - model = ModelWrapper(raw_m) - model = model.transform(InferShapes()) - # load one of the test vectors - 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) - # run using FINN-based execution (full graph) - input_dict = {"Input3": np_helper.to_array(input_tensor)} - output_dict = oxe.execute_onnx(model, input_dict, return_full_exec_context=True) - assert np.isclose( - np_helper.to_array(output_tensor), output_dict["Plus214_Output_0"], atol=1e-3 - ).all() - # test subgraph execution - start_node = model.graph.node[1] - end_node = model.graph.node[3] - subgraph_i_dict = {start_node.input[0]: output_dict[start_node.input[0]]} - subgraph_o_dict = oxe.execute_onnx( - model, - subgraph_i_dict, - return_full_exec_context=True, - start_node=start_node, - end_node=end_node, - ) - assert np.isclose( - subgraph_o_dict[end_node.output[0]], output_dict[end_node.output[0]], atol=1e-3 - ).all() - - -def test_onnx_exec_internal_rounding(): - inp0 = onnx.helper.make_tensor_value_info("inp0", onnx.TensorProto.FLOAT, [2, 2]) - inp1 = onnx.helper.make_tensor_value_info("inp1", onnx.TensorProto.FLOAT, [1]) - outp = onnx.helper.make_tensor_value_info("outp", onnx.TensorProto.FLOAT, [2, 2]) - mul_node = onnx.helper.make_node("Mul", inputs=["inp0", "inp1"], outputs=["outp"]) - graph = onnx.helper.make_graph( - nodes=[mul_node], name="mul_graph", inputs=[inp0, inp1], outputs=[outp] - ) - - model = onnx.helper.make_model(graph, producer_name="mul-model") - model = ModelWrapper(model) - idt = DataType.INT2 - model.set_tensor_datatype("inp0", idt) - model.set_tensor_datatype("inp1", idt) - model.transform(InferShapes()) - - mul_value = np.asarray([-1], dtype=np.float32) - inp_int = gen_finn_dt_tensor(idt, [2, 2]) - scale = np.random.uniform(low=0, high=1, size=(2, 2)).astype(np.float32) - inp_rounded = (inp_int * scale) / (scale + 1e-7) - input_dict = {"inp0": inp_rounded, "inp1": mul_value} - output_dict = oxe.execute_onnx(model, input_dict) - produced = output_dict["outp"] - expected = np.multiply(inp_int, mul_value) - assert (produced == expected).all() diff --git a/tests/core/test_custom_onnx_exec.py b/tests/core/test_custom_onnx_exec.py deleted file mode 100644 index 086681dde0ff029ceaa7d3274bad4d3f15bd32fc..0000000000000000000000000000000000000000 --- a/tests/core/test_custom_onnx_exec.py +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx import TensorProto, helper - -import finn.core.execute_custom_node as ex_cu_node - - -def test_execute_custom_node_multithreshold(): - inputs = np.ndarray( - shape=(6, 3, 2, 2), - buffer=np.array( - [ - 4.8, - 3.2, - 1.2, - 4.9, - 7.8, - 2.4, - 3.1, - 4.7, - 6.2, - 5.1, - 4.9, - 2.2, - 6.2, - 0.0, - 0.8, - 4.7, - 0.2, - 5.6, - 8.9, - 9.2, - 9.1, - 4.0, - 3.3, - 4.9, - 2.3, - 1.7, - 1.3, - 2.2, - 4.6, - 3.4, - 3.7, - 9.8, - 4.7, - 4.9, - 2.8, - 2.7, - 8.3, - 6.7, - 4.2, - 7.1, - 2.8, - 3.1, - 0.8, - 0.6, - 4.4, - 2.7, - 6.3, - 6.1, - 1.4, - 5.3, - 2.3, - 1.9, - 4.7, - 8.1, - 9.3, - 3.7, - 2.7, - 5.1, - 4.2, - 1.8, - 4.1, - 7.3, - 7.1, - 0.4, - 0.2, - 1.3, - 4.3, - 8.9, - 1.4, - 1.6, - 8.3, - 9.4, - ] - ), - ) - - threshold_values = np.ndarray( - shape=(3, 7), - buffer=np.array( - [ - 0.8, - 1.4, - 1.7, - 3.5, - 5.2, - 6.8, - 8.2, - 0.2, - 2.2, - 3.5, - 4.5, - 6.6, - 8.6, - 9.2, - 1.3, - 4.1, - 4.5, - 6.5, - 7.8, - 8.1, - 8.9, - ] - ), - ) - - v = helper.make_tensor_value_info("v", TensorProto.FLOAT, [6, 3, 2, 2]) - thresholds = helper.make_tensor_value_info("thresholds", TensorProto.FLOAT, [3, 7]) - out = helper.make_tensor_value_info("out", TensorProto.FLOAT, [6, 3, 2, 2]) - - node_def = helper.make_node( - "MultiThreshold", ["v", "thresholds"], ["out"], domain="finn" - ) - - graph_def = helper.make_graph([node_def], "test_model", [v, thresholds], [out]) - - execution_context = {} - execution_context["v"] = inputs - execution_context["thresholds"] = threshold_values - - ex_cu_node.execute_custom_node(node_def, execution_context, graph_def) - - outputs = np.ndarray( - shape=(6, 3, 2, 2), - buffer=np.array( - [ - 4.0, - 3.0, - 1.0, - 4.0, - 5.0, - 2.0, - 2.0, - 4.0, - 3.0, - 3.0, - 3.0, - 1.0, - 5.0, - 0.0, - 1.0, - 4.0, - 1.0, - 4.0, - 6.0, - 7.0, - 7.0, - 1.0, - 1.0, - 3.0, - 3.0, - 3.0, - 1.0, - 3.0, - 4.0, - 2.0, - 3.0, - 7.0, - 3.0, - 3.0, - 1.0, - 1.0, - 7.0, - 5.0, - 4.0, - 6.0, - 2.0, - 2.0, - 1.0, - 1.0, - 2.0, - 1.0, - 3.0, - 3.0, - 2.0, - 5.0, - 3.0, - 3.0, - 4.0, - 5.0, - 7.0, - 3.0, - 1.0, - 3.0, - 2.0, - 1.0, - 4.0, - 6.0, - 6.0, - 0.0, - 1.0, - 1.0, - 3.0, - 6.0, - 1.0, - 1.0, - 6.0, - 7.0, - ] - ), - ) - - assert (execution_context["out"] == outputs).all() - - # test the optional output scaling features on MultiThreshold - node_def = helper.make_node( - "MultiThreshold", - ["v", "thresholds"], - ["out"], - domain="finn", - out_scale=2.0, - out_bias=-1.0, - ) - - graph_def = helper.make_graph([node_def], "test_model", [v, thresholds], [out]) - ex_cu_node.execute_custom_node(node_def, execution_context, graph_def) - outputs_scaled = 2.0 * outputs - 1.0 - assert (execution_context["out"] == outputs_scaled).all() - - # test the optional data layout option for MultiThreshold - node_def = helper.make_node( - "MultiThreshold", - ["v", "thresholds"], - ["out"], - domain="finn", - data_layout="NHWC", - ) - - v_nhwc = helper.make_tensor_value_info("v", TensorProto.FLOAT, [6, 2, 2, 3]) - out_nhwc = helper.make_tensor_value_info("out", TensorProto.FLOAT, [6, 2, 2, 3]) - inputs_nhwc = np.transpose(inputs, (0, 2, 3, 1)) # NCHW -> NHWC - outputs_nhwc = np.transpose(outputs, (0, 2, 3, 1)) # NCHW -> NHWC - execution_context["v"] = inputs_nhwc - - graph_def = helper.make_graph( - [node_def], "test_model", [v_nhwc, thresholds], [out_nhwc] - ) - ex_cu_node.execute_custom_node(node_def, execution_context, graph_def) - assert (execution_context["out"] == outputs_nhwc).all() diff --git a/tests/core/test_datatypes.py b/tests/core/test_datatypes.py deleted file mode 100644 index f1d34923c5f8b05dc21a0d5b7781879deabae379..0000000000000000000000000000000000000000 --- a/tests/core/test_datatypes.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from finn.core.datatype import DataType - - -def test_datatypes(): - assert DataType.BIPOLAR.allowed(-1) - assert DataType.BIPOLAR.allowed(0) is False - assert DataType.BINARY.allowed(-1) is False - assert DataType.BINARY.allowed(1) - assert DataType.TERNARY.allowed(2) is False - assert DataType.TERNARY.allowed(-1) - assert DataType.UINT2.allowed(2) - assert DataType.UINT2.allowed(10) is False - assert DataType.UINT3.allowed(5) - assert DataType.UINT3.allowed(-7) is False - assert DataType.UINT4.allowed(15) - assert DataType.UINT4.allowed(150) is False - assert DataType.UINT8.allowed(150) - assert DataType.UINT8.allowed(777) is False - assert DataType.UINT16.allowed(14500) - assert DataType.UINT16.allowed(-1) is False - assert DataType.UINT32.allowed(2 ** 10) - assert DataType.UINT32.allowed(-1) is False - assert DataType.INT2.allowed(-1) - assert DataType.INT2.allowed(-10) is False - assert DataType.INT3.allowed(5) is False - assert DataType.INT3.allowed(-2) - assert DataType.INT4.allowed(15) is False - assert DataType.INT4.allowed(-5) - assert DataType.INT8.allowed(150) is False - assert DataType.INT8.allowed(-127) - assert DataType.INT16.allowed(-1.04) is False - assert DataType.INT16.allowed(-7777) - assert DataType.INT32.allowed(7.77) is False - assert DataType.INT32.allowed(-5) - assert DataType.INT32.allowed(5) - assert DataType.BINARY.signed() is False - assert DataType.FLOAT32.signed() - assert DataType.BIPOLAR.signed() - assert DataType.TERNARY.signed() - - -def test_smallest_possible(): - assert DataType.get_smallest_possible(1) == DataType.BINARY - assert DataType.get_smallest_possible(1.1) == DataType.FLOAT32 - assert DataType.get_smallest_possible(-1) == DataType.BIPOLAR - assert DataType.get_smallest_possible(-3) == DataType.INT3 - assert DataType.get_smallest_possible(-3.2) == DataType.FLOAT32 diff --git a/tests/core/test_mixed_onnx_exec.py b/tests/core/test_mixed_onnx_exec.py deleted file mode 100644 index d8754105e3001f3ef40b1df23e4d5a22aa176ba4..0000000000000000000000000000000000000000 --- a/tests/core/test_mixed_onnx_exec.py +++ /dev/null @@ -1,252 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 onnx import TensorProto, helper - -import finn.core.onnx_exec as oxe -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_shapes import InferShapes - - -def test_execute_mixed_model(): - - out0 = helper.make_tensor_value_info("out0", TensorProto.FLOAT, [6, 3, 2, 2]) - - graph_def = helper.make_graph( - nodes=[ - helper.make_node( - "MultiThreshold", ["v", "thresholds"], ["out0"], domain="finn" - ), - helper.make_node("Relu", ["out0"], ["out1"]), - ], - name="test-model", - inputs=[ - helper.make_tensor_value_info("v", TensorProto.FLOAT, [6, 3, 2, 2]), - helper.make_tensor_value_info("thresholds", TensorProto.FLOAT, [3, 7]), - ], - outputs=[ - helper.make_tensor_value_info("out1", TensorProto.FLOAT, [6, 3, 2, 2]) - ], - value_info=[out0], - ) - model_def = helper.make_model(graph_def, producer_name="onnx-example") - - model = ModelWrapper(model_def) - model = model.transform(InferShapes()) - - inputs = np.asarray( - [ - 4.8, - 3.2, - 1.2, - 4.9, - 7.8, - 2.4, - 3.1, - 4.7, - 6.2, - 5.1, - 4.9, - 2.2, - 6.2, - 0.0, - 0.8, - 4.7, - 0.2, - 5.6, - 8.9, - 9.2, - 9.1, - 4.0, - 3.3, - 4.9, - 2.3, - 1.7, - 1.3, - 2.2, - 4.6, - 3.4, - 3.7, - 9.8, - 4.7, - 4.9, - 2.8, - 2.7, - 8.3, - 6.7, - 4.2, - 7.1, - 2.8, - 3.1, - 0.8, - 0.6, - 4.4, - 2.7, - 6.3, - 6.1, - 1.4, - 5.3, - 2.3, - 1.9, - 4.7, - 8.1, - 9.3, - 3.7, - 2.7, - 5.1, - 4.2, - 1.8, - 4.1, - 7.3, - 7.1, - 0.4, - 0.2, - 1.3, - 4.3, - 8.9, - 1.4, - 1.6, - 8.3, - 9.4, - ], - dtype=np.float32, - ).reshape(6, 3, 2, 2) - - threshold_values = np.asarray( - [ - 0.8, - 1.4, - 1.7, - 3.5, - 5.2, - 6.8, - 8.2, - 0.2, - 2.2, - 3.5, - 4.5, - 6.6, - 8.6, - 9.2, - 1.3, - 4.1, - 4.5, - 6.5, - 7.8, - 8.1, - 8.9, - ], - dtype=np.float32, - ).reshape(3, 7) - - input_dict = {} - input_dict["v"] = inputs - input_dict["thresholds"] = threshold_values - - output_dict = oxe.execute_onnx(model, input_dict) - - outputs = np.asarray( - [ - 4.0, - 3.0, - 1.0, - 4.0, - 5.0, - 2.0, - 2.0, - 4.0, - 3.0, - 3.0, - 3.0, - 1.0, - 5.0, - 0.0, - 1.0, - 4.0, - 1.0, - 4.0, - 6.0, - 7.0, - 7.0, - 1.0, - 1.0, - 3.0, - 3.0, - 3.0, - 1.0, - 3.0, - 4.0, - 2.0, - 3.0, - 7.0, - 3.0, - 3.0, - 1.0, - 1.0, - 7.0, - 5.0, - 4.0, - 6.0, - 2.0, - 2.0, - 1.0, - 1.0, - 2.0, - 1.0, - 3.0, - 3.0, - 2.0, - 5.0, - 3.0, - 3.0, - 4.0, - 5.0, - 7.0, - 3.0, - 1.0, - 3.0, - 2.0, - 1.0, - 4.0, - 6.0, - 6.0, - 0.0, - 1.0, - 1.0, - 3.0, - 6.0, - 1.0, - 1.0, - 6.0, - 7.0, - ], - dtype=np.float32, - ).reshape(6, 3, 2, 2) - - assert (output_dict["out1"] == outputs).all() diff --git a/tests/core/test_modelwrapper.py b/tests/core/test_modelwrapper.py deleted file mode 100644 index 0fb7ae42f3bd556755f81a02be6c71fd73ffc519..0000000000000000000000000000000000000000 --- a/tests/core/test_modelwrapper.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -import onnx -from collections import Counter -import brevitas.onnx as bo -import numpy as np -import finn.core.data_layout as DataLayout - -from finn.core.modelwrapper import ModelWrapper -from finn.util.test import get_test_model_trained - -export_onnx_path = "test_modelwrapper.onnx" - - -def test_modelwrapper(): - 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) - assert model.check_all_tensor_shapes_specified() is False - inp_name = model.graph.input[0].name - inp_shape = model.get_tensor_shape(inp_name) - assert inp_shape == [1, 1, 28, 28] - # find first matmul node - l0_mat_tensor_name = "" - l0_inp_tensor_name = "" - for node in model.graph.node: - if node.op_type == "MatMul": - l0_inp_tensor_name = node.input[0] - l0_mat_tensor_name = node.input[1] - break - assert l0_mat_tensor_name != "" - l0_weights = model.get_initializer(l0_mat_tensor_name) - assert l0_weights.shape == (784, 1024) - l0_weights_hist = Counter(l0_weights.flatten()) - assert (l0_weights_hist[1.0] + l0_weights_hist[-1.0]) == 784 * 1024 - l0_weights_rand = np.random.randn(784, 1024) - model.set_initializer(l0_mat_tensor_name, l0_weights_rand) - assert (model.get_initializer(l0_mat_tensor_name) == l0_weights_rand).all() - assert l0_inp_tensor_name != "" - inp_cons = model.find_consumer(l0_inp_tensor_name) - assert inp_cons.op_type == "MatMul" - out_prod = model.find_producer(l0_inp_tensor_name) - assert out_prod.op_type == "MultiThreshold" - inp_layout = model.get_tensor_layout(inp_name) - assert inp_layout is None - inp_layout = DataLayout.NCHW - model.set_tensor_layout(inp_name, inp_layout) - assert model.get_tensor_layout(inp_name) == inp_layout - inp_sparsity = model.get_tensor_sparsity(inp_name) - assert inp_sparsity is None - inp_sparsity = {"dw": {"kernel_shape": 3}} - model.set_tensor_sparsity(inp_name, inp_sparsity) - assert model.get_tensor_sparsity(inp_name) == inp_sparsity - os.remove(export_onnx_path) - - -def test_modelwrapper_graph_order(): - # create small network with properties to be tested - Neg_node = onnx.helper.make_node("Neg", inputs=["in1"], outputs=["neg1"]) - Round_node = onnx.helper.make_node("Round", inputs=["neg1"], outputs=["round1"]) - - Ceil_node = onnx.helper.make_node("Ceil", inputs=["neg1"], outputs=["ceil1"]) - Add_node = onnx.helper.make_node( - "Add", inputs=["round1", "ceil1"], outputs=["out1"] - ) - - in1 = onnx.helper.make_tensor_value_info("in1", onnx.TensorProto.FLOAT, [4, 4]) - out1 = onnx.helper.make_tensor_value_info("out1", onnx.TensorProto.FLOAT, [4, 4]) - - graph = onnx.helper.make_graph( - nodes=[Neg_node, Round_node, Ceil_node, Add_node], - name="simple_graph", - inputs=[in1], - outputs=[out1], - value_info=[ - onnx.helper.make_tensor_value_info("neg1", onnx.TensorProto.FLOAT, [4, 4]), - onnx.helper.make_tensor_value_info( - "round1", onnx.TensorProto.FLOAT, [4, 4] - ), - onnx.helper.make_tensor_value_info("ceil1", onnx.TensorProto.FLOAT, [4, 4]), - ], - ) - - onnx_model = onnx.helper.make_model(graph, producer_name="simple-model") - model = ModelWrapper(onnx_model) - - # test graph order functions - assert model.find_consumers("in1") == [Neg_node] - assert model.find_consumers("neg1") == [Round_node, Ceil_node] - assert model.find_consumers("round1") == [Add_node] - assert model.find_consumers("ceil1") == [Add_node] - assert model.find_consumers("out1") is None - - assert model.find_direct_successors(Neg_node) == [Round_node, Ceil_node] - assert model.find_direct_successors(Round_node) == [Add_node] - assert model.find_direct_successors(Ceil_node) == [Add_node] - assert model.find_direct_successors(Add_node) is None - - assert model.find_direct_predecessors(Neg_node) is None - assert model.find_direct_predecessors(Round_node) == [Neg_node] - assert model.find_direct_predecessors(Ceil_node) == [Neg_node] - assert model.find_direct_predecessors(Add_node) == [Round_node, Ceil_node] - - assert model.get_node_index(Neg_node) == 0 - assert model.get_node_index(Round_node) == 1 - assert model.get_node_index(Ceil_node) == 2 - assert model.get_node_index(Add_node) == 3 - - -def test_modelwrapper_detect_forks_n_joins(): - # create small network with properties to be tested - Neg_node = onnx.helper.make_node("Neg", inputs=["in1"], outputs=["neg1"]) - Round_node = onnx.helper.make_node("Round", inputs=["neg1"], outputs=["round1"]) - - Ceil_node = onnx.helper.make_node("Ceil", inputs=["neg1"], outputs=["ceil1"]) - Add_node = onnx.helper.make_node( - "Add", inputs=["round1", "ceil1"], outputs=["out1"] - ) - - in1 = onnx.helper.make_tensor_value_info("in1", onnx.TensorProto.FLOAT, [4, 4]) - out1 = onnx.helper.make_tensor_value_info("out1", onnx.TensorProto.FLOAT, [4, 4]) - - graph = onnx.helper.make_graph( - nodes=[Neg_node, Round_node, Ceil_node, Add_node], - name="simple_graph", - inputs=[in1], - outputs=[out1], - value_info=[ - onnx.helper.make_tensor_value_info("neg1", onnx.TensorProto.FLOAT, [4, 4]), - onnx.helper.make_tensor_value_info( - "round1", onnx.TensorProto.FLOAT, [4, 4] - ), - onnx.helper.make_tensor_value_info("ceil1", onnx.TensorProto.FLOAT, [4, 4]), - ], - ) - - onnx_model = onnx.helper.make_model(graph, producer_name="simple-model") - model = ModelWrapper(onnx_model) - - # test - assert model.is_fork_node(Neg_node) - assert not model.is_fork_node(Round_node) - assert not model.is_fork_node(Ceil_node) - assert not model.is_fork_node(Add_node) - - assert not model.is_join_node(Neg_node) - assert not model.is_join_node(Round_node) - assert not model.is_join_node(Ceil_node) - assert model.is_join_node(Add_node) diff --git a/tests/custom_op/test_im2col.py b/tests/custom_op/test_im2col.py deleted file mode 100644 index 0b148145bd6f7d9819e4b72da5333d2556de94e4..0000000000000000000000000000000000000000 --- a/tests/custom_op/test_im2col.py +++ /dev/null @@ -1,320 +0,0 @@ -import numpy as np -from onnx import TensorProto, helper - -import finn.core.onnx_exec as oxe -from finn.core.datatype import DataType -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_datatypes import InferDataTypes -from finn.transformation.infer_shapes import InferShapes -from finn.custom_op.im2col import compute_conv_output_dim - - -def check_two_dict_for_equality(dict1, dict2): - for key in dict1: - assert key in dict2, "Key: {} is not in both dictionaries".format(key) - assert ( - dict1[key] == dict2[key] - ), """Values for key {} are not the same - in both dictionaries""".format( - key - ) - - return True - - -def execution_im2col(x, idt, k, stride, ifm_ch, ifm_dim, pad_amt=0, pad_val=0): - ofm_dim = compute_conv_output_dim(ifm_dim, k, stride, pad_amt) - - # set up onnx model - inp = helper.make_tensor_value_info( - "inp", TensorProto.FLOAT, [1, ifm_dim, ifm_dim, ifm_ch] - ) - outp = helper.make_tensor_value_info( - "outp", TensorProto.FLOAT, [1, ofm_dim, ofm_dim, k * k * ifm_ch] - ) - - Im2Col_node = helper.make_node( - "Im2Col", - ["inp"], - ["outp"], - domain="finn", - stride=stride, - kernel_size=k, - pad_amount=pad_amt, - pad_value=pad_val, - input_shape="(1,{},{},{})".format(ifm_dim, ifm_dim, ifm_ch), - ) - - graph = helper.make_graph( - nodes=[Im2Col_node], name="im2col_graph", inputs=[inp], outputs=[outp] - ) - - model = helper.make_model(graph, producer_name="im2col-model") - model = ModelWrapper(model) - - model.set_tensor_datatype("inp", idt) - - # test shape inference - model.transform(InferShapes()) - assert model.get_tensor_shape("outp") == [1, ofm_dim, ofm_dim, k * k * ifm_ch] - - # test datatype inference - assert model.get_tensor_datatype("outp") is DataType.FLOAT32 - model = model.transform(InferDataTypes()) - assert model.get_tensor_datatype("outp") is idt - - # prepare input data - input_dict = {"inp": x} - - # execute model - y_produced = oxe.execute_onnx(model, input_dict)["outp"] - - return y_produced - - -def test_im2col(): - # bipolar inputs with following im2col parameters - idt = DataType.BIPOLAR - k = 2 - stride = 1 - ifm_ch = 1 - ifm_dim = 4 - pad_amt = 0 - pad_val = 0 - ofm_dim = compute_conv_output_dim(ifm_dim, k, stride, pad_amt) - - x = np.asarray( - [ - -1.0, - -1.0, - 1.0, - 1.0, - 1.0, - -1.0, - 1.0, - -1.0, - -1.0, - 1.0, - -1.0, - -1.0, - 1.0, - 1.0, - 1.0, - 1.0, - ], - dtype=np.float32, - ).reshape(1, ifm_dim, ifm_dim, ifm_ch) - - expected = np.asarray( - [ - -1.0, - -1.0, - 1.0, - -1.0, - -1.0, - 1.0, - -1.0, - 1.0, - 1.0, - 1.0, - 1.0, - -1.0, - 1.0, - -1.0, - -1.0, - 1.0, - -1.0, - 1.0, - 1.0, - -1.0, - 1.0, - -1.0, - -1.0, - -1.0, - -1.0, - 1.0, - 1.0, - 1.0, - 1.0, - -1.0, - 1.0, - 1.0, - -1.0, - -1.0, - 1.0, - 1.0, - ], - dtype=np.float32, - ).reshape(1, ofm_dim, ofm_dim, k * k * ifm_ch) - - produced = execution_im2col(x, idt, k, stride, ifm_ch, ifm_dim, pad_amt, pad_val) - assert (produced == expected).all() - - idt = DataType.INT8 - k = 2 - stride = 1 - ifm_ch = 2 - ifm_dim = 4 - pad_amt = 0 - pad_val = 0 - ofm_dim = compute_conv_output_dim(ifm_dim, k, stride, pad_amt) - - x = np.asarray( - [ - [ - [[1, -1], [2, -2], [3, -3], [4, -4]], - [[5, -5], [6, -6], [7, -7], [8, -8]], - [[9, -9], [10, -10], [11, -11], [12, -12]], - [[13, -13], [14, -14], [15, -15], [16, -16]], - ] - ], - dtype=np.float32, - ) - - expected = np.asarray( - [ - [ - [ - [1.0, -1.0, 2.0, -2.0, 5.0, -5.0, 6.0, -6.0], - [2.0, -2.0, 3.0, -3.0, 6.0, -6.0, 7.0, -7.0], - [3.0, -3.0, 4.0, -4.0, 7.0, -7.0, 8.0, -8.0], - ], - [ - [5.0, -5.0, 6.0, -6.0, 9.0, -9.0, 10.0, -10.0], - [6.0, -6.0, 7.0, -7.0, 10.0, -10.0, 11.0, -11.0], - [7.0, -7.0, 8.0, -8.0, 11.0, -11.0, 12.0, -12.0], - ], - [ - [9.0, -9.0, 10.0, -10.0, 13.0, -13.0, 14.0, -14.0], - [10.0, -10.0, 11.0, -11.0, 14.0, -14.0, 15.0, -15.0], - [11.0, -11.0, 12.0, -12.0, 15.0, -15.0, 16.0, -16.0], - ], - ] - ], - dtype=np.float32, - ) - - produced = execution_im2col(x, idt, k, stride, ifm_ch, ifm_dim, pad_amt, pad_val) - assert (produced == expected).all() - - idt = DataType.INT8 - k = 2 - stride = 1 - ifm_ch = 2 - ifm_dim = 4 - pad_amt = 1 - pad_val = 0 - ofm_dim = compute_conv_output_dim(ifm_dim, k, stride, pad_amt) - - x = np.asarray( - [ - [ - [[1, -1], [2, -2], [3, -3], [4, -4]], - [[5, -5], [6, -6], [7, -7], [8, -8]], - [[9, -9], [10, -10], [11, -11], [12, -12]], - [[13, -13], [14, -14], [15, -15], [16, -16]], - ] - ], - dtype=np.float32, - ) - - expected = np.asarray( - [ - [ - [ - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, -1.0], - [0.0, 0.0, 0.0, 0.0, 1.0, -1.0, 2.0, -2.0], - [0.0, 0.0, 0.0, 0.0, 2.0, -2.0, 3.0, -3.0], - [0.0, 0.0, 0.0, 0.0, 3.0, -3.0, 4.0, -4.0], - [0.0, 0.0, 0.0, 0.0, 4.0, -4.0, 0.0, 0.0], - ], - [ - [0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 5.0, -5.0], - [1.0, -1.0, 2.0, -2.0, 5.0, -5.0, 6.0, -6.0], - [2.0, -2.0, 3.0, -3.0, 6.0, -6.0, 7.0, -7.0], - [3.0, -3.0, 4.0, -4.0, 7.0, -7.0, 8.0, -8.0], - [4.0, -4.0, 0.0, 0.0, 8.0, -8.0, 0.0, 0.0], - ], - [ - [0.0, 0.0, 5.0, -5.0, 0.0, 0.0, 9.0, -9.0], - [5.0, -5.0, 6.0, -6.0, 9.0, -9.0, 10.0, -10.0], - [6.0, -6.0, 7.0, -7.0, 10.0, -10.0, 11.0, -11.0], - [7.0, -7.0, 8.0, -8.0, 11.0, -11.0, 12.0, -12.0], - [8.0, -8.0, 0.0, 0.0, 12.0, -12.0, 0.0, 0.0], - ], - [ - [0.0, 0.0, 9.0, -9.0, 0.0, 0.0, 13.0, -13.0], - [9.0, -9.0, 10.0, -10.0, 13.0, -13.0, 14.0, -14.0], - [10.0, -10.0, 11.0, -11.0, 14.0, -14.0, 15.0, -15.0], - [11.0, -11.0, 12.0, -12.0, 15.0, -15.0, 16.0, -16.0], - [12.0, -12.0, 0.0, 0.0, 16.0, -16.0, 0.0, 0.0], - ], - [ - [0.0, 0.0, 13.0, -13.0, 0.0, 0.0, 0.0, 0.0], - [13.0, -13.0, 14.0, -14.0, 0.0, 0.0, 0.0, 0.0], - [14.0, -14.0, 15.0, -15.0, 0.0, 0.0, 0.0, 0.0], - [15.0, -15.0, 16.0, -16.0, 0.0, 0.0, 0.0, 0.0], - [16.0, -16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ], - ] - ], - dtype=np.float32, - ) - - produced = execution_im2col(x, idt, k, stride, ifm_ch, ifm_dim, pad_amt, pad_val) - assert (produced == expected).all() - - -def test_im2col_infer_shapes(): - idt = DataType.BIPOLAR - k = 2 - stride = 1 - ifm_ch = 1 - ifm_dim = 4 - ofm_dim = int(((ifm_dim - k) / stride) + 1) - - # set up onnx model - inp = helper.make_tensor_value_info( - "inp", TensorProto.FLOAT, [1, ifm_dim, ifm_dim, ifm_ch] - ) - outp = helper.make_tensor_value_info( - "outp", TensorProto.FLOAT, [1, ofm_dim, ofm_dim, k * k * ifm_ch] - ) - - abs_node = helper.make_node("Abs", inputs=["inp"], outputs=["abs"]) - - Im2Col_node = helper.make_node( - "Im2Col", - ["abs"], - ["im2col"], - domain="finn", - stride=stride, - kernel_size=k, - input_shape="(1,{},{},{})".format(ifm_dim, ifm_dim, ifm_ch), - ) - - abs1_node = helper.make_node("Abs", inputs=["im2col"], outputs=["outp"]) - - graph = helper.make_graph( - nodes=[abs_node, Im2Col_node, abs1_node], - name="shape_graph", - inputs=[inp], - outputs=[outp], - value_info=[ - helper.make_tensor_value_info( - "abs", TensorProto.FLOAT, [1, ifm_dim, ifm_dim, ifm_ch] - ), - helper.make_tensor_value_info( - "im2col", TensorProto.FLOAT, [1, ofm_dim, ofm_dim, k * k * ifm_ch] - ), - ], - ) - - model = helper.make_model(graph, producer_name="shape-model") - model = ModelWrapper(model) - - model.set_tensor_datatype("inp", idt) - - # test shape inference - model.transform(InferShapes()) - assert model.get_tensor_shape("im2col") == [1, ofm_dim, ofm_dim, k * k * ifm_ch] diff --git a/tests/custom_op/test_multithreshold.py b/tests/custom_op/test_multithreshold.py deleted file mode 100644 index 7e6ad4fe08517290dd22a2c74b2847d007b74b1f..0000000000000000000000000000000000000000 --- a/tests/custom_op/test_multithreshold.py +++ /dev/null @@ -1,322 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 time -from finn.custom_op.multithreshold import multithreshold - - -def compare(x, y): - """Comparison helper function for multithresholding. - - Gets two values and returns 1.0 if x>=y otherwise 0.0.""" - if x >= y: - return 1.0 - else: - return 0.0 - -# naive implementation of thresholding for performance comparison -def multithreshold_elementwise(v, thresholds, out_scale=None, out_bias=None): - """Given a set of threshold values t={t_0, t_1 ... t_n} the successive - thresholding maps any real number x to an integer in the interval [0, n], - where the returned integer is the number of thresholds x is greater than - or equal to. - - The output tensor will be scaled by out_scale and biased by out_bias.""" - # the inputs are expected to be in the shape (N,C,H,W) or (N, C) - # the MultiThreshold node supports a data_layout attribute that can be set - # to 'NHWC' to support (N,H,W,C) data layout mode for in-out as well - # N : Batch size - # C : Number of channels - # H : Heigth of the input images - # W : Width of the input images - # - # the thresholds are expected to be in the shape (C, B) - # C : Number of channels (must be the same value as C in input tensor - # or 1 if all channels use the same threshold value) - # B : Desired activation steps => i.e. for 4-bit activation, - # B=7 (2^(n)-1 and n=4) - # the output tensor will be scaled by out_scale and biased by out_bias - # assert threshold shape - is_global_threshold = thresholds.shape[0] == 1 - assert ( - v.shape[1] == thresholds.shape[0] - ) or is_global_threshold, """"Threshold - shape incorrect""" - # save the required shape sizes for the loops (N, C and B) - num_batch = v.shape[0] - num_channel = v.shape[1] - num_act = thresholds.shape[1] - # reshape inputs to enable channel-wise reading - vr = v.reshape((v.shape[0], v.shape[1], -1)) - # save the new shape size of the images - num_img_elem = vr.shape[2] - # initiate output tensor - ret = np.zeros_like(vr) - # iterate over thresholds channel-wise - for t in range(num_channel): - channel_thresh = thresholds[0] if is_global_threshold else thresholds[t] - # iterate over batches - for b in range(num_batch): - # iterate over image elements on which the thresholds will be applied - for elem in range(num_img_elem): - # iterate over the different thresholds for one channel - for a in range(num_act): - # apply successive thresholding to every element - ret[b][t][elem] += compare(vr[b][t][elem], channel_thresh[a]) - if out_scale is None: - out_scale = 1.0 - if out_bias is None: - out_bias = 0.0 - return out_scale * ret.reshape(v.shape) + out_bias - - -def test_multithreshold(): - - inputs = np.ndarray( - shape=(6, 3, 2, 2), - buffer=np.array( - [ - 4.8, - 3.2, - 1.2, - 4.9, - 7.8, - 2.4, - 3.1, - 4.7, - 6.2, - 5.1, - 4.9, - 2.2, - 6.2, - 0.0, - 0.8, - 4.7, - 0.2, - 5.6, - 8.9, - 9.2, - 9.1, - 4.0, - 3.3, - 4.9, - 2.3, - 1.7, - 1.3, - 2.2, - 4.6, - 3.4, - 3.7, - 9.8, - 4.7, - 4.9, - 2.8, - 2.7, - 8.3, - 6.7, - 4.2, - 7.1, - 2.8, - 3.1, - 0.8, - 0.6, - 4.4, - 2.7, - 6.3, - 6.1, - 1.4, - 5.3, - 2.3, - 1.9, - 4.7, - 8.1, - 9.3, - 3.7, - 2.7, - 5.1, - 4.2, - 1.8, - 4.1, - 7.3, - 7.1, - 0.4, - 0.2, - 1.3, - 4.3, - 8.9, - 1.4, - 1.6, - 8.3, - 9.4, - ] - ), - ) - - thresholds = np.ndarray( - shape=(3, 7), - buffer=np.array( - [ - 0.8, - 1.4, - 1.7, - 3.5, - 5.2, - 6.8, - 8.2, - 0.2, - 2.2, - 3.5, - 4.5, - 6.6, - 8.6, - 9.2, - 1.3, - 4.1, - 4.5, - 6.5, - 7.8, - 8.1, - 8.9, - ] - ), - ) - - outputs = np.ndarray( - shape=(6, 3, 2, 2), - buffer=np.array( - [ - 4.0, - 3.0, - 1.0, - 4.0, - 5.0, - 2.0, - 2.0, - 4.0, - 3.0, - 3.0, - 3.0, - 1.0, - 5.0, - 0.0, - 1.0, - 4.0, - 1.0, - 4.0, - 6.0, - 7.0, - 7.0, - 1.0, - 1.0, - 3.0, - 3.0, - 3.0, - 1.0, - 3.0, - 4.0, - 2.0, - 3.0, - 7.0, - 3.0, - 3.0, - 1.0, - 1.0, - 7.0, - 5.0, - 4.0, - 6.0, - 2.0, - 2.0, - 1.0, - 1.0, - 2.0, - 1.0, - 3.0, - 3.0, - 2.0, - 5.0, - 3.0, - 3.0, - 4.0, - 5.0, - 7.0, - 3.0, - 1.0, - 3.0, - 2.0, - 1.0, - 4.0, - 6.0, - 6.0, - 0.0, - 1.0, - 1.0, - 3.0, - 6.0, - 1.0, - 1.0, - 6.0, - 7.0, - ] - ), - ) - - results = multithreshold(inputs, thresholds) - assert (results == outputs).all() - - results_scaled = multithreshold(inputs, thresholds, 2.0, -1.0) - outputs_scaled = 2.0 * outputs - 1.0 - assert (results_scaled == outputs_scaled).all() - - # performance and random test - np.random.seed(0) - inputs = np.random.random((1, 256, 64, 64)) - thresholds = (np.array([[1, 2, 3, 4, 5, 6]]) - 0.5) / 6 - - before = time.time() - vec_results = multithreshold(inputs, thresholds) - after = time.time() - vector_runtime = after - before - - before = time.time() - nonvec_results = multithreshold_elementwise(inputs, thresholds) - after = time.time() - non_vector_runtime = after - before - - assert (vec_results == nonvec_results).all() - - return vector_runtime, non_vector_runtime - - -if __name__ == "__main__": - vector_runtime, non_vector_runtime = test_multithreshold() - - print("Runtime non-vectorized: ", non_vector_runtime, "s") - print("Runtime vectorized: ", vector_runtime, "s") - print("Speed-up: ", non_vector_runtime / vector_runtime) diff --git a/tests/custom_op/test_xnorpopcountmatmul.py b/tests/custom_op/test_xnorpopcountmatmul.py deleted file mode 100644 index 745b782d418129d96e21c327a49de04d53aa7c48..0000000000000000000000000000000000000000 --- a/tests/custom_op/test_xnorpopcountmatmul.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -from pkgutil import get_data - -import brevitas.onnx as bo -import numpy as np -import onnx -import onnx.helper as helper -import onnx.numpy_helper as nph -from onnx import TensorProto - -import finn.core.onnx_exec as oxe -from finn.core.datatype import DataType -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.bipolar_to_xnor import ConvertBipolarMatMulToXnorPopcount -from finn.transformation.fold_constants import FoldConstants -from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames -from finn.transformation.infer_datatypes import InferDataTypes -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.streamline.sign_to_thres import ConvertSignToThres -from finn.util.test import get_test_model_trained - -export_onnx_path = "test_xnorpopcountmatmul.onnx" - - -def test_xnorpopcountmatmul(): - M = 1 - K = 3 - N = 3 - x = helper.make_tensor_value_info("x", TensorProto.FLOAT, [M, K]) - W = helper.make_tensor_value_info("W", TensorProto.FLOAT, [K, N]) - out = helper.make_tensor_value_info("out", TensorProto.FLOAT, ["x", "y"]) - node_def = helper.make_node( - "XnorPopcountMatMul", ["x", "W"], ["out"], domain="finn" - ) - modelproto = helper.make_model( - helper.make_graph([node_def], "test_model", [x], [out], value_info=[W]) - ) - model = ModelWrapper(modelproto) - model.set_tensor_datatype("x", DataType.BINARY) - model.set_tensor_datatype("W", DataType.BINARY) - W_data = np.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32) - model.set_initializer("W", W_data) - # test shape inference - model = model.transform(InferShapes()) - assert model.get_tensor_shape("out") == [M, N] - # test datatype inference - assert model.get_tensor_datatype("out") is DataType.FLOAT32 - model = model.transform(InferDataTypes()) - assert model.get_tensor_datatype("out") is DataType.UINT32 - # test execution - x_data = np.asarray([[1, 0, 0]], dtype=np.float32) - inp_dict = {"x": x_data} - out_dict = oxe.execute_onnx(model, inp_dict) - Wb = 2 * W_data - 1 - xb = 2 * x_data - 1 - rb = np.matmul(xb, Wb) - assert (2 * out_dict["out"] - K == rb).all() - - -def test_convert_bipolar_matmul_to_xnorpopcountmatmul(): - 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()) - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveReadableTensorNames()) - model = model.transform(ConvertSignToThres()) - # 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) - # run using FINN-based execution - input_dict = {"global_in": nph.to_array(input_tensor)} - expected_ctx = oxe.execute_onnx(model, input_dict, True) - expected = expected_ctx[model.graph.output[0].name] - model = model.transform(ConvertBipolarMatMulToXnorPopcount()) - produced_ctx = oxe.execute_onnx(model, input_dict, True) - produced = produced_ctx[model.graph.output[0].name] - assert np.isclose(expected, produced, atol=1e-3).all() - os.remove(export_onnx_path) diff --git a/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py b/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py index e8b50efef0723c1394c2bdd438a87e090071507d..20751a5877a879eeabf1ed6b67a7573208cf9367 100644 --- a/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py +++ b/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py @@ -73,7 +73,7 @@ def test_convert_to_hls_layers_cnv_w1a1(fused_activation): model = model.transform(InferDataLayouts()) # model.save("golden.onnx") # load one of the test vectors - fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") + fn = pk.resource_filename("finn.qnn-data", "cifar10/cifar10-test-data-class3.npz") input_tensor = np.load(fn)["arr_0"].astype(np.float32) input_tensor = input_tensor / 255 assert input_tensor.shape == (1, 3, 32, 32) diff --git a/tests/fpgadataflow/test_convert_to_hls_layers_fc.py b/tests/fpgadataflow/test_convert_to_hls_layers_fc.py index bd600c6c57d00d5fc03152f75b9f2f8c6beeeb2c..cb66fa7237416579b509aa4f508c9105d386d08a 100644 --- a/tests/fpgadataflow/test_convert_to_hls_layers_fc.py +++ b/tests/fpgadataflow/test_convert_to_hls_layers_fc.py @@ -110,7 +110,7 @@ def test_convert_to_hls_layers_tfc_w1a1(): model = model.transform(CompileCppSim()) model = model.transform(SetExecMode("cppsim")) - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") + 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) # run using FINN-based execution input_dict = {"global_in": nph.to_array(input_tensor)} @@ -175,7 +175,7 @@ def test_convert_to_hls_layers_tfc_w1a2(): model = model.transform(PrepareCppSim()) model = model.transform(CompileCppSim()) model = model.transform(SetExecMode("cppsim")) - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") + 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) # run using FINN-based execution input_dict = {"global_in": nph.to_array(input_tensor)} diff --git a/tests/fpgadataflow/test_create_dataflow_partition.py b/tests/fpgadataflow/test_create_dataflow_partition.py index c4f748051ff038371353574298580f3bf9e05e9f..6732b92ae0865e390002bd3c65dfefe3890610e2 100644 --- a/tests/fpgadataflow/test_create_dataflow_partition.py +++ b/tests/fpgadataflow/test_create_dataflow_partition.py @@ -45,7 +45,7 @@ build_dir = make_build_dir("test_dataflow_partition_") def test_dataflow_partition_create(): # load the onnx model raw_m = get_data( - "finn", "data/onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx" + "finn.qnn-data", "onnx/finn-hls-model/tfc_w1_a1_after_conv_to_hls.onnx" ) model = ModelWrapper(raw_m) model = model.transform(CreateDataflowPartition()) diff --git a/tests/util/test_shape_utils.py b/tests/test_enforce_import_order.py similarity index 78% rename from tests/util/test_shape_utils.py rename to tests/test_enforce_import_order.py index ab58f591f39234b659de7c05f2592c76ebb73a3e..3859a6ca38c9d69e9d0b2ab5dd331077fe5a7f36 100644 --- a/tests/util/test_shape_utils.py +++ b/tests/test_enforce_import_order.py @@ -26,16 +26,18 @@ # OR TORT (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 finn.util.basic as util +# IMPORTANT: +# - do not move this file to subfolders +# pytest must discover it before other FINN tests, can be checked with: +# pytest --collect-only +# - do not change the order of imports below +# this is to workaround the onnx 1.6.0/pytorch bug +# https://github.com/onnx/onnx/issues/2394#issuecomment-581638840 +import onnx # noqa +import torch # noqa -def test_interleave_matrix_outer_dim_from_partitions(): - A = np.eye(10) - n_parts = 2 - Ax = util.interleave_matrix_outer_dim_from_partitions(A, n_parts) - part_size = 10 // n_parts - assert Ax.shape == (n_parts, part_size, 10) - for r_ind in range(A.shape[0]): - assert (A[r_ind] == Ax[r_ind % n_parts][r_ind // n_parts]).all() + +def test_enforce_import_order(): + assert True diff --git a/tests/transformation/test_absorb_mul_into_topk.py b/tests/transformation/streamline/test_absorb_mul_into_topk.py similarity index 100% rename from tests/transformation/test_absorb_mul_into_topk.py rename to tests/transformation/streamline/test_absorb_mul_into_topk.py diff --git a/tests/transformation/test_absorb_opposite_transposes.py b/tests/transformation/streamline/test_absorb_opposite_transposes.py similarity index 100% rename from tests/transformation/test_absorb_opposite_transposes.py rename to tests/transformation/streamline/test_absorb_opposite_transposes.py diff --git a/tests/transformation/test_absorb_transp_into_flatten.py b/tests/transformation/streamline/test_absorb_transp_into_flatten.py similarity index 100% rename from tests/transformation/test_absorb_transp_into_flatten.py rename to tests/transformation/streamline/test_absorb_transp_into_flatten.py diff --git a/tests/transformation/test_collapse_repeated_op.py b/tests/transformation/streamline/test_collapse_repeated_op.py similarity index 100% rename from tests/transformation/test_collapse_repeated_op.py rename to tests/transformation/streamline/test_collapse_repeated_op.py diff --git a/tests/transformation/test_factor_out_mul_sign_magnitude.py b/tests/transformation/streamline/test_factor_out_mul_sign_magnitude.py similarity index 100% rename from tests/transformation/test_factor_out_mul_sign_magnitude.py rename to tests/transformation/streamline/test_factor_out_mul_sign_magnitude.py diff --git a/tests/transformation/test_linear_past_eltwise.py b/tests/transformation/streamline/test_linear_past_eltwise.py similarity index 100% rename from tests/transformation/test_linear_past_eltwise.py rename to tests/transformation/streamline/test_linear_past_eltwise.py diff --git a/tests/transformation/test_move_add_past_mul.py b/tests/transformation/streamline/test_move_add_past_mul.py similarity index 100% rename from tests/transformation/test_move_add_past_mul.py rename to tests/transformation/streamline/test_move_add_past_mul.py diff --git a/tests/transformation/test_move_chw_add_past_conv.py b/tests/transformation/streamline/test_move_chw_add_past_conv.py similarity index 100% rename from tests/transformation/test_move_chw_add_past_conv.py rename to tests/transformation/streamline/test_move_chw_add_past_conv.py diff --git a/tests/transformation/test_move_flatten_past_affine.py b/tests/transformation/streamline/test_move_flatten_past_affine.py similarity index 100% rename from tests/transformation/test_move_flatten_past_affine.py rename to tests/transformation/streamline/test_move_flatten_past_affine.py diff --git a/tests/transformation/test_move_flatten_past_topk.py b/tests/transformation/streamline/test_move_flatten_past_topk.py similarity index 100% rename from tests/transformation/test_move_flatten_past_topk.py rename to tests/transformation/streamline/test_move_flatten_past_topk.py diff --git a/tests/transformation/test_move_maxpool_past_multithreshold.py b/tests/transformation/streamline/test_move_maxpool_past_multithreshold.py similarity index 100% rename from tests/transformation/test_move_maxpool_past_multithreshold.py rename to tests/transformation/streamline/test_move_maxpool_past_multithreshold.py diff --git a/tests/transformation/test_move_mul_past_dw_conv.py b/tests/transformation/streamline/test_move_mul_past_dw_conv.py similarity index 100% rename from tests/transformation/test_move_mul_past_dw_conv.py rename to tests/transformation/streamline/test_move_mul_past_dw_conv.py diff --git a/tests/transformation/test_move_past_fork.py b/tests/transformation/streamline/test_move_past_fork.py similarity index 100% rename from tests/transformation/test_move_past_fork.py rename to tests/transformation/streamline/test_move_past_fork.py diff --git a/tests/transformation/test_move_scalar_past_conv.py b/tests/transformation/streamline/test_move_scalar_past_conv.py similarity index 100% rename from tests/transformation/test_move_scalar_past_conv.py rename to tests/transformation/streamline/test_move_scalar_past_conv.py diff --git a/tests/transformation/test_move_scalar_past_matmul.py b/tests/transformation/streamline/test_move_scalar_past_matmul.py similarity index 100% rename from tests/transformation/test_move_scalar_past_matmul.py rename to tests/transformation/streamline/test_move_scalar_past_matmul.py diff --git a/tests/transformation/test_move_transpose_past_scalar_mul.py b/tests/transformation/streamline/test_move_transpose_past_scalar_mul.py similarity index 100% rename from tests/transformation/test_move_transpose_past_scalar_mul.py rename to tests/transformation/streamline/test_move_transpose_past_scalar_mul.py diff --git a/tests/transformation/test_remove_identity_ops.py b/tests/transformation/streamline/test_remove_identity_ops.py similarity index 100% rename from tests/transformation/test_remove_identity_ops.py rename to tests/transformation/streamline/test_remove_identity_ops.py diff --git a/tests/transformation/test_round_thresholds.py b/tests/transformation/streamline/test_round_thresholds.py similarity index 100% rename from tests/transformation/test_round_thresholds.py rename to tests/transformation/streamline/test_round_thresholds.py diff --git a/tests/transformation/test_sign_to_thres.py b/tests/transformation/streamline/test_sign_to_thres.py similarity index 97% rename from tests/transformation/test_sign_to_thres.py rename to tests/transformation/streamline/test_sign_to_thres.py index a92f839e5f6ca8b45eadf939fa35973ac153e0b1..4618dffc43f5cee848b580b77cf418c612b48f3e 100644 --- a/tests/transformation/test_sign_to_thres.py +++ b/tests/transformation/streamline/test_sign_to_thres.py @@ -52,7 +52,7 @@ def test_sign_to_thres(): 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") + 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) diff --git a/tests/transformation/streamline/test_streamline_cnv.py b/tests/transformation/streamline/test_streamline_cnv.py index 82a38636e3927e17e5e2a3e8714f46082bba10e4..ca8cf3b1ceba6943828f47bcbcf974aa5b368c4e 100644 --- a/tests/transformation/streamline/test_streamline_cnv.py +++ b/tests/transformation/streamline/test_streamline_cnv.py @@ -67,7 +67,7 @@ def test_streamline_cnv(size, wbits, abits): model = model.transform(GiveReadableTensorNames()) model = model.transform(RemoveStaticGraphInputs()) # load one of the test vectors - fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") + fn = pk.resource_filename("finn.qnn-data", "cifar10/cifar10-test-data-class3.npz") input_tensor = np.load(fn)["arr_0"].astype(np.float32) input_tensor = input_tensor / 255 assert input_tensor.shape == (1, 3, 32, 32) diff --git a/tests/transformation/streamline/test_streamline_fc.py b/tests/transformation/streamline/test_streamline_fc.py index 9ce98066cfbf9d1c64514b957d8a260705fd0d7c..d88bf14913d2551cd7347c5617895998a7d56799 100644 --- a/tests/transformation/streamline/test_streamline_fc.py +++ b/tests/transformation/streamline/test_streamline_fc.py @@ -72,7 +72,7 @@ def test_streamline_fc(size, wbits, abits): model = model.transform(GiveReadableTensorNames()) model = model.transform(RemoveStaticGraphInputs()) # load one of the test vectors - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") + 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) # run using FINN-based execution input_dict = {"global_in": nph.to_array(input_tensor)} diff --git a/tests/transformation/test_batchnorm_to_affine.py b/tests/transformation/test_batchnorm_to_affine_bnn_pynq.py similarity index 96% rename from tests/transformation/test_batchnorm_to_affine.py rename to tests/transformation/test_batchnorm_to_affine_bnn_pynq.py index a3df5ae9bbd3f99bc29bc088a5f461122af06d81..7e894c078b15c16f29dec60d694f8b6892e84a8a 100644 --- a/tests/transformation/test_batchnorm_to_affine.py +++ b/tests/transformation/test_batchnorm_to_affine_bnn_pynq.py @@ -51,7 +51,7 @@ def test_batchnorm_to_affine_cnv_w1a1(): model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) - fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") + fn = pk.resource_filename("finn.qnn-data", "cifar10/cifar10-test-data-class3.npz") input_tensor = np.load(fn)["arr_0"].astype(np.float32) input_tensor = input_tensor / 255 assert input_tensor.shape == (1, 3, 32, 32) @@ -77,7 +77,7 @@ def test_batchnorm_to_affine_lfc_w1a1(): 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") + 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) diff --git a/tests/transformation/test_change_datalayout.py b/tests/transformation/test_change_datalayout.py deleted file mode 100644 index 66459d574957575e61ec1bec631fb7030a27cca1..0000000000000000000000000000000000000000 --- a/tests/transformation/test_change_datalayout.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 pytest -from onnx import helper, TensorProto - -from finn.custom_op.maxpoolnhwc import compute_pool_output_dim -from finn.core.modelwrapper import ModelWrapper -from finn.core.datatype import DataType -import finn.core.data_layout as DataLayout -from finn.transformation.change_datalayout import ChangeDataLayoutQuantAvgPool2d -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.infer_datatypes import InferDataTypes -from finn.transformation.infer_data_layouts import InferDataLayouts -from finn.transformation.general import GiveUniqueNodeNames, GiveReadableTensorNames -from finn.util.basic import gen_finn_dt_tensor -from finn.util.basic import get_by_name -import finn.core.onnx_exec as oxe - -# stride -@pytest.mark.parametrize("s", [1, 2]) -# kernel -@pytest.mark.parametrize("k", [3, 4]) -# ibits -@pytest.mark.parametrize("ibits", [4, 8]) -# obits -@pytest.mark.parametrize("obits", [2, 4]) -# signed -@pytest.mark.parametrize("signed", [False, True]) -# channels -@pytest.mark.parametrize("c", [2, 3]) -# input dimension -@pytest.mark.parametrize("idim", [6, 7]) -def test_change_datalayout_quantavgpool(s, k, ibits, obits, signed, c, idim): - n = 1 - odim = compute_pool_output_dim(idim, k, s) - # determine input FINN datatype - if signed is True: - prefix = "INT" - else: - prefix = "UINT" - dt_name = prefix + str(ibits) - dtype = DataType[dt_name] - - inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, [n, c, idim, idim]) - outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, [n, c, odim, odim]) - - node = helper.make_node( - "QuantAvgPool2d", - ["inp"], - ["outp"], - domain="finn", - stride=s, - kernel=k, - ibits=ibits, - obits=obits, - signed=signed, - data_layout="NCHW", - ) - graph = helper.make_graph( - nodes=[node], name="single-quantavgpool", inputs=[inp], outputs=[outp] - ) - - model = helper.make_model(graph) - model = ModelWrapper(model) - model = model.transform(InferShapes()) - model = model.transform(InferDataTypes()) - model = model.transform(InferDataLayouts()) - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveReadableTensorNames()) - model_transformed = model.transform(ChangeDataLayoutQuantAvgPool2d()) - model_transformed = model_transformed.transform(InferShapes()) - model_transformed = model_transformed.transform(InferDataTypes()) - model_transformed = model_transformed.transform(InferDataLayouts()) - model_transformed = model_transformed.transform(GiveUniqueNodeNames()) - model_transformed = model_transformed.transform(GiveReadableTensorNames()) - inp_values = gen_finn_dt_tensor(dtype, [n, c, idim, idim]) - idict = {"inp": inp_values} - assert oxe.compare_execution(model, model_transformed, idict) - assert len(model.graph.node) + 2 == len(model_transformed.graph.node) - assert model_transformed.graph.node[-1].op_type == "Transpose" - assert model_transformed.graph.node[0].op_type == "Transpose" - # check if QuantAvgPool2d node has datalayout set correctly - node = model_transformed.graph.node[1] - d_layout = get_by_name(node.attribute, "data_layout").s.decode("UTF-8") - assert d_layout == "NHWC" - assert model_transformed.get_tensor_layout(node.input[0]) == DataLayout.NHWC - assert model_transformed.get_tensor_layout(node.output[0]) == DataLayout.NHWC diff --git a/tests/transformation/test_conv_lowering.py b/tests/transformation/test_conv_lowering.py deleted file mode 100644 index b6ab634b374dea3ba309bbf12654c73c0a90e36c..0000000000000000000000000000000000000000 --- a/tests/transformation/test_conv_lowering.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 pytest -import onnx.helper as oh -from onnx import TensorProto -import os -import pkg_resources as pk -import brevitas.onnx as bo -import numpy as np - -from finn.core.modelwrapper import ModelWrapper -from finn.core.datatype import DataType -from finn.transformation.fold_constants import FoldConstants -from finn.transformation.infer_shapes import InferShapes -from finn.util.test import get_test_model_trained -from finn.transformation.lower_convs_to_matmul import LowerConvsToMatMul -import finn.core.onnx_exec as oxe -from finn.custom_op.im2col import compute_conv_output_dim -from finn.util.basic import gen_finn_dt_tensor -from finn.custom_op.registry import getCustomOp - -export_onnx_path = "test_conv_lowering.onnx" - - -def test_conv_lowering_cnv_w1a1(): - cnv = get_test_model_trained("CNV", 1, 1) - bo.export_finn_onnx(cnv, (1, 3, 32, 32), export_onnx_path) - model = ModelWrapper(export_onnx_path) - model = model.transform(InferShapes()) - model = model.transform(FoldConstants()) - fn = pk.resource_filename("finn", "data/cifar10/cifar10-test-data-class3.npz") - input_tensor = np.load(fn)["arr_0"].astype(np.float32) - input_tensor = input_tensor / 255 - assert input_tensor.shape == (1, 3, 32, 32) - # execute imported model to get expected answer - input_dict = {"0": input_tensor} - output_dict_e = oxe.execute_onnx(model, input_dict) - expected = output_dict_e[list(output_dict_e.keys())[0]] - # execute transformed model and compare - model = model.transform(LowerConvsToMatMul()) - output_dict_p = oxe.execute_onnx(model, input_dict) - produced = output_dict_p[list(output_dict_p.keys())[0]] - assert np.isclose(produced, expected).all() - assert np.argmax(produced) == 3 - os.remove(export_onnx_path) - - -# input datatype -@pytest.mark.parametrize("idt", [DataType.INT2, DataType.INT4]) -# kernel size -@pytest.mark.parametrize("k", [2, 4]) -# input dimension -@pytest.mark.parametrize("ifm_dim", [4, 6]) -# input channels -@pytest.mark.parametrize("ifm_ch", [2, 3]) -# stride -@pytest.mark.parametrize("stride", [1, 2]) -# padding -@pytest.mark.parametrize("padding", [[0, 0, 0, 0], [1, 1, 1, 1]]) -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_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 diff --git a/tests/transformation/test_fold_constants.py b/tests/transformation/test_fold_constants.py deleted file mode 100644 index a976ffd62bce744a474a6fac2a61a6478526777f..0000000000000000000000000000000000000000 --- a/tests/transformation/test_fold_constants.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 os -from pkgutil import get_data - -import brevitas.onnx as bo -import numpy as np -import onnx -import onnx.numpy_helper as np_helper - -import finn.core.onnx_exec as oxe -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.fold_constants import FoldConstants -from finn.transformation.infer_shapes import InferShapes -from finn.util.test import get_test_model_untrained - -export_onnx_path = "test_fold_constants.onnx" - - -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 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) diff --git a/tests/transformation/test_general_transformation.py b/tests/transformation/test_general_transformation.py deleted file mode 100644 index 153af378eb3e07d5824f114fd194730048fb4953..0000000000000000000000000000000000000000 --- a/tests/transformation/test_general_transformation.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from pkgutil import get_data - -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.general import GiveUniqueNodeNames - -import numpy as np -import onnx -import finn.core.onnx_exec as oxe -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.general import GiveUniqueParameterTensors - - -def test_give_unique_node_names(): - raw_m = get_data("finn", "data/onnx/mnist-conv/model.onnx") - model = ModelWrapper(raw_m) - model = model.transform(GiveUniqueNodeNames()) - assert model.graph.node[0].name == "Reshape_0" - assert model.graph.node[1].name == "Conv_0" - assert model.graph.node[11].name == "Add_2" - - -def test_give_unique_parameter_tensors(): - - # Create model - input_shape = [4, 4] - in1 = onnx.helper.make_tensor_value_info("in1", onnx.TensorProto.FLOAT, input_shape) - out1 = onnx.helper.make_tensor_value_info( - "out1", onnx.TensorProto.FLOAT, input_shape - ) - - graph_nodes = [] - graph_nodes += [ - onnx.helper.make_node("Add", inputs=["in1", "param1"], outputs=["t1"]) - ] - - graph_nodes += [ - onnx.helper.make_node("Sum", inputs=["t1", "param1", "param1"], outputs=["t2"]) - ] - - graph_nodes += [ - onnx.helper.make_node("Sum", inputs=["t2", "param2", "param1"], outputs=["t3"]) - ] - - graph_nodes += [ - onnx.helper.make_node("Add", inputs=["t3", "param1"], outputs=["out1"]) - ] - - onnx_graph = onnx.helper.make_graph( - nodes=graph_nodes, name="simple_graph", inputs=[in1], outputs=[out1], - ) - - onnx_model = onnx.helper.make_model(onnx_graph, producer_name="simple-model") - model = ModelWrapper(onnx_model) - - # Set param values - np.random.seed(0) - param1 = np.random.rand(*input_shape).astype(np.float32) - param2 = np.random.rand(*input_shape).astype(np.float32) - model.set_initializer("param1", param1) - model.set_initializer("param2", param2) - model = model.transform(InferShapes()) - - # Apply transformation - new_model = model.transform(GiveUniqueParameterTensors()) - new_model = new_model.transform(InferShapes()) - - # Test - # Breaks the model? - input_tensor = np.random.rand(*input_shape).astype(np.float32) - input_dict = {"in1": input_tensor} - - # run original - expected_context = oxe.execute_onnx(model, input_dict) - expected_output = expected_context[model.graph.output[0].name] - - # run modified - produced_context = oxe.execute_onnx(new_model, input_dict) - produced_output = produced_context[new_model.graph.output[0].name] - - assert np.isclose( - expected_output, produced_output, atol=1e-8 - ).all(), " GiveUniqueParameterTensors() transform breaks the model" - - # Does the job? - param_set = set() - param_cnt = 0 - for n in new_model.graph.node: - for i in range(1, len(n.input)): - param_set |= {n.input[i]} - param_cnt += 1 - - assert len(param_set) == param_cnt, " There are still parameters reused" diff --git a/tests/transformation/test_infer_data_layouts.py b/tests/transformation/test_infer_data_layouts_cnv.py similarity index 99% rename from tests/transformation/test_infer_data_layouts.py rename to tests/transformation/test_infer_data_layouts_cnv.py index 0bc30ea0eb48087606545c86e705328217b004ca..a8ba81dff608994b8e5efb33ec23bd0e3f894175 100644 --- a/tests/transformation/test_infer_data_layouts.py +++ b/tests/transformation/test_infer_data_layouts_cnv.py @@ -46,7 +46,7 @@ import finn.core.data_layout as DataLayout export_onnx_path_cnv = "test_infer_data_layouts.onnx" -def test_infer_data_layouts(): +def test_infer_data_layouts_cnv(): cnv = get_test_model_trained("CNV", 1, 1) bo.export_finn_onnx(cnv, (1, 3, 32, 32), export_onnx_path_cnv) model = ModelWrapper(export_onnx_path_cnv) diff --git a/tests/transformation/test_infer_datatypes.py b/tests/transformation/test_infer_datatypes_lfc.py similarity index 98% rename from tests/transformation/test_infer_datatypes.py rename to tests/transformation/test_infer_datatypes_lfc.py index 097ae03f6153843fbb7956a72b38431559d5d0f1..0802c50c7d15a649182529a4e6897b9bbe273336 100644 --- a/tests/transformation/test_infer_datatypes.py +++ b/tests/transformation/test_infer_datatypes_lfc.py @@ -41,7 +41,7 @@ from finn.util.test import get_test_model_trained export_onnx_path = "test_infer_datatypes.onnx" -def test_infer_datatypes(): +def test_infer_datatypes_lfc(): 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) diff --git a/tests/transformation/test_infer_shapes.py b/tests/transformation/test_infer_shapes.py deleted file mode 100644 index a6ebe540bb5e081178704ec0493d511277562acb..0000000000000000000000000000000000000000 --- a/tests/transformation/test_infer_shapes.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from pkgutil import get_data - -import numpy as np -from onnx import TensorProto, helper - -import finn.util.basic as util -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.infer_shapes import InferShapes - - -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" - ) - 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" diff --git a/tests/transformation/test_merge_onnx_models.py b/tests/transformation/test_merge_onnx_models.py deleted file mode 100644 index db7c990baddfb50a39603937a9c5b73f512a0e59..0000000000000000000000000000000000000000 --- a/tests/transformation/test_merge_onnx_models.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from pkgutil import get_data - -import numpy as np -import onnx -import onnx.numpy_helper as np_helper -from onnx import TensorProto, helper - -from finn.core.modelwrapper import ModelWrapper -from finn.core.datatype import DataType -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.infer_datatypes import InferDataTypes -from finn.transformation.infer_data_layouts import InferDataLayouts -from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames -from finn.transformation.merge_onnx_models import MergeONNXModels -import finn.core.onnx_exec as oxe - - -def test_merge_onnx_models(): - # load pre model - raw_m = get_data("finn", "data/onnx/mnist-conv/model.onnx") - model1 = ModelWrapper(raw_m) - # the input for model1 comes from a uint8 vector so we set the finn datatype - # of the input tensor to DataType.UINT8 to verify that the datatypes are correctly - # preserved in the transformed model - model1.set_tensor_datatype(model1.graph.input[0].name, DataType.UINT8) - model1 = model1.transform(InferShapes()) - model1 = model1.transform(GiveUniqueNodeNames()) - model1 = model1.transform(GiveReadableTensorNames()) - - # set up post model - shape = [1, 10] - inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, shape) - a0 = helper.make_tensor_value_info("a0", TensorProto.FLOAT, []) - a1 = helper.make_tensor_value_info("a1", TensorProto.FLOAT, []) - outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, shape) - - mul_node = helper.make_node("Mul", ["inp", "a0"], ["mul_out"]) - div_node = helper.make_node("Div", ["mul_out", "a1"], ["outp"]) - - graph = helper.make_graph( - nodes=[mul_node, div_node], - name="model2-graph", - inputs=[inp], - outputs=[outp], - value_info=[a0, a1], - ) - - model2 = helper.make_model(graph, producer_name="model2") - model2 = ModelWrapper(model2) - # initialize model2 - a0_value = np.random.uniform(low=0, high=1, size=(1)).astype(np.float32) - model2.set_initializer("a0", a0_value) - a1_value = np.random.uniform(low=0.1, high=1, size=(1)).astype(np.float32) - model2.set_initializer("a1", a1_value) - # set a dummy sparsity annotation to check if it gets correctly transferred - # to the merged model - sparsity = {"dw": {"kernel_shape": 0}} - model2.set_tensor_sparsity("a1", sparsity) - model2 = model2.transform(InferShapes()) - model2 = model2.transform(InferDataTypes()) - model2 = model2.transform(InferDataLayouts()) - model2 = model2.transform(GiveUniqueNodeNames()) - model2 = model2.transform(GiveReadableTensorNames()) - - # simulate the models before the merging and pass the output of model1 to model2 - # load one of the test vectors - raw_i = get_data("finn", "data/onnx/mnist-conv/test_data_set_0/input_0.pb") - inp_values = onnx.load_tensor_from_string(raw_i) - inp_values = np_helper.to_array(inp_values) - idict = {model1.graph.input[0].name: inp_values} - odict = oxe.execute_onnx(model1, idict) - temp = odict[model1.graph.output[0].name] - - idict = {model2.graph.input[0].name: temp} - odict = oxe.execute_onnx(model2, idict) - outp = odict[model2.graph.output[0].name] - # merge models - model_transformed = model2.transform(MergeONNXModels(model1)) - - idict = {model_transformed.graph.input[0].name: inp_values} - odict = oxe.execute_onnx(model_transformed, idict) - outp_transformed = odict[model_transformed.graph.output[0].name] - - assert (outp == outp_transformed).all() - assert len(model_transformed.graph.node) == len(model1.graph.node) + len( - model2.graph.node - ) - # to test if the value is preserved we set the sparsity annotation of input[1] - # of the division block to a dummy value, we can now look for the division block - # and check if the sparsity annotation is still the same - for n in model_transformed.graph.node: - if n.op_type == "Div": - tensor_name = n.input[1] - set_sparsity = model_transformed.get_tensor_sparsity(tensor_name) - assert sparsity == set_sparsity - - # check if finn datatype of graph.input[0] is still set to UINT8 - assert model_transformed.get_tensor_datatype("global_in") == DataType.UINT8 diff --git a/tests/transformation/test_renaming.py b/tests/transformation/test_renaming.py deleted file mode 100644 index db8b8410e27f881b15abc426537b153348f38206..0000000000000000000000000000000000000000 --- a/tests/transformation/test_renaming.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from pkgutil import get_data - -import numpy as np -import onnx -import onnx.numpy_helper as np_helper - -import finn.core.onnx_exec as oxe -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames -from finn.transformation.infer_shapes import InferShapes - - -def test_renaming(): - # load the onnx model - raw_m = get_data("finn", "data/onnx/mnist-conv/model.onnx") - model = ModelWrapper(raw_m) - model = model.transform(InferShapes()) - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveReadableTensorNames()) - # do some basic checks - assert model.graph.input[0].name == "global_in" - assert model.graph.output[0].name == "global_out" - assert model.graph.node[1].op_type == "Conv" - assert model.graph.node[1].name == "Conv_0" - assert model.graph.node[1].input[1] == "Conv_0_param0" - assert model.graph.node[6].op_type == "Add" - assert model.graph.node[6].name == "Add_1" - assert model.graph.node[6].input[1] == "Add_1_param0" - # ensure running renaming twice still yields the same names - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveReadableTensorNames()) - assert model.graph.node[1].op_type == "Conv" - assert model.graph.node[1].name == "Conv_0" - assert model.graph.node[1].input[1] == "Conv_0_param0" - assert model.graph.node[6].op_type == "Add" - assert model.graph.node[6].name == "Add_1" - assert model.graph.node[6].input[1] == "Add_1_param0" - # run renamed model to make sure we did not mess up the topology - 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 = {"global_in": 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["global_out"], atol=1e-3 - ).all() diff --git a/tests/transformation/test_sort_graph.py b/tests/transformation/test_sort_graph.py deleted file mode 100644 index 05842504c13b144bb34e8084fb12b5086fa84115..0000000000000000000000000000000000000000 --- a/tests/transformation/test_sort_graph.py +++ /dev/null @@ -1,150 +0,0 @@ -from onnx import TensorProto, helper -import numpy as np - -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.general import SortGraph -from finn.transformation.infer_shapes import InferShapes -import pytest -import finn.analysis.topology as ta - - -def make_randomly_sorted_linear_model(num_of_nodes, seed=None): - if seed is not None: - np.random.seed(seed) - - ch = 2 - ifmdim = 16 - input_shape = (1, ch, ifmdim, ifmdim) - - top_in = helper.make_tensor_value_info("t0", TensorProto.FLOAT, input_shape) - top_out = helper.make_tensor_value_info( - "t" + str(num_of_nodes), TensorProto.FLOAT, input_shape - ) - - value_info = [] - nodes = [] - for i in range(num_of_nodes): - nodes += [ - helper.make_node("Add", ["t" + str(i), "p" + str(i)], ["t" + str(i + 1)]) - ] - value_info += [ - helper.make_tensor_value_info("p" + str(i), TensorProto.FLOAT, input_shape) - ] - - nodes = np.random.permutation(nodes) - - modelproto = helper.make_model( - helper.make_graph( - name="test", - inputs=[top_in], - outputs=[top_out], - value_info=value_info, - nodes=nodes, - ) - ) - model = ModelWrapper(modelproto) - model = model.transform(InferShapes()) - - for i in range(num_of_nodes): - model.set_initializer( - "p" + str(i), np.random.rand(*input_shape).astype(np.float32) - ) - - return model - - -@pytest.mark.parametrize("num_of_nodes", [64]) -def test_sort_linear_graph(num_of_nodes): - model = make_randomly_sorted_linear_model(num_of_nodes, seed=0) - 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_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." - - -if __name__ == "__main__": - import time - - sizes = [10, 50, 100, 500, 1000] - times = [] - reps = 10 - - print("SortGraph performance test:") - print("Test sizes", sizes) - print("Repetitions per size:", reps) - for sz in sizes: - acc_time = 0 - print(" Testing size ", sz) - for i in range(reps): - # it should take the same time even with the sorted one - # but better new model each time as it is a more general approach - model = make_randomly_sorted_linear_model(sz) # new model as seed is None - bef = time.time() - new_model = model.transform(SortGraph(), make_deepcopy=False) - acc_time += time.time() - bef - - times += [acc_time / reps] - - # print csv - print("\nnum_of_nodes, seconds") - for sz, tm in zip(sizes, times): - print("{:12d}, {:6.4e}".format(sz, tm)) - - # plot - # import matplotlib.pyplot as plt - # plt.plot(sizes,times,"--o") - # plt.grid(True) diff --git a/tests/transformation/test_topk_insert.py b/tests/transformation/test_topk_insert.py deleted file mode 100644 index a9faac4df0caf973d9aae6430e007eac349a7c43..0000000000000000000000000000000000000000 --- a/tests/transformation/test_topk_insert.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import onnx -from finn.util.test import get_test_model_trained -import brevitas.onnx as bo -import numpy as np -import onnx.numpy_helper as nph -import torch - -from finn.core.modelwrapper import ModelWrapper -from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames -from finn.transformation.infer_shapes import InferShapes -from finn.transformation.infer_datatypes import InferDataTypes -from finn.transformation.fold_constants import FoldConstants -from finn.transformation.insert_topk import InsertTopK - -import finn.core.onnx_exec as oxe -from pkgutil import get_data - -import pytest - -export_onnx_path = "test_topk_insert.onnx" - - -@pytest.mark.parametrize("k", [1, 2]) -def test_topk_insert(k): - tfc = get_test_model_trained("TFC", 1, 1) - bo.export_finn_onnx(tfc, (1, 1, 28, 28), export_onnx_path) - model = ModelWrapper(export_onnx_path) - - # do transformations (no topk) - model = model.transform(InferShapes()) - model = model.transform(FoldConstants()) - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveReadableTensorNames()) - model = model.transform(InferDataTypes()) - - # verification: generate random input, run through net, streamline, - # run again, check that output is top-k - 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_brevitas = torch.from_numpy(nph.to_array(input_tensor)).float() - output_golden = tfc.forward(input_brevitas).detach().numpy() - output_golden_topk = np.flip(output_golden.flatten().argsort())[:k] - output_golden_topk = output_golden_topk.flatten() - - input_dict = {"global_in": nph.to_array(input_tensor)} - - # insert top-k - model = model.transform(InsertTopK(k)) - model = model.transform(GiveUniqueNodeNames()) - model = model.transform(GiveReadableTensorNames()) - model = model.transform(InferShapes()) - - # verify output of top-k - output_dict_topk = oxe.execute_onnx(model, input_dict) - output_pysim_topk = output_dict_topk[list(output_dict_topk.keys())[0]] - output_pysim_topk = output_pysim_topk.astype(np.int).flatten() - - assert np.array_equal(output_golden_topk, output_pysim_topk) - os.remove(export_onnx_path) diff --git a/tests/util/test_create.py b/tests/util/test_create.py deleted file mode 100644 index 4e236978592b02e1c18b03aba56ff8b2369311a6..0000000000000000000000000000000000000000 --- a/tests/util/test_create.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 pytest -import finn.util.create as create -from finn.core.datatype import DataType - - -@pytest.mark.parametrize("bitwidth", [DataType.BIPOLAR, DataType.INT2, DataType.INT4]) -def test_hls_random_mlp_maker(bitwidth): - w = bitwidth - a = bitwidth - layer_spec = [ - { - "mw": 185, - "mh": 100, - "simd": 185, - "pe": 100, - "idt": DataType.BIPOLAR, - "wdt": w, - "act": a, - }, - {"mw": 100, "mh": 100, "simd": 100, "pe": 100, "idt": a, "wdt": w, "act": a}, - {"mw": 100, "mh": 100, "simd": 100, "pe": 100, "idt": a, "wdt": w, "act": a}, - {"mw": 100, "mh": 100, "simd": 100, "pe": 100, "idt": a, "wdt": w, "act": a}, - { - "mw": 100, - "mh": 1, - "simd": 100, - "pe": 1, - "idt": a, - "wdt": w, - "act": DataType.BIPOLAR, - }, - ] - - ret = create.hls_random_mlp_maker(layer_spec) - assert len(ret.graph.node) == 5 - # ret.save("mlp-%s.onnx" % str(bitwidth)) diff --git a/tests/util/test_data_packing.py b/tests/util/test_data_packing_hls.py similarity index 56% rename from tests/util/test_data_packing.py rename to tests/util/test_data_packing_hls.py index 7b77c4be20c1f41c11b53a9b65b79441c9bbbe47..a926bc4068831a552ccfb728511ddda4a8670ca8 100644 --- a/tests/util/test_data_packing.py +++ b/tests/util/test_data_packing_hls.py @@ -36,13 +36,7 @@ import numpy as np import finn.util.basic as cutil from finn.core.datatype import DataType -from finn.util.data_packing import ( - array2hexstring, - finnpy_to_packed_bytearray, - numpy_to_hls_code, - pack_innermost_dim_as_hex_string, - packed_bytearray_to_finnpy, -) +from finn.util.data_packing import numpy_to_hls_code @pytest.mark.parametrize("dtype", [DataType.BINARY, DataType.INT2, DataType.INT32]) @@ -95,7 +89,7 @@ def test_npy2apintstream(test_shape, dtype): f.write("\n".join(test_app_string)) cmd_compile = """ g++ -o test_npy2apintstream test.cpp /workspace/cnpy/cnpy.cpp \ --I/workspace/cnpy/ -I{}/include -I/workspace/finn/src/finn/data/cpp \ +-I/workspace/cnpy/ -I{}/include -I/workspace/finn/src/finn/qnn-data/cpp \ --std=c++11 -lz""".format( os.environ["VIVADO_PATH"] ) @@ -121,34 +115,6 @@ g++ -o test_npy2apintstream test.cpp /workspace/cnpy/cnpy.cpp \ assert success -def test_array2hexstring(): - assert array2hexstring([1, 1, 1, 0], DataType.BINARY, 4) == "0xe" - assert array2hexstring([1, 1, 1, 0], DataType.BINARY, 8) == "0x0e" - assert array2hexstring([1, 1, 1, -1], DataType.BIPOLAR, 8) == "0x0e" - assert array2hexstring([3, 3, 3, 3], DataType.UINT2, 8) == "0xff" - assert array2hexstring([1, 3, 3, 1], DataType.UINT2, 8) == "0x7d" - assert array2hexstring([1, -1, 1, -1], DataType.INT2, 8) == "0x77" - assert array2hexstring([1, 1, 1, -1], DataType.INT4, 16) == "0x111f" - assert array2hexstring([-1], DataType.FLOAT32, 32) == "0xbf800000" - assert array2hexstring([17.125], DataType.FLOAT32, 32) == "0x41890000" - assert array2hexstring([1, 1, 0, 1], DataType.BINARY, 4, reverse=True) == "0xb" - assert array2hexstring([1, 1, 1, 0], DataType.BINARY, 8, reverse=True) == "0x07" - - -def test_pack_innermost_dim_as_hex_string(): - A = [[1, 1, 1, 0], [0, 1, 1, 0]] - eA = np.asarray(["0x0e", "0x06"]) - assert (pack_innermost_dim_as_hex_string(A, DataType.BINARY, 8) == eA).all() - B = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] - eB = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) - assert (pack_innermost_dim_as_hex_string(B, DataType.UINT2, 8) == eB).all() - C = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] - eC = np.asarray([["0x0f", "0x0f"], ["0x0d", "0x07"]]) - assert ( - pack_innermost_dim_as_hex_string(C, DataType.UINT2, 8, reverse_inner=True) == eC - ).all() - - def test_numpy_to_hls_code(): def remove_all_whitespace(s): return "".join(s.split()) @@ -168,68 +134,3 @@ def test_numpy_to_hls_code(): eB = """{{ap_uint<4>("0xf", 16), ap_uint<4>("0xf", 16)}, {ap_uint<4>("0x7", 16), ap_uint<4>("0xd", 16)}};""" assert remove_all_whitespace(ret) == remove_all_whitespace(eB) - - -def test_finnpy_to_packed_bytearray(): - A = [[1, 1, 1, 0], [0, 1, 1, 0]] - eA = np.asarray([[14], [6]], dtype=np.uint8) - assert (finnpy_to_packed_bytearray(A, DataType.BINARY) == eA).all() - B = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] - eB = np.asarray([[[15], [15]], [[7], [13]]], dtype=np.uint8) - assert (finnpy_to_packed_bytearray(B, DataType.UINT2) == eB).all() - C = [1, 7, 2, 5] - eC = np.asarray([23, 37], dtype=np.uint8) - assert (finnpy_to_packed_bytearray(C, DataType.UINT4) == eC).all() - D = [[1, 7, 2, 5], [2, 5, 1, 7]] - eD = np.asarray([[23, 37], [37, 23]], dtype=np.uint8) - assert (finnpy_to_packed_bytearray(D, DataType.UINT4) == eD).all() - E = [[-4, 0, -4, -4]] - eE = np.asarray( - [[255, 255, 255, 252, 0, 0, 0, 0, 255, 255, 255, 252, 255, 255, 255, 252]], - dtype=np.uint8, - ) - assert (finnpy_to_packed_bytearray(E, DataType.INT32) == eE).all() - - -def test_packed_bytearray_to_finnpy(): - A = np.asarray([[14], [6]], dtype=np.uint8) - eA = [[1, 1, 1, 0], [0, 1, 1, 0]] - eA = np.asarray(eA, dtype=np.float32) - shapeA = eA.shape - assert (packed_bytearray_to_finnpy(A, DataType.BINARY, shapeA) == eA).all() - B = np.asarray([[[15], [15]], [[7], [13]]], dtype=np.uint8) - eB = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] - eB = np.asarray(eB, dtype=np.float32) - shapeB = eB.shape - assert (packed_bytearray_to_finnpy(B, DataType.UINT2, shapeB) == eB).all() - C = np.asarray([23, 37], dtype=np.uint8) - eC = [1, 7, 2, 5] - eC = np.asarray(eC, dtype=np.float32) - shapeC = eC.shape - assert (packed_bytearray_to_finnpy(C, DataType.UINT4, shapeC) == eC).all() - D = np.asarray([[23, 37], [37, 23]], dtype=np.uint8) - eD = [[1, 7, 2, 5], [2, 5, 1, 7]] - eD = np.asarray(eD, dtype=np.float32) - shapeD = eD.shape - assert (packed_bytearray_to_finnpy(D, DataType.UINT4, shapeD) == eD).all() - E = np.asarray( - [[255, 255, 255, 252, 0, 0, 0, 0, 255, 255, 255, 252, 255, 255, 255, 252]], - dtype=np.uint8, - ) - eE = [[-4, 0, -4, -4]] - eE = np.asarray(eE, dtype=np.float32) - shapeE = eE.shape - assert (packed_bytearray_to_finnpy(E, DataType.INT32, shapeE) == eE).all() - F = np.asarray( - [[252, 255, 255, 255, 0, 0, 0, 0, 252, 255, 255, 255, 252, 255, 255, 255]], - dtype=np.uint8, - ) - eF = [[-4, 0, -4, -4]] - eF = np.asarray(eE, dtype=np.float32) - shapeF = eF.shape - assert ( - packed_bytearray_to_finnpy( - F, DataType.INT32, shapeF, reverse_inner=True, reverse_endian=True - ) - == eF - ).all() diff --git a/tests/util/test_gen_finn_dt_tensor.py b/tests/util/test_gen_finn_dt_tensor.py deleted file mode 100644 index f9944e7f5283725d4c7c3b70ead899c9e1d4ea49..0000000000000000000000000000000000000000 --- a/tests/util/test_gen_finn_dt_tensor.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.util.basic as util -from finn.core.datatype import DataType - - -def test_finn_tensor_generator(): - # bipolar - shape_bp = [2, 2] - dt_bp = DataType.BIPOLAR - tensor_bp = util.gen_finn_dt_tensor(dt_bp, shape_bp) - # test shape - for i in range(len(shape_bp)): - assert ( - shape_bp[i] == tensor_bp.shape[i] - ), """Shape of generated tensor - does not match the desired shape""" - # test if elements are FINN datatype - for value in tensor_bp.flatten(): - assert dt_bp.allowed( - value - ), """Data type of generated tensor - does not match the desired Data type""" - - # binary - shape_b = [4, 2, 3] - dt_b = DataType.BINARY - tensor_b = util.gen_finn_dt_tensor(dt_b, shape_b) - # test shape - for i in range(len(shape_b)): - assert ( - shape_b[i] == tensor_b.shape[i] - ), """Shape of generated tensor - does not match the desired shape""" - # test if elements are FINN datatype - for value in tensor_b.flatten(): - assert dt_b.allowed( - value - ), """Data type of generated tensor - does not match the desired Data type""" - - # ternary - shape_t = [7, 1, 3, 1] - dt_t = DataType.TERNARY - tensor_t = util.gen_finn_dt_tensor(dt_t, shape_t) - # test shape - for i in range(len(shape_t)): - assert ( - shape_t[i] == tensor_t.shape[i] - ), """Shape of generated tensor - does not match the desired shape""" - # test if elements are FINN datatype - for value in tensor_t.flatten(): - assert dt_t.allowed( - value - ), """Data type of generated tensor - does not match the desired Data type""" - - # int2 - shape_int2 = [7, 4] - dt_int2 = DataType.INT2 - tensor_int2 = util.gen_finn_dt_tensor(dt_int2, shape_int2) - # test shape - for i in range(len(shape_int2)): - assert ( - shape_int2[i] == tensor_int2.shape[i] - ), """Shape of generated tensor - does not match the desired shape""" - # test if elements are FINN datatype - for value in tensor_int2.flatten(): - assert value in [ - -2, - -1, - 0, - 1, - ], """Data type of generated tensor - does not match the desired Data type""" - - # import pdb; pdb.set_trace() diff --git a/tests/util/test_padding.py b/tests/util/test_padding.py deleted file mode 100644 index 4e49acf12badc28bd231e990a5d02dc25d3a2006..0000000000000000000000000000000000000000 --- a/tests/util/test_padding.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.util.basic import pad_tensor_to_multiple_of - - -def test_pad_tensor_to_multiple_of(): - A = np.eye(3) - B = pad_tensor_to_multiple_of(A, [2, 2], val=-1) - assert B.shape == (4, 4) - assert (B[:3, :3] == A).all() - assert (B[3, :] == -1).all() - assert (B[:, 3] == -1).all() - B = pad_tensor_to_multiple_of(A, [5, 5], val=-1, distr_pad=True) - assert B.shape == (5, 5) - assert (B[1:4, 1:4] == A).all() - assert (B[0, :] == -1).all() - assert (B[:, 0] == -1).all() - assert (B[4, :] == -1).all() - assert (B[:, 4] == -1).all() - # using -1 in pad_to parameter should give an unpadded dimension - B = pad_tensor_to_multiple_of(A, [-1, 5], val=-1, distr_pad=True) - assert B.shape == (3, 5) - assert (B[:, 1:4] == A).all() - assert (B[:, 0] == -1).all() - assert (B[:, 4] == -1).all() - # if odd number of padding pixels required, 1 more should go after existing - B = pad_tensor_to_multiple_of(A, [6, 6], val=-1, distr_pad=True) - assert B.shape == (6, 6) - assert (B[1:4, 1:4] == A).all() diff --git a/tests/util/test_rtlsim2npy.py b/tests/util/test_rtlsim2npy.py deleted file mode 100644 index 87ea5c2c57b10360ba64ac0593d4a7f5badef735..0000000000000000000000000000000000000000 --- a/tests/util/test_rtlsim2npy.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (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 finn.core.datatype import DataType -from finn.util.data_packing import unpack_innermost_dim_from_hex_string - - -def test_unpack_innermost_dim_from_hex_string(): - # BINARY - A = np.asarray(["0x0e", "0x06"]) - dtype = DataType.BINARY - shape = (1, 2, 4) - eA = [[1, 1, 1, 0], [0, 1, 1, 0]] - A_unpacked = unpack_innermost_dim_from_hex_string(A, dtype, shape, 8) - assert (A_unpacked == eA).all() - - A = np.asarray(["0x0e", "0x06"]) - eA_flipped = [[0, 1, 1, 1], [0, 1, 1, 0]] - A_unpacked_flipped = unpack_innermost_dim_from_hex_string( - A, dtype, shape, 8, reverse_inner=True - ) - assert (A_unpacked_flipped == eA_flipped).all() - - # UINT2 - B = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) - dtype = DataType.UINT2 - shape = (1, 2, 2, 2) - eB = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] - B_unpacked = unpack_innermost_dim_from_hex_string(B, dtype, shape, 8) - assert (B_unpacked == eB).all() - - B = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) - eB_flipped = [[[3, 3], [3, 3]], [[3, 1], [1, 3]]] - B_unpacked_flipped = unpack_innermost_dim_from_hex_string( - B, dtype, shape, 8, reverse_inner=True - ) - assert (B_unpacked_flipped == eB_flipped).all() - - # INT2 - C = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) - dtype = DataType.INT2 - shape = (1, 2, 2, 2) - eC = [[[-1, -1], [-1, -1]], [[1, -1], [-1, 1]]] - C_unpacked = unpack_innermost_dim_from_hex_string(C, dtype, shape, 8) - assert (C_unpacked == eC).all() - - C = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) - dtype = DataType.INT2 - shape = (1, 2, 2, 2) - eC = [[[-1, -1], [-1, -1]], [[-1, 1], [1, -1]]] - C_unpacked = unpack_innermost_dim_from_hex_string( - C, dtype, shape, 8, reverse_inner=True - ) - assert (C_unpacked == eC).all() - - # INT4 - D = np.asarray(["0x0e", "0x06"]) - dtype = DataType.INT4 - shape = (2, 1) - eD = [[-2], [6]] - D_unpacked = unpack_innermost_dim_from_hex_string(D, dtype, shape, 8) - assert (D_unpacked == eD).all() - - D_unpacked = unpack_innermost_dim_from_hex_string( - D, dtype, shape, 8, reverse_inner=True - ) - assert (D_unpacked == eD).all() - - # INT32 - E = np.asarray(["0xffffffff", "0xfffffffe", "0x02", "0xffffffef"]) - dtype = DataType.INT32 - shape = (1, 4, 1) - eE = [[[-1], [-2], [2], [-17]]] - E_unpacked = unpack_innermost_dim_from_hex_string(E, dtype, shape, 32) - assert (E_unpacked == eE).all() - - E_unpacked = unpack_innermost_dim_from_hex_string( - E, dtype, shape, 32, reverse_inner=True - ) - assert (E_unpacked == eE).all()