├── .coveragerc ├── .github └── workflows │ ├── ci-pipeline.yml │ ├── pre-commit.yml │ └── python-publish.yml ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── AUTHORS.rst ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── docker ├── Dockerfile ├── build_docs.sh ├── entrypoint.sh ├── requirements.txt └── run_tests.sh ├── docs ├── Makefile ├── QONNX │ ├── bipolar_quant_op.md │ ├── quant_op.md │ └── trunc_op.md ├── _static │ └── .gitignore ├── _templates │ └── apidoc │ │ ├── module.rst_t │ │ ├── package.rst_t │ │ └── toc.rst_t ├── authors.rst ├── conf.py ├── genindex.rst ├── index.rst ├── license.rst ├── overview.rst └── tutorials.rst ├── run-docker.sh ├── setup.cfg ├── setup.py ├── src └── finn │ ├── analysis │ ├── base.py │ ├── inference_cost.py │ └── topology.py │ ├── core │ ├── data_layout.py │ ├── datatype.py │ ├── execute_custom_node.py │ ├── modelwrapper.py │ ├── onnx_exec.py │ ├── remote_exec.py │ ├── rtlsim_exec.py │ └── throughput_test.py │ ├── custom_op │ ├── base.py │ ├── general │ │ ├── __init__.py │ │ ├── bipolar_quant.py │ │ ├── debugmarker.py │ │ ├── genericpartition.py │ │ ├── im2col.py │ │ ├── maxpoolnhwc.py │ │ ├── multithreshold.py │ │ ├── quant.py │ │ ├── quantavgpool2d.py │ │ ├── trunc.py │ │ └── xnorpopcount.py │ └── registry.py │ ├── data │ ├── __init__.py │ ├── onnx │ │ └── mnist-conv │ │ │ ├── README.md │ │ │ ├── model.onnx │ │ │ └── test_data_set_0 │ │ │ ├── input_0.pb │ │ │ └── output_0.pb │ └── verilog │ │ └── myadd │ │ ├── myadd_myadd.v │ │ └── myadd_myadd_control_s_axi.v │ ├── transformation │ ├── base.py │ ├── batchnorm_to_affine.py │ ├── bipolar_to_xnor.py │ ├── change_3d_tensors_to_4d.py │ ├── change_datalayout.py │ ├── create_generic_partitions.py │ ├── double_to_single_float.py │ ├── extend_partition.py │ ├── extract_conv_bias.py │ ├── fold_constants.py │ ├── gemm_to_matmul.py │ ├── general.py │ ├── infer_data_layouts.py │ ├── infer_datatypes.py │ ├── infer_shapes.py │ ├── insert_topk.py │ ├── lower_convs_to_matmul.py │ ├── make_input_chanlast.py │ ├── merge_onnx_models.py │ └── remove.py │ └── util │ ├── basic.py │ ├── config.py │ ├── data_packing.py │ ├── fpgadataflow.py │ ├── hls.py │ ├── inference_cost.py │ ├── onnx.py │ ├── platforms.py │ ├── pyverilator.py │ └── vivado.py └── tests ├── analysis ├── test_is_linear.py └── test_topology_checks.py ├── conftest.py ├── core ├── test_basic_onnx_exec.py ├── test_custom_onnx_exec.py ├── test_datatypes.py ├── test_mixed_onnx_exec.py └── test_modelwrapper.py ├── custom_op ├── test_im2col.py ├── test_multithreshold.py └── test_xnorpopcountmatmul.py ├── transformation ├── test_4d_conversion.py ├── test_batchnorm_to_affine.py ├── test_change_datalayout.py ├── test_conv_lowering.py ├── test_extend_partition.py ├── test_fold_constants.py ├── test_general_transformation.py ├── test_generic_partitioning.py ├── test_infer_data_layouts.py ├── test_infer_datatypes.py ├── test_infer_shapes.py ├── test_make_input_chanlast.py ├── test_merge_onnx_models.py ├── test_remove_identity_ops.py ├── test_renaming.py ├── test_sort_graph.py └── test_topk_insert.py └── util ├── test_data_packing.py ├── test_gen_finn_dt_tensor.py ├── test_padding.py ├── test_pyverilator.py ├── test_rtlsim2npy.py └── test_shape_utils.py /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | source = finn 5 | # omit = bad_file.py 6 | 7 | [paths] 8 | source = 9 | src/ 10 | */site-packages/ 11 | 12 | [report] 13 | # Regexes for lines to exclude from consideration 14 | exclude_lines = 15 | # Have to re-enable the standard pragma 16 | pragma: no cover 17 | 18 | # Don't complain about missing debug-only code: 19 | def __repr__ 20 | if self\.debug 21 | 22 | # Don't complain if tests don't hit defensive assertion code: 23 | raise AssertionError 24 | raise NotImplementedError 25 | 26 | # Don't complain if non-runnable code isn't run: 27 | if 0: 28 | if __name__ == .__main__.: 29 | -------------------------------------------------------------------------------- /.github/workflows/ci-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: CI Pipeline 2 | 3 | on: 4 | pull_request: 5 | branches: [ main, dev ] 6 | push: 7 | branches: [ main, dev ] 8 | 9 | 10 | jobs: 11 | 12 | test: 13 | name: Test PR or Push to DEV 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Build Docker 21 | run: | 22 | docker build -f docker/Dockerfile -t finn-base-gha \ 23 | --build-arg GROUP=$(id -gn) \ 24 | --build-arg GID=$(id -g) \ 25 | --build-arg USER=$(id -un) \ 26 | --build-arg UID=$(id -u) \ 27 | . 28 | 29 | - name: Run Tests 30 | run: | 31 | docker run --rm \ 32 | -v $(pwd):/workspace/finn-base \ 33 | -e FINN_BUILD_DIR=/tmp/finn-base-gha \ 34 | finn-base-gha run_tests.sh 35 | 36 | - name: Build Docs 37 | run: | 38 | docker run --rm \ 39 | -v $(pwd):/workspace/finn-base \ 40 | -e FINN_BUILD_DIR=/tmp/finn-base-gha \ 41 | finn-base-gha build_docs.sh 42 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Run pre-commit 2 | 3 | on: 4 | pull_request: 5 | branches: [ main, dev ] 6 | push: 7 | branches: [ main, dev ] 8 | 9 | jobs: 10 | lint: 11 | name: Lint PR or Push to DEV 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.8] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Run Lint 27 | uses: pre-commit/action@v2.0.0 28 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: __token__ 28 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 29 | run: | 30 | python setup.py sdist 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary and binary files 2 | *~ 3 | *.py[cod] 4 | *.so 5 | *.cfg 6 | !.isort.cfg 7 | !setup.cfg 8 | *.orig 9 | *.log 10 | *.pot 11 | __pycache__/* 12 | .cache/* 13 | .*.swp 14 | .ipynb_checkpoints 15 | .DS_Store 16 | 17 | # Project files 18 | .ropeproject 19 | .project 20 | .pydevproject 21 | .settings 22 | .idea 23 | tags 24 | 25 | # Package files 26 | *.egg 27 | *.eggs/ 28 | .installed.cfg 29 | *.egg-info 30 | 31 | # Unittest and coverage 32 | htmlcov/* 33 | .coverage 34 | .tox 35 | junit.xml 36 | coverage.xml 37 | .pytest_cache/ 38 | 39 | # Build and docs folder/files 40 | build/* 41 | dist/* 42 | sdist/* 43 | docs/api/* 44 | docs/_rst/* 45 | docs/_build/* 46 | cover/* 47 | MANIFEST 48 | 49 | # Per-project virtualenvs 50 | .venv*/ 51 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=88 3 | indent=' ' 4 | skip=.tox,.venv,build,dist 5 | known_standard_library=setuptools,pkg_resources 6 | known_test=pytest 7 | known_first_party=finn 8 | sections=FUTURE,STDLIB,TEST,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 9 | default_section=THIRDPARTY 10 | multi_line_output=3 11 | profile=black 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: '^docs/conf.py' 2 | 3 | default_language_version: 4 | python: python3.8 5 | 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v4.2.0 9 | hooks: 10 | - id: trailing-whitespace 11 | - id: check-added-large-files 12 | - id: check-ast 13 | - id: check-json 14 | - id: check-merge-conflict 15 | - id: check-xml 16 | - id: check-yaml 17 | - id: debug-statements 18 | - id: end-of-file-fixer 19 | - id: requirements-txt-fixer 20 | - id: mixed-line-ending 21 | args: ['--fix=no'] 22 | 23 | - repo: https://github.com/PyCQA/isort 24 | rev: 5.10.1 25 | hooks: 26 | - id: isort 27 | 28 | - repo: https://github.com/psf/black 29 | rev: 22.3.0 30 | hooks: 31 | - id: black 32 | language_version: python3 33 | 34 | - repo: https://gitlab.com/pycqa/flake8 35 | rev: 3.9.2 36 | hooks: 37 | - id: flake8 38 | # black-compatible flake-8 config 39 | args: ['--max-line-length=88', # black default 40 | '--extend-ignore=E203'] # E203 is not PEP8 compliant 41 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributors 3 | ============ 4 | 5 | * Yaman Umuroglu (@maltanar) (maintainer) 6 | * Sambhav Jain (@sjain-stanford) 7 | * Jakoba Petri-Koenig (@auphelia) 8 | * Lucian Petrica (@quetric) 9 | * Tobias Alonso (@Tobi-Alonso) 10 | * Hendrik Borras (@HenniOVP) 11 | * Mirza Mrahorovic (@mmrahorovic) 12 | * Felix Paul Jentzsch (@felixpj) 13 | * Jon Ander Lezeta (@jalezeta) 14 | * Radoslav Pitoňák (@rpitonak) 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribution guidelines for `finn-base` are under construction. 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Xilinx, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Xilinx nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation notice 2 | This repo (`finn-base`) is scheduled to be deprecated soon, and should be replaced in any dependent projects with the new QONNX (https://github.com/fastmachinelearning/qonnx) repo. QONNX is nearly a drop-in replacement for `finn-base`, only a namespace change is needed (e.g. `import finn.core.modelwrapper` becomes `import qonnx.core.modelwrapper`). 3 | 4 | ## Core Components for Quantized Neural Network Inference 5 | 6 | `finn-base` is part of the [FINN project](https://xilinx.github.io/finn/) and provides the core infrastructure for the [FINN compiler](https://github.com/Xilinx/finn/), including: 7 | 8 | * wrapper around ONNX models for easier manipulation 9 | * infrastructure for applying transformation and analysis passes on ONNX graphs 10 | * infrastructure for defining and executing custom ONNX ops (for verification and code generation) 11 | * extensions to ONNX models using annotations, including few-bit data types, sparsity and data layout specifiers 12 | * several transformation passes, including topological sorting, constant folding and convolution lowering 13 | * several custom ops including im2col and multi-thresholding for quantized activations 14 | * several utility functions, including packing for few-bit integers 15 | 16 | ## Installation 17 | 18 | `finn-base` can be installed via pip by [following these instructions](http://finn-base.readthedocs.io/). 19 | 20 | ## Documentation 21 | 22 | You can view the documentation on [readthedocs](https://finn-base.readthedocs.io) or build them locally using `./run-docker.sh docs`. 23 | 24 | ## Community 25 | 26 | We have a [gitter channel](https://gitter.im/xilinx-finn/community) where you can ask questions. You can use the GitHub issue tracker to report bugs, but please don't file issues to ask questions as this is better handled in the gitter channel. 27 | 28 | We also heartily welcome contributions to the project, please check out the [contribution guidelines](CONTRIBUTING.md) and the [list of open issues](https://github.com/Xilinx/finn-base/issues). Don't hesitate to get in touch over [Gitter](https://gitter.im/xilinx-finn/community) to discuss your ideas. 29 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | FROM mcr.microsoft.com/azureml/onnxruntime:v1.4.0 30 | 31 | LABEL maintainer="Yaman Umuroglu " 32 | 33 | ARG GROUP 34 | ARG GID 35 | ARG USER 36 | ARG UID 37 | 38 | ENV SHELL=/bin/bash 39 | 40 | # Run below commands as root 41 | USER root 42 | 43 | RUN apt-get update 44 | RUN apt-get install -y git build-essential verilator python3-dev 45 | # use python3 and pip3 as python and pip defaults 46 | RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 10 47 | RUN update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 10 48 | 49 | # Copy entrypoint scripts to the workdir (as root) 50 | WORKDIR /usr/local/bin 51 | COPY docker/entrypoint.sh . 52 | COPY docker/run_tests.sh . 53 | COPY docker/build_docs.sh . 54 | RUN chmod +x entrypoint.sh run_tests.sh build_docs.sh 55 | 56 | # This creates /workspace if it doesn't exist 57 | WORKDIR /workspace 58 | 59 | # Add user 60 | RUN groupadd -o -g ${GID} ${GROUP} && \ 61 | useradd -u ${UID} -g ${GROUP} -ms /bin/bash ${USER} && \ 62 | usermod -aG sudo ${USER} && \ 63 | chown -R ${USER}:${GROUP} /workspace 64 | 65 | # Color prompt 66 | RUN echo "PS1='\[\033[1;36m\]\u\[\033[1;31m\]@\[\033[1;32m\]\h:\[\033[1;35m\]\w\[\033[1;31m\]\$\[\033[0m\] '" >> /home/${USER}/.bashrc 67 | 68 | # Install Python package requirements 69 | COPY docker/requirements.txt . 70 | RUN python -mpip install --upgrade pip && \ 71 | pip install --upgrade setuptools &&\ 72 | pip install -r requirements.txt && \ 73 | rm requirements.txt 74 | 75 | # Install custom fork of pyverilator 76 | RUN pip install git+https://github.com/maltanar/pyverilator.git@0c3eb9343500fc1352a02c020a736c8c2db47e8e 77 | 78 | # Install pytest-xdist (not in requirements, only for faster testing in Docker) 79 | RUN pip install pytest-xdist==2.0.0 80 | 81 | # Run below commands as user 82 | USER ${USER} 83 | 84 | # Set ENV 85 | ENV PYTHONPATH "${PYTHONPATH}:/workspace/finn-base/src" 86 | ENV PATH "${PATH}:/home/${USER}/.local/bin" 87 | 88 | # Set workdir to gray before launching container 89 | WORKDIR /workspace/finn-base 90 | 91 | ENTRYPOINT ["entrypoint.sh"] 92 | CMD ["bash"] 93 | -------------------------------------------------------------------------------- /docker/build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2020 Xilinx, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of Xilinx nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | cd /workspace/finn-base/docs 31 | make clean 32 | 33 | cd /workspace/finn-base 34 | python setup.py docs 35 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2020 Xilinx, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of Xilinx nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | RED='\033[0;31m' 31 | GREEN='\033[0;32m' 32 | NC='\033[0m' # No Color 33 | 34 | # Green echo 35 | gecho () { 36 | echo -e "${GREEN}$1${NC}" 37 | } 38 | 39 | # Red echo 40 | recho () { 41 | echo -e "${RED}$1${NC}" 42 | } 43 | 44 | gecho "Installing finn-base in develop mode" 45 | pip install --user --no-dependencies -e /workspace/finn-base 46 | 47 | exec $@ 48 | -------------------------------------------------------------------------------- /docker/requirements.txt: -------------------------------------------------------------------------------- 1 | bitstring>=3.1.7 2 | clize==4.1.1 3 | numpy 4 | onnx==1.7.0 5 | onnxruntime==1.4.0 6 | pre-commit>=2.7.1 7 | sphinx>=3.2.1 8 | sphinx_rtd_theme>=0.5.0 9 | toposort>=1.5.0 10 | -------------------------------------------------------------------------------- /docker/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2020 Xilinx, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of Xilinx nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | cd /workspace/finn-base 31 | python setup.py test --addopts "-n auto" 32 | -------------------------------------------------------------------------------- /docs/QONNX/bipolar_quant_op.md: -------------------------------------------------------------------------------- 1 | ### **BipolarQuant** 2 | 3 | Calculates the binary quantized values of one input data (Tensor) and produces one output data (Tensor). 4 | Additionally, takes one float as input, which define the scaling. 5 | 6 | #### Version 7 | 8 | This operator is not part of the ONNX standard and is not currently versioned. 9 | 10 | #### Attributes 11 | 12 |
13 |
14 | 15 | #### Inputs 16 | 17 |
18 |
X (differentiable) : tensor(float32)
19 |
input tensor to quantize
20 |
scale : float32
21 |
The scale factor
22 |
23 | 24 | 25 | #### Outputs 26 | 27 |
28 |
Y (differentiable) : tensor(float32)
29 |
Output tensor
30 |
31 | 32 | 33 | #### Examples 34 |
35 | BipolarQuant 36 | 37 | ```python 38 | from onnx import helper 39 | import numpy as np 40 | 41 | # Define node settings and input 42 | x = np.random.randn(100).astype(np.float32)*10. 43 | scale = np.array(1.) 44 | 45 | # Create node 46 | node = helper.make_node( 47 | 'BipolarQuant', 48 | domain='finn.custom_op.general', 49 | inputs=['x', 'scale'], 50 | outputs=['y'], 51 | ) 52 | 53 | # Execute the same settings with the reference implementation (quant) 54 | # See the sample implementation for more details on quant. 55 | output_ref = binary_quant(x, scale) 56 | 57 | # Execute node and compare 58 | expect(node, inputs=[x, scale], outputs=[output_ref], name='test_binary_quant') 59 | 60 | ``` 61 | 62 |
63 | 64 | 65 | #### Sample Implementation 66 | 67 |
68 | BipolarQuant 69 | 70 | ```python 71 | # SPDX-License-Identifier: Apache-2.0 72 | 73 | from __future__ import absolute_import 74 | from __future__ import division 75 | from __future__ import print_function 76 | from __future__ import unicode_literals 77 | 78 | import numpy as np 79 | 80 | def binary_quant(inp_tensor, scale): 81 | # Quantizing 82 | y_int = inp_tensor 83 | y_ones = np.ones(y_int.shape, dtype=y_int.dtype) 84 | y_int = np.where(y_int >= 0.0, y_ones, -y_ones) 85 | # Scaling 86 | out_tensor = y_int * scale 87 | 88 | return out_tensor 89 | 90 | ``` 91 | 92 |
93 | -------------------------------------------------------------------------------- /docs/QONNX/trunc_op.md: -------------------------------------------------------------------------------- 1 | ### **Trunc** 2 | 3 | Truncates the values of one input data (Tensor) at a specified bitwidth and produces one output data (Tensor). 4 | Additionally, takes four float tensors as input, which define the scale, zero-point, input bit-width and output bit-width of the quantization. 5 | The attribute rounding_mode defines how truncated values are rounded. 6 | 7 | #### Version 8 | 9 | This operator is not part of the ONNX standard and is not currently versioned. 10 | 11 | #### Attributes 12 | 13 |
14 |
rounding_mode : string (default is "FLOOR")
15 |
Defines how rounding should be applied during truncation. Currently available modes are: "ROUND", "CEIL" and "FLOOR". Here "ROUND" implies a round-to-even operation.
16 |
17 | 18 | #### Inputs 19 | 20 |
21 |
X (differentiable) : tensor(float32)
22 |
input tensor to truncate
23 |
scale : float32
24 |
The scale factor
25 |
zeropt : float32
26 |
The zero-point
27 |
in_bitwidth : int32
28 |
The number of bits used at the input of the truncation
29 |
out_bitwidth : int32
30 |
The number of bits used at the output of the truncation
31 |
32 | 33 | 34 | #### Outputs 35 | 36 |
37 |
Y (differentiable) : tensor(float32)
38 |
Output tensor
39 |
40 | 41 | 42 | #### Examples 43 |
44 | Trunc 45 | 46 | ```python 47 | from onnx import helper 48 | import numpy as np 49 | 50 | # Define node settings and input 51 | x = np.random.randn(100).astype(np.float32)*10. 52 | scale = np.array(1.) 53 | zeropt = np.array(0.) 54 | in_bitwidth = np.array(10) 55 | out_bitwidth = np.array(4) 56 | rounding_mode = "ROUND" 57 | 58 | # Create node 59 | node = helper.make_node( 60 | 'Trunc', 61 | domain='finn.custom_op.general', 62 | inputs=['x', 'scale', 'zeropt', 'in_bitwidth', 'out_bitwidth'], 63 | outputs=['y'], 64 | rounding_mode=rounding_mode, 65 | ) 66 | 67 | # Execute the same settings with the reference implementation (trunc) 68 | # See the sample implementation for more details on trunc. 69 | output_ref = trunc(inp_tensor, scale, zeropt, in_bitwidth, out_bitwidth, rounding_mode) 70 | 71 | # Execute node and compare 72 | expect(node, inputs=[x, scale, zeropt, bitwidth], outputs=[output_ref], name='test_trunc') 73 | 74 | ``` 75 | 76 |
77 | 78 | 79 | #### Sample Implementation 80 | 81 |
82 | Trunc 83 | 84 | ```python 85 | # SPDX-License-Identifier: Apache-2.0 86 | 87 | from __future__ import absolute_import 88 | from __future__ import division 89 | from __future__ import print_function 90 | from __future__ import unicode_literals 91 | 92 | import numpy as np 93 | 94 | def trunc(inp_tensor, scale, zeropt, input_bit_width, output_bit_width, rounding_mode): 95 | # Port of TruncIntQuant class from Brevitas: https://bit.ly/3wzIpTR 96 | 97 | # Scaling 98 | y = inp_tensor / scale 99 | y = y + zeropt 100 | # Rounding 101 | y = np.round(y) 102 | # Truncate 103 | trunc_bit_width = input_bit_width - output_bit_width 104 | trunc_scale = 2.0 ** trunc_bit_width 105 | y = y / trunc_scale 106 | 107 | # To int 108 | rounding_fx = resolve_rounding_mode(rounding_mode) 109 | y = rounding_fx(y) 110 | 111 | # Rescale 112 | y = y - zeropt 113 | y = y * scale 114 | 115 | return y 116 | 117 | def resolve_rounding_mode(mode_string): 118 | """Resolve the rounding mode string of Quant and Trunc ops 119 | to the corresponding numpy functions.""" 120 | if mode_string == "ROUND": 121 | return np.round 122 | elif mode_string == "CEIL": 123 | return np.ceil 124 | elif mode_string == "FLOOR": 125 | return np.floor 126 | else: 127 | raise ValueError(f"Could not resolve rounding mode called: {mode_string}") 128 | 129 | ``` 130 | 131 |
132 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | # Empty directory 2 | -------------------------------------------------------------------------------- /docs/_templates/apidoc/module.rst_t: -------------------------------------------------------------------------------- 1 | {%- if show_headings %} 2 | {{- [basename, "(module)"] | join(' ') | e | heading }} 3 | 4 | {% endif -%} 5 | .. automodule:: {{ qualname }} 6 | {%- for option in automodule_options %} 7 | :{{ option }}: 8 | {%- endfor %} 9 | -------------------------------------------------------------------------------- /docs/_templates/apidoc/package.rst_t: -------------------------------------------------------------------------------- 1 | {%- macro automodule(modname, options) -%} 2 | .. automodule:: {{ modname }} 3 | {%- for option in options %} 4 | :{{ option }}: 5 | {%- endfor %} 6 | {%- endmacro %} 7 | 8 | {%- macro toctree(docnames) -%} 9 | .. toctree:: 10 | :maxdepth: {{ maxdepth }} 11 | {% for docname in docnames %} 12 | {{ docname }} 13 | {%- endfor %} 14 | {%- endmacro %} 15 | 16 | {%- if is_namespace %} 17 | {{- [pkgname, "(namespace)"] | join(" ") | e | heading }} 18 | {% else %} 19 | {{- [pkgname, "(package)"] | join(" ") | e | heading }} 20 | {% endif %} 21 | 22 | {%- if modulefirst and not is_namespace %} 23 | {{ automodule(pkgname, automodule_options) }} 24 | {% endif %} 25 | 26 | {%- if subpackages %} 27 | Subpackages: 28 | 29 | {{ toctree(subpackages) }} 30 | {% endif %} 31 | 32 | {%- if submodules %} 33 | Submodules: 34 | 35 | {% if separatemodules %} 36 | {{ toctree(submodules) }} 37 | {% else %} 38 | {%- for submodule in submodules %} 39 | {% if show_headings %} 40 | {{- [submodule, "(module)"] | join(" ") | e | heading(2) }} 41 | {% endif %} 42 | {{ automodule(submodule, automodule_options) }} 43 | {% endfor %} 44 | {%- endif %} 45 | {%- endif %} 46 | 47 | {%- if not modulefirst and not is_namespace %} 48 | Module contents: 49 | 50 | {{ automodule(pkgname, automodule_options) }} 51 | {% endif %} 52 | -------------------------------------------------------------------------------- /docs/_templates/apidoc/toc.rst_t: -------------------------------------------------------------------------------- 1 | {{ header | heading }} 2 | 3 | The finn-base sources are divided into different modules. They are listed below. 4 | 5 | .. toctree:: 6 | :maxdepth: {{ maxdepth }} 7 | {% for docname in docnames %} 8 | {{ docname }} 9 | {%- endfor %} 10 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/genindex.rst: -------------------------------------------------------------------------------- 1 | .. This file is a placeholder and will be replaced 2 | 3 | ***** 4 | Index 5 | ***** 6 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ********* 2 | finn-base 3 | ********* 4 | 5 | .. note:: **finn-base** is currently under active development. APIs will likely change. 6 | 7 | ``finn-base`` is part of the `FINN 8 | project `__ and provides the core 9 | infrastructure for the `FINN 10 | compiler `__, including: 11 | 12 | - wrapper around ONNX models for easier manipulation 13 | - infrastructure for applying transformation and analysis passes on 14 | ONNX graphs 15 | - infrastructure for defining and executing custom ONNX ops (for 16 | verification and code generation) 17 | - extensions to ONNX models using annotations, including few-bit data 18 | types, sparsity and data layout specifiers 19 | - several transformation passes, including topological sorting, 20 | constant folding and convolution lowering 21 | - several custom ops including im2col and multi-thresholding for 22 | quantized activations 23 | - several utility functions, including packing for few-bit integers 24 | 25 | Installation 26 | ============ 27 | 28 | Install with full functionality including documentation building: 29 | 30 | :: 31 | 32 | pip install finn-base[onnx,pyverilator,docs] 33 | 34 | Lightweight install for e.g. access to data packing utility functions: 35 | 36 | :: 37 | 38 | pip install finn-base 39 | 40 | Testing 41 | ======= 42 | 43 | With Docker CE installed, execute the following in the repo root: 44 | 45 | :: 46 | 47 | ./run-docker.sh tests 48 | 49 | Alternatively, pull requests to `dev` will trigger GitHub Actions for the above. 50 | 51 | 52 | .. toctree:: 53 | :maxdepth: 2 54 | :hidden: 55 | 56 | Overview 57 | Tutorials 58 | API 59 | License 60 | Contributors 61 | Index 62 | 63 | 64 | * :ref:`modindex` 65 | * :ref:`search` 66 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | License 3 | ******* 4 | 5 | .. include:: ../LICENSE.txt 6 | -------------------------------------------------------------------------------- /docs/tutorials.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | ********* 4 | Tutorials 5 | ********* 6 | 7 | The FINN compiler repository FINN provides several Jupyter notebooks that can help to get familiar with the basics, the internals and the end-to-end flow in FINN. 8 | All Jupyter notebooks can be found in the FINN compiler repository under the `notebook folder `_. 9 | Some of those notebooks are specific to FPGA dataflow-style deployment, 10 | but the ones highlighted below are more generic, relating to the core 11 | infrastructure that ``finn-base`` provides. 12 | 13 | Basics 14 | ====== 15 | 16 | The notebooks in this folder should give a basic insight into FINN, how to get started and the basic concepts. 17 | 18 | * 0_how_to_work_with_onnx 19 | 20 | * This notebook can help you to learn how to create and manipulate a simple ONNX model, also by using FINN 21 | 22 | Advanced 23 | ======== 24 | 25 | The notebooks in this folder are more developer oriented. They should help you to get familiar with the principles in FINN and how to add new content regarding these concepts. 26 | 27 | * 0_custom_analysis_pass 28 | 29 | * Explains what an analysis pass is and how to write one for FINN. 30 | 31 | * 1_custom_transformation_pass 32 | 33 | * Explains what a transformation pass is and how to write one for FINN. 34 | 35 | * 2_custom_op 36 | 37 | * Explains the basics of FINN custom ops and how to define a new one. 38 | -------------------------------------------------------------------------------- /run-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2020 Xilinx, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of Xilinx nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | RED='\033[0;31m' 31 | GREEN='\033[0;32m' 32 | NC='\033[0m' # No Color 33 | 34 | # Green echo 35 | gecho () { 36 | echo -e "${GREEN}$1${NC}" 37 | } 38 | 39 | # Red echo 40 | recho () { 41 | echo -e "${RED}$1${NC}" 42 | } 43 | 44 | DOCKER_GROUP=$(id -gn) 45 | DOCKER_GID=$(id -g) 46 | DOCKER_USER=$(id -un) 47 | DOCKER_UID=$(id -u) 48 | DOCKER_TAG="finn-base" 49 | 50 | # Absolute path to this script. 51 | SCRIPT=$(readlink -f "$0") 52 | # Absolute path of dir this script is in. 53 | SCRIPTPATH=$(dirname "${SCRIPT}") 54 | 55 | # Take build dir from environment variable, otherwise use this default 56 | : ${FINN_HOST_BUILD_DIR="/tmp/finn-base_dev"} 57 | # Ensure build dir exists locally 58 | mkdir -p $FINN_HOST_BUILD_DIR 59 | 60 | DOCKER_INTERACTIVE="" 61 | 62 | if [ "$1" = "tests" ]; then 63 | echo "Running test suite" 64 | DOCKER_CMD="run_tests.sh" 65 | elif [ "$1" = "docs" ]; then 66 | echo "Building docs" 67 | DOCKER_CMD="build_docs.sh" 68 | elif [ "$1" = "bash" ]; then 69 | gecho "Running container in interactive mode" 70 | DOCKER_CMD="bash" 71 | DOCKER_INTERACTIVE="-it" 72 | else 73 | recho "Provide one of the following options to run-docker.sh:" 74 | recho " {tests, docs, bash}" 75 | exit -1 76 | fi 77 | 78 | gecho "Mounting $FINN_HOST_BUILD_DIR into $FINN_HOST_BUILD_DIR" 79 | 80 | # Build the finn-base docker image 81 | docker build -f docker/Dockerfile -t ${DOCKER_TAG} \ 82 | --build-arg GROUP=${DOCKER_GROUP} \ 83 | --build-arg GID=${DOCKER_GID} \ 84 | --build-arg USER=${DOCKER_USER} \ 85 | --build-arg UID=${DOCKER_UID} \ 86 | . 87 | 88 | # Launch container with current directory mounted 89 | docker run -t --rm ${DOCKER_INTERACTIVE} \ 90 | -v ${SCRIPTPATH}:/workspace/finn-base \ 91 | -v $FINN_HOST_BUILD_DIR:$FINN_HOST_BUILD_DIR \ 92 | -e FINN_BUILD_DIR=$FINN_HOST_BUILD_DIR \ 93 | ${DOCKER_TAG} ${DOCKER_CMD} 94 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # This file is used to configure your project. 2 | # Read more about the various options under: 3 | # http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files 4 | 5 | [metadata] 6 | name = finn-base 7 | description = Components for ONNX graph manipulation and custom execution 8 | author = Yaman Umuroglu 9 | author-email = yamanu@xilinx.com 10 | license = new-bsd 11 | long-description = file: README.md 12 | long-description-content-type = text/markdown 13 | url = https://xilinx.github.io/finn/ 14 | project-urls = 15 | Documentation = https://finn-base.readthedocs.io/ 16 | # Change if running only on Windows, Mac or Linux (comma-separated) 17 | platforms = Linux 18 | # Add here all kinds of additional classifiers as defined under 19 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 20 | classifiers = 21 | Development Status :: 4 - Beta 22 | Programming Language :: Python 23 | Operating System :: POSIX :: Linux 24 | 25 | [options] 26 | zip_safe = False 27 | packages = find_namespace: 28 | include_package_data = True 29 | package_dir = 30 | =src 31 | # DON'T CHANGE THE FOLLOWING LINE! IT WILL BE UPDATED BY PYSCAFFOLD! 32 | setup_requires = pyscaffold>=3.2a0,<3.3a0 33 | # Add here dependencies of your project (semicolon/line-separated), e.g. 34 | install_requires = 35 | bitstring>=3.1.7 36 | numpy 37 | # The usage of test_requires is discouraged, see `Dependency Management` docs 38 | # tests_require = pytest; pytest-cov 39 | # Require a specific Python version, e.g. Python 2.7 or >= 3.4 40 | # python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* 41 | 42 | [options.packages.find] 43 | where = src 44 | exclude = 45 | tests 46 | 47 | [options.extras_require] 48 | # Add here additional requirements for extra features, to install with: 49 | # `pip install finn-base[PDF]` like: 50 | # PDF = ReportLab; RXP 51 | # Add here test requirements (semicolon/line-separated) 52 | pyverilator = 53 | pyverilator @ https://github.com/maltanar/pyverilator/tarball/master#egg=pyverilator" 54 | docs = 55 | sphinx>=3.2.1 56 | sphinx_rtd_theme>=0.5.0 57 | onnx = 58 | clize==4.1.1 59 | onnx==1.7.0 60 | onnxruntime==1.4.0 61 | toposort>=1.5.0 62 | testing = 63 | pytest 64 | pytest-cov 65 | 66 | [options.entry_points] 67 | console_scripts = 68 | inference_cost = finn.util.inference_cost:main 69 | # Add here console scripts like: 70 | # console_scripts = 71 | # script_name = finn.finn_base.module:function 72 | # For example: 73 | # console_scripts = 74 | # fibonacci = finn.finn_base.skeleton:run 75 | # And any other entry points, for example: 76 | # pyscaffold.cli = 77 | # awesome = pyscaffoldext.awesome.extension:AwesomeExtension 78 | 79 | [test] 80 | # py.test options when running `python setup.py test` 81 | # addopts = --verbose 82 | extras = True 83 | 84 | [tool:pytest] 85 | # Options for py.test: 86 | # Specify command line options as you would do when invoking py.test directly. 87 | # e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml 88 | # in order to write a coverage file that can be read by Jenkins. 89 | addopts = 90 | --cov finn --cov-report term-missing 91 | --verbose 92 | norecursedirs = 93 | dist 94 | build 95 | .tox 96 | testpaths = tests 97 | 98 | [aliases] 99 | dists = bdist_wheel 100 | docs = build_sphinx 101 | 102 | [bdist_wheel] 103 | # Use this option if your package is pure-python 104 | universal = 0 105 | 106 | [build_sphinx] 107 | source-dir = docs 108 | build-dir = docs/_build 109 | warning-is-error = True 110 | keep-going = True 111 | 112 | [devpi:upload] 113 | # Options for the devpi: PyPI server and packaging tool 114 | # VCS export must be deactivated since we are using setuptools-scm 115 | no-vcs = 1 116 | formats = bdist_wheel 117 | 118 | [flake8] 119 | # Some sane defaults for the code style checker flake8 120 | max-line-length = 88 121 | extend-ignore = E203 122 | exclude = 123 | .tox 124 | build 125 | dist 126 | .eggs 127 | docs/conf.py 128 | 129 | [pyscaffold] 130 | # PyScaffold's parameters when the project was created. 131 | # This will be used when updating. Do not change! 132 | version = 3.2.3 133 | package = finn_base 134 | extensions = 135 | namespace 136 | pre_commit 137 | namespace = finn 138 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Setup file for finn_base. 4 | Use setup.cfg to configure your project. 5 | 6 | This file was generated with PyScaffold 3.2.3. 7 | PyScaffold helps you to put up the scaffold of your new Python project. 8 | Learn more under: https://pyscaffold.org/ 9 | """ 10 | from pkg_resources import VersionConflict, require 11 | from setuptools import setup 12 | 13 | import sys 14 | 15 | try: 16 | require("setuptools>=38.3") 17 | except VersionConflict: 18 | print("Error: version of setuptools is too old (<38.3)!") 19 | sys.exit(1) 20 | 21 | 22 | if __name__ == "__main__": 23 | setup(use_pyscaffold=True) 24 | -------------------------------------------------------------------------------- /src/finn/analysis/base.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | """ 31 | How to write an analysis pass for FINN 32 | -------------------------------------- 33 | 34 | An analysis pass traverses the graph structure and produces information about 35 | certain properties. The convention is to take in a ModelWrapper, and return 36 | a dictionary of named properties that the analysis extracts. 37 | """ 38 | -------------------------------------------------------------------------------- /src/finn/analysis/topology.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | 31 | 32 | def is_linear(model): 33 | """Checks whether the given model graph is linear. This is done by looking 34 | at the fan-out of each tensor. All tensors have a fan-out <= 1 in a linear 35 | graph. 36 | 37 | Returns {"is_linear": Bool}.""" 38 | per_tensor_fanouts = get_per_tensor_fanouts(model) 39 | # check for tensors that have fanout > 1 40 | multi_fanouts = list(filter(lambda x: x[1] > 1, per_tensor_fanouts.items())) 41 | return {"is_linear": len(multi_fanouts) == 0} 42 | 43 | 44 | def get_per_tensor_fanouts(model): 45 | """Returns a dictionary of {tensor_name: tensor_fanout} for the model.""" 46 | # make execution context to get a list of tensors 47 | per_tensor_fanouts = model.make_empty_exec_context() 48 | # replace every tensor with its fanout 49 | for tensor_name in per_tensor_fanouts.keys(): 50 | per_tensor_fanouts[tensor_name] = model.get_tensor_fanout(tensor_name) 51 | return per_tensor_fanouts 52 | 53 | 54 | def all_tensors_f32(model): 55 | """Checks whether all tensors have a float32 dtype, extra quantization 56 | annotations notwithstanding. 57 | 58 | Returns {"all_tensors_f32": Bool}.""" 59 | all_tensors = model.make_empty_exec_context().items() 60 | non_f32_tensors = filter(lambda x: x[1].dtype != np.float32, all_tensors) 61 | return {"all_tensors_f32": len(list(non_f32_tensors)) == 0} 62 | 63 | 64 | def node_inputs_in_expected_order(model): 65 | """Verifies that the node inputs are ordered in the way that FINN expects 66 | them. When a node has a mixture of static (= constant, initialized) inputs 67 | and dynamic inputs, the dynamic input should come first, followed by the 68 | static one. Only verifiable for a small subset of op_types for now. 69 | 70 | Returns {"node_inputs_in_expected_order": Bool}.""" 71 | op_types = ["MatMul", "Conv", "Add", "Mul"] 72 | nodes = filter(lambda x: x.op_type in op_types, model.graph.node) 73 | all_OK = True 74 | for n in nodes: 75 | all_OK = all_OK and len(list(n.input)) == 2 76 | # input 0 should be dynamic, no initializer 77 | all_OK = all_OK and (model.get_initializer(n.input[0]) is None) 78 | # input 1 should be static (unless eltwise add) 79 | if n.op_type != "Add": 80 | all_OK = all_OK and (model.get_initializer(n.input[1]) is not None) 81 | return {"node_inputs_in_expected_order": all_OK} 82 | 83 | 84 | def nodes_topologically_sorted(model): 85 | """Verifies that graph.node is topologically sorted. This is required by the 86 | ONNX specification. 87 | 88 | Returns {"nodes_topologically_sorted": Bool}.""" 89 | 90 | # get successors of every node and check that 91 | # successor index > current node index 92 | 93 | all_OK = True 94 | for n in model.graph.node: 95 | successors = model.find_direct_successors(n) 96 | if successors is not None: 97 | for successor in successors: 98 | # check the condition by checking the antithesis 99 | index_n = model.get_node_index(n) 100 | index_suc = model.get_node_index(successor) 101 | if index_n > index_suc: 102 | all_OK = False 103 | 104 | return {"nodes_topologically_sorted": all_OK} 105 | -------------------------------------------------------------------------------- /src/finn/core/data_layout.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # predefined lists of strings to have a cannonical way of expresing data layout 30 | # annotations 31 | 32 | NHWC = ["N", "H", "W", "C"] 33 | NCHW = ["N", "C", "H", "W"] 34 | NCW = ["N", "C", "W"] 35 | NWC = ["N", "W", "C"] 36 | NC = ["N", "C"] 37 | UNKNOWN = [] 38 | 39 | 40 | def is_channels_last(layout): 41 | return layout[-1] == "C" 42 | 43 | 44 | def get_channels_last_layout_for_ndims(ndims): 45 | return {4: NHWC, 3: NWC, 2: NC}[ndims] 46 | 47 | 48 | def get_channels_first_layout_for_ndims(ndims): 49 | return {4: NCHW, 3: NCW, 2: NC}[ndims] 50 | -------------------------------------------------------------------------------- /src/finn/core/execute_custom_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import finn.custom_op.registry as registry 30 | 31 | 32 | def execute_custom_node(node, context, graph): 33 | """Call custom implementation to execute a single custom node. 34 | Input/output provided via context.""" 35 | op_type = node.op_type 36 | try: 37 | # lookup op_type in registry of CustomOps 38 | inst = registry.getCustomOp(node) 39 | inst.execute_node(context, graph) 40 | except KeyError: 41 | # exception if op_type is not supported 42 | raise Exception("Custom op_type %s is currently not supported." % op_type) 43 | -------------------------------------------------------------------------------- /src/finn/custom_op/general/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from finn.custom_op.general.bipolar_quant import BipolarQuant 30 | from finn.custom_op.general.debugmarker import DebugMarker 31 | from finn.custom_op.general.genericpartition import GenericPartition 32 | from finn.custom_op.general.im2col import Im2Col 33 | from finn.custom_op.general.maxpoolnhwc import MaxPoolNHWC 34 | from finn.custom_op.general.multithreshold import MultiThreshold 35 | from finn.custom_op.general.quant import Quant 36 | from finn.custom_op.general.quantavgpool2d import QuantAvgPool2d 37 | from finn.custom_op.general.trunc import Trunc 38 | from finn.custom_op.general.xnorpopcount import XnorPopcountMatMul 39 | 40 | custom_op = dict() 41 | 42 | custom_op["DebugMarker"] = DebugMarker 43 | custom_op["QuantAvgPool2d"] = QuantAvgPool2d 44 | custom_op["MaxPoolNHWC"] = MaxPoolNHWC 45 | custom_op["GenericPartition"] = GenericPartition 46 | custom_op["MultiThreshold"] = MultiThreshold 47 | custom_op["XnorPopcountMatMul"] = XnorPopcountMatMul 48 | custom_op["Im2Col"] = Im2Col 49 | custom_op["Quant"] = Quant 50 | custom_op["Trunc"] = Trunc 51 | custom_op["BipolarQuant"] = BipolarQuant 52 | -------------------------------------------------------------------------------- /src/finn/custom_op/general/bipolar_quant.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx.helper as helper 31 | 32 | from finn.core.datatype import DataType 33 | from finn.custom_op.base import CustomOp 34 | 35 | 36 | def binary_quant(inp_tensor, scale): 37 | # ToDo: Update this link, when the PR gets merged 38 | # Port of IntQuant class from Brevitas: https://bit.ly/2S6qvZJ 39 | 40 | # Quantizing 41 | y_int = inp_tensor 42 | y_ones = np.ones(y_int.shape, dtype=y_int.dtype) 43 | y_int = np.where(y_int >= 0.0, y_ones, -y_ones) 44 | # Scaling 45 | out_tensor = y_int * scale 46 | 47 | return out_tensor 48 | 49 | 50 | class BipolarQuant(CustomOp): 51 | """Bipolar quantization operation for QONNX. Takes four inputs: 52 | - input tensor to quantize 53 | - the scale 54 | 55 | The output is a tensor of the same shape as the input tensor, with quantized 56 | values. 57 | """ 58 | 59 | def get_nodeattr_types(self): 60 | return dict() 61 | 62 | def make_shape_compatible_op(self, model): 63 | node = self.onnx_node 64 | return helper.make_node("Identity", [node.input[0]], [node.output[0]]) 65 | 66 | def get_integer_datatype(self, model): 67 | return DataType["BIPOLAR"] 68 | 69 | def get_output_dtype(self, model): 70 | node = self.onnx_node 71 | # scale must be read from initializers 72 | scale = model.get_initializer(node.input[1]) 73 | # determine the FINN DataType 74 | unit_scale = np.all(scale == 1.0) 75 | if unit_scale: 76 | finn_dt = self.get_integer_datatype(model) 77 | else: 78 | finn_dt = DataType["FLOAT32"] 79 | 80 | return finn_dt 81 | 82 | def infer_node_datatype(self, model): 83 | try: 84 | finn_dt = self.get_output_dtype(model) 85 | except AssertionError: 86 | finn_dt = DataType["FLOAT32"] 87 | node = self.onnx_node 88 | model.set_tensor_datatype(node.output[0], finn_dt) 89 | 90 | def execute_node(self, context, graph): 91 | node = self.onnx_node 92 | # save inputs 93 | inp_tensor = context[node.input[0]] 94 | scale = context[node.input[1]] 95 | # calculate output 96 | ret = binary_quant(inp_tensor, scale) 97 | # ensure output is ndarray (even if 0d) 98 | # since numpy silently flattens 0d arrays to scalars 99 | # more: https://github.com/numpy/numpy/issues/13105 100 | if not isinstance(ret, np.ndarray): 101 | ret = np.asarray(ret, dtype=np.float32) 102 | # set context according to output name 103 | context[node.output[0]] = ret 104 | 105 | def verify_node(self): 106 | pass 107 | -------------------------------------------------------------------------------- /src/finn/custom_op/general/debugmarker.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from onnx import helper 30 | 31 | from finn.custom_op.base import CustomOp 32 | 33 | 34 | class DebugMarker(CustomOp): 35 | def get_nodeattr_types(self): 36 | return {"export_debug_name": ("s", True, "")} 37 | 38 | def make_shape_compatible_op(self, model): 39 | node = self.onnx_node 40 | return helper.make_node("Identity", [node.input[0]], [node.output[0]]) 41 | 42 | def infer_node_datatype(self, model): 43 | node = self.onnx_node 44 | # data type stays the same 45 | dtype = model.get_tensor_datatype(node.input[0]) 46 | model.set_tensor_datatype(node.output[0], dtype) 47 | # create quantization annotation for debug marker 48 | model.set_tensor_datatype(self.get_nodeattr("export_debug_name"), dtype) 49 | 50 | def execute_node(self, context, graph): 51 | node = self.onnx_node 52 | inp_name = node.input[0] 53 | out_name = node.output[0] 54 | inp = context[inp_name] 55 | context[out_name] = inp 56 | # insert debug marker output as separate tensor 57 | context[self.get_nodeattr("export_debug_name")] = inp 58 | 59 | def verify_node(self): 60 | info_messages = [] 61 | return info_messages 62 | -------------------------------------------------------------------------------- /src/finn/custom_op/general/genericpartition.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from finn.custom_op.base import CustomOp 30 | 31 | 32 | class GenericPartition(CustomOp): 33 | """Class that corresponds to the meta/container node GenericPartition 34 | which is a placeholder for a group of nodes that have been separated 35 | out into a FINN-ONNX model of its own.""" 36 | 37 | def get_nodeattr_types(self): 38 | return { 39 | "model": ("s", True, ""), 40 | } 41 | 42 | def make_shape_compatible_op(self, model): 43 | pass 44 | 45 | def infer_node_datatype(self, model): 46 | pass 47 | 48 | def execute_node(self, context, graph): 49 | pass 50 | 51 | def verify_node(self): 52 | info_messages = [] 53 | 54 | # verify number of attributes 55 | num_of_attr = 1 56 | if len(self.onnx_node.attribute) == num_of_attr: 57 | info_messages.append("The number of attributes is correct") 58 | else: 59 | info_messages.append( 60 | """The number of attributes is incorrect, 61 | {} should have {} attributes""".format( 62 | self.onnx_node.op_type, num_of_attr 63 | ) 64 | ) 65 | # verify that all necessary attributes exist 66 | try: 67 | self.get_nodeattr("model") 68 | info_messages.append("All necessary attributes exist") 69 | except Exception: 70 | info_messages.append( 71 | """The necessary attributes do not exist. 72 | GenericPartition needs the following attribute(s): 73 | model""" 74 | ) 75 | 76 | return info_messages 77 | -------------------------------------------------------------------------------- /src/finn/custom_op/general/trunc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx.helper as helper 31 | 32 | from finn.core.datatype import DataType 33 | from finn.custom_op.base import CustomOp 34 | from finn.custom_op.general.quant import resolve_rounding_mode 35 | 36 | 37 | def trunc(inp_tensor, scale, zeropt, input_bit_width, output_bit_width, rounding_mode): 38 | # Port of TruncIntQuant class from Brevitas: https://bit.ly/3wzIpTR 39 | 40 | # Scaling 41 | y = inp_tensor / scale 42 | y = y + zeropt 43 | # Rounding 44 | y = np.round(y) 45 | # Truncate 46 | trunc_bit_width = input_bit_width - output_bit_width 47 | trunc_scale = 2.0**trunc_bit_width 48 | y = y / trunc_scale 49 | 50 | # To int 51 | rounding_fx = resolve_rounding_mode(rounding_mode) 52 | y = rounding_fx(y) 53 | 54 | # Rescale 55 | y = y - zeropt 56 | y = y * scale 57 | 58 | return y 59 | 60 | 61 | class Trunc(CustomOp): 62 | """Generic truncation operation for QONNX. Takes four inputs: 63 | - input tensor to truncate 64 | - the scale 65 | - the zero-point 66 | - the truncation bit-width 67 | 68 | The output is a tensor of the same shape as the input tensor, with truncated 69 | values. 70 | """ 71 | 72 | def get_nodeattr_types(self): 73 | return { 74 | # The rounding mode, which is used for the trunc function 75 | "rounding_mode": ("s", True, "FLOOR"), 76 | } 77 | 78 | def make_shape_compatible_op(self, model): 79 | node = self.onnx_node 80 | return helper.make_node("Identity", [node.input[0]], [node.output[0]]) 81 | 82 | def infer_node_datatype(self, model): 83 | node = self.onnx_node 84 | model.set_tensor_datatype(node.output[0], DataType["FLOAT32"]) 85 | 86 | def execute_node(self, context, graph): 87 | node = self.onnx_node 88 | # save inputs 89 | inp_tensor = context[node.input[0]] 90 | scale = context[node.input[1]] 91 | zeropt = context[node.input[2]] 92 | input_bit_width = context[node.input[3]] 93 | output_bit_width = context[node.input[4]] 94 | # save attributes 95 | rounding_mode = self.get_nodeattr("rounding_mode") 96 | # calculate output 97 | ret = trunc( 98 | inp_tensor, scale, zeropt, input_bit_width, output_bit_width, rounding_mode 99 | ) 100 | # set context according to output name 101 | context[node.output[0]] = ret 102 | 103 | def verify_node(self): 104 | pass 105 | -------------------------------------------------------------------------------- /src/finn/custom_op/general/xnorpopcount.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx.helper as helper 31 | 32 | from finn.core.datatype import DataType 33 | from finn.custom_op.base import CustomOp 34 | 35 | 36 | def xnorpopcountmatmul(inp0, inp1): 37 | """Simulates XNOR-popcount matrix multiplication as a regular bipolar 38 | matrix multiplication followed by some post processing.""" 39 | # extract the operand shapes 40 | # (M, K0) = inp0.shape 41 | # (K1, N) = inp1.shape 42 | K0 = inp0.shape[-1] 43 | K1 = inp1.shape[0] 44 | # make sure shapes are compatible with matmul 45 | assert K0 == K1, "Matrix shapes are not compatible with matmul." 46 | K = K0 47 | # convert binary inputs to bipolar 48 | inp0_bipolar = 2.0 * inp0 - 1.0 49 | inp1_bipolar = 2.0 * inp1 - 1.0 50 | # call regular numpy matrix multiplication 51 | out = np.matmul(inp0_bipolar, inp1_bipolar) 52 | # XNOR-popcount does not produce the regular dot product result -- 53 | # it returns the number of +1s after XNOR. let P be the number of +1s 54 | # and N be the number of -1s. XNOR-popcount returns P, whereas the 55 | # regular dot product result from numpy is P-N, so we need to apply 56 | # some correction. 57 | # out = P-N 58 | # K = P+N 59 | # out + K = 2P, so P = (out + K)/2 60 | return (out + K) * 0.5 61 | 62 | 63 | class XnorPopcountMatMul(CustomOp): 64 | """Class that corresponds to a XNOR-popcount matrix 65 | multiplication node.""" 66 | 67 | def get_nodeattr_types(self): 68 | return {} 69 | 70 | def make_shape_compatible_op(self, model): 71 | node = self.onnx_node 72 | return helper.make_node( 73 | "MatMul", [node.input[0], node.input[1]], [node.output[0]] 74 | ) 75 | 76 | def infer_node_datatype(self, model): 77 | node = self.onnx_node 78 | # ensure inputs are binary 79 | assert ( 80 | model.get_tensor_datatype(node.input[0]) == DataType["BINARY"] 81 | ), """FINN 82 | DataType of first input is not set to BINARY as it should be.""" 83 | assert ( 84 | model.get_tensor_datatype(node.input[1]) == DataType["BINARY"] 85 | ), """FINN 86 | DataTypes of second input is not set to BINARY as it should be.""" 87 | # XNOR-popcount produces unsigned integers, assume uint32 88 | model.set_tensor_datatype(node.output[0], DataType["UINT32"]) 89 | 90 | def execute_node(self, context, graph): 91 | node = self.onnx_node 92 | # save inputs 93 | inp0 = context[node.input[0]] 94 | inp1 = context[node.input[1]] 95 | # calculate output 96 | output = xnorpopcountmatmul(inp0, inp1) 97 | # set context according to output name 98 | context[node.output[0]] = output 99 | 100 | def verify_node(self): 101 | info_messages = [] 102 | 103 | # verify number of attributes 104 | num_of_attr = 0 105 | if len(self.onnx_node.attribute) == num_of_attr: 106 | info_messages.append("The number of attributes is correct") 107 | else: 108 | info_messages.append( 109 | """The number of attributes is incorrect, 110 | {} should have {} attributes""".format( 111 | self.onnx_node.op_type, num_of_attr 112 | ) 113 | ) 114 | 115 | # verify that all necessary attributes exist 116 | info_messages.append("XnorPopcountMatMul should not have any attributes") 117 | 118 | # verify the number of inputs 119 | if len(self.onnx_node.input) == 2: 120 | info_messages.append("The number of inputs is correct") 121 | else: 122 | info_messages.append("XnorPopcountMatMul needs 2 data inputs") 123 | 124 | return info_messages 125 | -------------------------------------------------------------------------------- /src/finn/custom_op/registry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import importlib 30 | 31 | 32 | def getCustomOp(node): 33 | "Return a FINN CustomOp instance for the given ONNX node, if it exists." 34 | op_type = node.op_type 35 | domain = node.domain 36 | try: 37 | opset_module = importlib.import_module(domain) 38 | assert type(opset_module.custom_op) is dict, ( 39 | "custom_op dict not found in Python module %s" % domain 40 | ) 41 | inst_wrapper = opset_module.custom_op[op_type] 42 | inst = inst_wrapper(node) 43 | return inst 44 | except ModuleNotFoundError: 45 | raise Exception( 46 | "Could not load custom opset %s, check your PYTHONPATH" % domain 47 | ) 48 | except KeyError: 49 | raise Exception("Op %s not found in custom opset %s" % (op_type, domain)) 50 | -------------------------------------------------------------------------------- /src/finn/data/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/finn/data/onnx/mnist-conv/README.md: -------------------------------------------------------------------------------- 1 | 2 | Source: https://github.com/onnx/models/tree/master/vision/classification/mnist#model 3 | -------------------------------------------------------------------------------- /src/finn/data/onnx/mnist-conv/model.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xilinx/finn-base/beae9785f2e29ef021541a3499832f3754e1026d/src/finn/data/onnx/mnist-conv/model.onnx -------------------------------------------------------------------------------- /src/finn/data/onnx/mnist-conv/test_data_set_0/input_0.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xilinx/finn-base/beae9785f2e29ef021541a3499832f3754e1026d/src/finn/data/onnx/mnist-conv/test_data_set_0/input_0.pb -------------------------------------------------------------------------------- /src/finn/data/onnx/mnist-conv/test_data_set_0/output_0.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xilinx/finn-base/beae9785f2e29ef021541a3499832f3754e1026d/src/finn/data/onnx/mnist-conv/test_data_set_0/output_0.pb -------------------------------------------------------------------------------- /src/finn/data/verilog/myadd/myadd_myadd.v: -------------------------------------------------------------------------------- 1 | // ============================================================== 2 | // RTL generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC 3 | // Version: 2019.1 4 | // Copyright (C) 1986-2019 Xilinx, Inc. All Rights Reserved. 5 | // 6 | // =========================================================== 7 | 8 | `timescale 1 ns / 1 ps 9 | 10 | (* CORE_GENERATION_INFO="myadd_myadd,hls_ip_2019_1,{HLS_INPUT_TYPE=cxx,HLS_INPUT_FLOAT=0,HLS_INPUT_FIXED=1,HLS_INPUT_PART=xc7z020-clg400-1,HLS_INPUT_CLOCK=5.000000,HLS_INPUT_ARCH=others,HLS_SYN_CLOCK=3.552000,HLS_SYN_LAT=0,HLS_SYN_TPT=none,HLS_SYN_MEM=0,HLS_SYN_DSP=0,HLS_SYN_FF=144,HLS_SYN_LUT=271,HLS_VERSION=2019_1}" *) 11 | 12 | module myadd_myadd ( 13 | s_axi_control_AWVALID, 14 | s_axi_control_AWREADY, 15 | s_axi_control_AWADDR, 16 | s_axi_control_WVALID, 17 | s_axi_control_WREADY, 18 | s_axi_control_WDATA, 19 | s_axi_control_WSTRB, 20 | s_axi_control_ARVALID, 21 | s_axi_control_ARREADY, 22 | s_axi_control_ARADDR, 23 | s_axi_control_RVALID, 24 | s_axi_control_RREADY, 25 | s_axi_control_RDATA, 26 | s_axi_control_RRESP, 27 | s_axi_control_BVALID, 28 | s_axi_control_BREADY, 29 | s_axi_control_BRESP, 30 | ap_clk, 31 | ap_rst_n, 32 | interrupt 33 | ); 34 | 35 | parameter C_S_AXI_CONTROL_DATA_WIDTH = 32; 36 | parameter C_S_AXI_CONTROL_ADDR_WIDTH = 6; 37 | parameter C_S_AXI_DATA_WIDTH = 32; 38 | 39 | parameter C_S_AXI_CONTROL_WSTRB_WIDTH = (32 / 8); 40 | parameter C_S_AXI_WSTRB_WIDTH = (32 / 8); 41 | 42 | input s_axi_control_AWVALID; 43 | output s_axi_control_AWREADY; 44 | input [C_S_AXI_CONTROL_ADDR_WIDTH - 1:0] s_axi_control_AWADDR; 45 | input s_axi_control_WVALID; 46 | output s_axi_control_WREADY; 47 | input [C_S_AXI_CONTROL_DATA_WIDTH - 1:0] s_axi_control_WDATA; 48 | input [C_S_AXI_CONTROL_WSTRB_WIDTH - 1:0] s_axi_control_WSTRB; 49 | input s_axi_control_ARVALID; 50 | output s_axi_control_ARREADY; 51 | input [C_S_AXI_CONTROL_ADDR_WIDTH - 1:0] s_axi_control_ARADDR; 52 | output s_axi_control_RVALID; 53 | input s_axi_control_RREADY; 54 | output [C_S_AXI_CONTROL_DATA_WIDTH - 1:0] s_axi_control_RDATA; 55 | output [1:0] s_axi_control_RRESP; 56 | output s_axi_control_BVALID; 57 | input s_axi_control_BREADY; 58 | output [1:0] s_axi_control_BRESP; 59 | input ap_clk; 60 | input ap_rst_n; 61 | output interrupt; 62 | 63 | wire ap_start; 64 | wire ap_done; 65 | wire ap_idle; 66 | wire ap_ready; 67 | wire [31:0] a_V; 68 | wire [31:0] b_V; 69 | wire [31:0] ap_return; 70 | reg ap_rst_n_inv; 71 | 72 | myadd_myadd_control_s_axi #( 73 | .C_S_AXI_ADDR_WIDTH( C_S_AXI_CONTROL_ADDR_WIDTH ), 74 | .C_S_AXI_DATA_WIDTH( C_S_AXI_CONTROL_DATA_WIDTH )) 75 | myadd_control_s_axi_U( 76 | .AWVALID(s_axi_control_AWVALID), 77 | .AWREADY(s_axi_control_AWREADY), 78 | .AWADDR(s_axi_control_AWADDR), 79 | .WVALID(s_axi_control_WVALID), 80 | .WREADY(s_axi_control_WREADY), 81 | .WDATA(s_axi_control_WDATA), 82 | .WSTRB(s_axi_control_WSTRB), 83 | .ARVALID(s_axi_control_ARVALID), 84 | .ARREADY(s_axi_control_ARREADY), 85 | .ARADDR(s_axi_control_ARADDR), 86 | .RVALID(s_axi_control_RVALID), 87 | .RREADY(s_axi_control_RREADY), 88 | .RDATA(s_axi_control_RDATA), 89 | .RRESP(s_axi_control_RRESP), 90 | .BVALID(s_axi_control_BVALID), 91 | .BREADY(s_axi_control_BREADY), 92 | .BRESP(s_axi_control_BRESP), 93 | .ACLK(ap_clk), 94 | .ARESET(ap_rst_n_inv), 95 | .ACLK_EN(1'b1), 96 | .ap_start(ap_start), 97 | .interrupt(interrupt), 98 | .ap_ready(ap_ready), 99 | .ap_done(ap_done), 100 | .ap_idle(ap_idle), 101 | .ap_return(ap_return), 102 | .a_V(a_V), 103 | .b_V(b_V) 104 | ); 105 | 106 | assign ap_done = ap_start; 107 | 108 | assign ap_idle = 1'b1; 109 | 110 | assign ap_ready = ap_start; 111 | 112 | assign ap_return = (b_V + a_V); 113 | 114 | always @ (*) begin 115 | ap_rst_n_inv = ~ap_rst_n; 116 | end 117 | 118 | endmodule //myadd_myadd 119 | -------------------------------------------------------------------------------- /src/finn/transformation/base.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """ 30 | Guide to writing FINN transformations 31 | ------------------------------------- 32 | 33 | * Your transformation must inherit the Transformation abstract base class. 34 | * Your transformation's apply function should take in a ModelWrapper, and return 35 | a tuple with (transformed_model: ModelWrapper, model_was_changed: Bool) 36 | * The transformations are meant to be applied using the .transform function 37 | in ModelWrapper. This makes a deep copy of the input model by default, so 38 | you don't have to. 39 | * model_was_changed indicates whether your transformation made any changes to 40 | the model. If you know your transformation needs to be called only once and 41 | repeated calls have no further effect, you can return False even if the model 42 | was changed. 43 | * You MUST return model_was_changed=False at some point when your transformation 44 | is called multiple times, otherwise apply_repeated() will loop infinitely. 45 | * If you cannot guarantee that the transformation will reach a fixed point, 46 | you must declare this, return model_was_changed = False and let the user 47 | manually re-apply the transform. 48 | """ 49 | 50 | import multiprocessing as mp 51 | from abc import ABC, abstractmethod 52 | 53 | from finn.util.basic import get_num_default_workers 54 | 55 | 56 | class Transformation(ABC): 57 | """Transformation class all transformations are based on. Contains only 58 | abstract method apply() every transformation has to fill.""" 59 | 60 | def __init__(self): 61 | super().__init__() 62 | 63 | @abstractmethod 64 | def apply(self, model): 65 | pass 66 | 67 | 68 | class NodeLocalTransformation(Transformation): 69 | """ 70 | Parent class for transformations, which can be executed locally to one node 71 | by accessing and modifying the attributes of only that node. 72 | This class can then automatically parallelize the transformation. 73 | Transformations sublcassing NodeLocalTransformation must implement the 74 | abstract method applyNodeLocal(). 75 | 76 | To control the degree of parallelization, specify the num_workers argument 77 | in the constructor, using one of the following values: 78 | * None: use NUM_DEFAULT_WORKERS environment variable 79 | * 0: use all available CPU cores 80 | * (any other int>0): set number of parallel workers 81 | """ 82 | 83 | def __init__(self, num_workers=None): 84 | super().__init__() 85 | if num_workers is None: 86 | self._num_workers = get_num_default_workers() 87 | else: 88 | self._num_workers = num_workers 89 | assert self._num_workers >= 0, "Number of workers must be nonnegative." 90 | if self._num_workers == 0: 91 | self._num_workers = mp.cpu_count() 92 | 93 | @abstractmethod 94 | def applyNodeLocal(self, node): 95 | pass 96 | 97 | def apply(self, model): 98 | # Remove old nodes from the current model 99 | old_nodes = [] 100 | for i in range(len(model.graph.node)): 101 | old_nodes.append(model.graph.node.pop()) 102 | 103 | # Execute transformation in parallel 104 | with mp.Pool(self._num_workers) as p: 105 | new_nodes_and_bool = p.map(self.applyNodeLocal, old_nodes, chunksize=1) 106 | 107 | # extract nodes and check if the transformation needs to run again 108 | # Note: .pop() had initially reversed the node order 109 | run_again = False 110 | for node, run in reversed(new_nodes_and_bool): 111 | # Reattach new nodes to old model 112 | model.graph.node.append(node) 113 | if run is True: 114 | run_again = True 115 | 116 | return (model, run_again) 117 | -------------------------------------------------------------------------------- /src/finn/transformation/double_to_single_float.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | 31 | from finn.transformation.base import Transformation 32 | 33 | 34 | class DoubleToSingleFloat(Transformation): 35 | """Convert any float64 initializers to float32.""" 36 | 37 | def apply(self, model): 38 | graph_modified = False 39 | init_names = [x.name for x in model.graph.initializer] 40 | for nm in init_names: 41 | init = model.get_initializer(nm) 42 | if init.dtype == np.float64: 43 | init_f32 = init.astype(np.float32) 44 | model.set_initializer(nm, init_f32) 45 | graph_modified = True 46 | return (model, graph_modified) 47 | -------------------------------------------------------------------------------- /src/finn/transformation/extend_partition.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from finn.core.modelwrapper import ModelWrapper 30 | from finn.transformation.base import Transformation 31 | from finn.transformation.general import SortGraph 32 | from finn.util.basic import get_by_name 33 | 34 | 35 | class ExtendPartition(Transformation): 36 | """Extends GenericPartition type nodes by inserting the graph pointed to by 37 | the model attribute. 38 | Argument 0: extend_index 39 | * List that contains the node indices of the GenericPartition nodes 40 | """ 41 | 42 | def __init__(self, extend_index): 43 | super().__init__() 44 | self.extend_index = extend_index 45 | 46 | def apply(self, model): 47 | graph = model.graph 48 | graph_modified = False 49 | 50 | partition_nodes_dict = { 51 | ind: n 52 | for ind, n in enumerate(graph.node) 53 | if n.op_type == "GenericPartition" 54 | } 55 | 56 | for k, v in partition_nodes_dict.items(): 57 | if k in self.extend_index: 58 | path_to_model = get_by_name(v.attribute, "model", "name").s.decode( 59 | "utf-8" 60 | ) 61 | model_partition = ModelWrapper(path_to_model) 62 | 63 | # Append nodes 64 | for partition_node in model_partition.graph.node: 65 | graph.node.append(partition_node) 66 | 67 | # Append value infos 68 | partition_valueinfos = [ 69 | x.name for x in model_partition.graph.value_info 70 | ] 71 | for vi_name in partition_valueinfos: 72 | vi = model_partition.get_tensor_valueinfo(vi_name) 73 | graph.value_info.append(vi) 74 | 75 | # Append initializers 76 | partition_initializers = [x for x in model_partition.graph.initializer] 77 | for i in partition_initializers: 78 | graph.initializer.append(i) 79 | 80 | # Append tensor annotations, except for the input/output tensors 81 | # of the partitioned graph, as these will be present in the 82 | # 'upper' model. 83 | in_out_names = [x.name for x in model_partition.graph.input] 84 | in_out_names += [x.name for x in model_partition.graph.output] 85 | partition_annotations = [ 86 | x 87 | for x in model_partition.graph.quantization_annotation 88 | if x.tensor_name not in in_out_names 89 | ] 90 | for a in partition_annotations: 91 | graph.quantization_annotation.append(a) 92 | 93 | graph.node.remove(v) 94 | graph_modified = True 95 | 96 | if graph_modified: 97 | model = model.transform(SortGraph()) 98 | 99 | return (model, graph_modified) 100 | -------------------------------------------------------------------------------- /src/finn/transformation/extract_conv_bias.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import warnings 30 | from onnx import TensorProto, helper 31 | 32 | from finn.transformation.base import Transformation 33 | 34 | 35 | class ExtractBiasFromConv(Transformation): 36 | """ 37 | Extracts the (optional) Bias from a Conv node and inserts it behind the 38 | Conv node as an Add node. 39 | """ 40 | 41 | def apply(self, model): 42 | graph = model.graph 43 | node_ind = 0 44 | for n in graph.node: 45 | node_ind += 1 46 | if n.op_type == "Conv": 47 | # Check if the node has a bias input 48 | if len(n.input) > 2: 49 | # Extract bias 50 | bias = model.get_initializer(n.input[2]) 51 | if bias is None: 52 | warnings.warn( 53 | f"Could not extract bias from Conv node {n}, " 54 | f"due to missing static initialization." 55 | ) 56 | continue 57 | 58 | # Insert bias as Add node behind the Conv node 59 | out_shape = model.get_tensor_shape(n.output[0]) 60 | # Reshape bias tensor 61 | add_shape = [1] * len(out_shape) 62 | # ToDo: this must change to "add_shape[-1] = bias.shape[0]" when 63 | # the channels last layout comes around. 64 | add_shape[1] = bias.shape[0] 65 | model.set_initializer(n.input[2], bias.reshape(add_shape)) 66 | 67 | act_add_tensor = helper.make_tensor_value_info( 68 | model.make_new_valueinfo_name(), 69 | TensorProto.FLOAT, 70 | out_shape, 71 | ) 72 | graph.value_info.append(act_add_tensor) 73 | 74 | add_node = helper.make_node( 75 | "Add", 76 | [act_add_tensor.name, n.input[2]], 77 | [n.output[0]], 78 | ) 79 | graph.node.insert(node_ind, add_node) 80 | 81 | # Repoint Conv output and remove bias tensor 82 | n.output[0] = act_add_tensor.name 83 | n.input.remove(n.input[2]) 84 | 85 | return model, True 86 | 87 | return model, False 88 | -------------------------------------------------------------------------------- /src/finn/transformation/fold_constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import finn.core.onnx_exec as oxe 30 | from finn.transformation.base import Transformation 31 | from finn.transformation.infer_shapes import InferShapes 32 | 33 | 34 | class FoldConstants(Transformation): 35 | """Replace the output of a node with const-only inputs with a precomputed 36 | result. Skip any op types given in exclude_op_types.""" 37 | 38 | def __init__(self, exclude_op_types=["Quant", "BipolarQuant"]): 39 | super().__init__() 40 | self.exclude_op_types = exclude_op_types 41 | 42 | def apply(self, model): 43 | graph = model.graph 44 | node_ind = 0 45 | graph_modified = False 46 | execution_context = model.make_empty_exec_context() 47 | for n in graph.node: 48 | node_ind += 1 49 | node_inp_inits = list(map(lambda x: model.get_initializer(x), n.input)) 50 | node_inp_dyn = list(filter(lambda x: x is None, node_inp_inits)) 51 | node_out = n.output[0] 52 | is_all_constant_inputs = len(node_inp_dyn) == 0 53 | ishape = model.get_tensor_shape(n.input[0]) 54 | is_const_shape = (n.op_type == "Shape") and (ishape is not None) 55 | exclude = n.op_type in self.exclude_op_types 56 | if (is_all_constant_inputs or is_const_shape) and not exclude: 57 | # this node has no dynamic inputs, only constant ones -- so we can 58 | # do constant folding. 59 | oxe.execute_node(n, execution_context, graph) 60 | # use the execution result as an initializer 61 | model.set_initializer(node_out, execution_context[node_out]) 62 | # remove old node 63 | graph.node.remove(n) 64 | graph_modified = True 65 | if graph_modified: 66 | model = model.transform(InferShapes()) 67 | return (model, graph_modified) 68 | -------------------------------------------------------------------------------- /src/finn/transformation/infer_shapes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import onnx.shape_inference as si 30 | 31 | import finn.custom_op.registry as registry 32 | from finn.core.modelwrapper import ModelWrapper 33 | from finn.transformation.base import Transformation 34 | from finn.util.basic import is_finn_op 35 | 36 | 37 | def _make_shape_compatible_op(node, model): 38 | """Return a shape-compatible non-FINN op for a given FINN op. Used for 39 | shape inference with custom ops.""" 40 | assert is_finn_op(node.domain), "Node domain is not set to finn.*" 41 | op_type = node.op_type 42 | try: 43 | # lookup op_type in registry of CustomOps 44 | inst = registry.getCustomOp(node) 45 | return inst.make_shape_compatible_op(model) 46 | except KeyError: 47 | # exception if op_type is not supported 48 | raise Exception("Custom op_type %s is currently not supported." % op_type) 49 | 50 | 51 | def _hide_finn_ops(model): 52 | """Replace any FINN ops by shape-compatible ones, and return a dict that 53 | can be used to map the string representations of the new (shape-compatible) 54 | ops back to the old ops.""" 55 | hidden_ops = {} 56 | node_ind = 0 57 | for node in model.graph.node: 58 | node_ind += 1 59 | if is_finn_op(node.domain): 60 | new_node = _make_shape_compatible_op(node, model) 61 | hidden_ops[str(new_node)] = node 62 | model.graph.node.insert(node_ind, new_node) 63 | model.graph.node.remove(node) 64 | return hidden_ops 65 | 66 | 67 | def _restore_finn_ops(model, hidden_ops): 68 | """Replace any shape-compatible ops with the FINN ops that originally 69 | generated them.""" 70 | node_ind = 0 71 | for node in model.graph.node: 72 | node_ind += 1 73 | try: 74 | old_node = hidden_ops[str(node)] 75 | model.graph.node.insert(node_ind, old_node) 76 | model.graph.node.remove(node) 77 | except KeyError: 78 | pass 79 | 80 | 81 | class InferShapes(Transformation): 82 | """Ensure every tensor in the model has a specified shape (ValueInfo).""" 83 | 84 | def apply(self, model): 85 | # hide your riches! 86 | hidden_ops = _hide_finn_ops(model) 87 | # call regular ONNX shape inference 88 | model = ModelWrapper(si.infer_shapes(model.model)) 89 | # bring back hidden ops 90 | _restore_finn_ops(model, hidden_ops) 91 | return (model, False) 92 | -------------------------------------------------------------------------------- /src/finn/transformation/insert_topk.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | from onnx import TensorProto 31 | from onnx import helper as oh 32 | 33 | from finn.core.datatype import DataType 34 | from finn.transformation.base import Transformation 35 | 36 | 37 | class InsertTopK(Transformation): 38 | """Add TopK node at the network output and replace the graph output with 39 | the TopK indices.""" 40 | 41 | def __init__(self, k=5, axis=-1, largest=1, sorted=1): 42 | super().__init__() 43 | self.k = k 44 | self.axis = axis 45 | self.largest = largest 46 | self.sorted = sorted 47 | 48 | def apply(self, model): 49 | # get name of output tensor 50 | graph_out_name = model.graph.output[0].name 51 | # find final node 52 | final_node = model.find_producer(graph_out_name) 53 | # if a top-select op is already present, do nothing 54 | if final_node.op_type == "TopK": 55 | return (model, False) 56 | else: 57 | out_shape = model.get_tensor_shape(graph_out_name) 58 | out_dtype = model.get_tensor_datatype(graph_out_name) 59 | # adjust shape 60 | out_shape[self.axis] = self.k 61 | # make new buffer 62 | k_tensor = np.array([self.k]).astype(np.int64) 63 | k_value = oh.make_tensor_value_info( 64 | model.make_new_valueinfo_name(), TensorProto.INT64, [1] 65 | ) 66 | topk_values = oh.make_tensor_value_info( 67 | model.make_new_valueinfo_name(), TensorProto.FLOAT, out_shape 68 | ) 69 | topk_indices = oh.make_tensor_value_info( 70 | model.make_new_valueinfo_name(), TensorProto.INT64, out_shape 71 | ) 72 | model.graph.value_info.append(k_value) 73 | model.set_tensor_datatype(k_value.name, out_dtype) # TODO set to int64 74 | model.graph.value_info.append(topk_values) 75 | model.set_tensor_datatype(topk_values.name, out_dtype) 76 | # create and append topk node 77 | model.set_initializer(k_value.name, k_tensor) 78 | topk_node = oh.make_node( 79 | "TopK", 80 | inputs=[graph_out_name, k_value.name], 81 | outputs=[topk_values.name, topk_indices.name], 82 | axis=self.axis, 83 | largest=self.largest, 84 | sorted=self.sorted, 85 | ) 86 | model.graph.node.append(topk_node) 87 | # replace the existing output definition with topk indices 88 | model.graph.output.insert(0, topk_indices) 89 | model.graph.output.pop(1) 90 | # set quantization annotation for indices 91 | # minimal output dtype for TopK indices dependens on num. classes 92 | # assuming UINT32 is large enough for now (FINN has currently no 93 | # DataType["INT64"]) 94 | model.set_tensor_datatype(topk_indices.name, DataType["UINT32"]) 95 | return (model, True) 96 | -------------------------------------------------------------------------------- /src/finn/transformation/make_input_chanlast.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from onnx import helper as oh 30 | 31 | import finn.core.data_layout as data_layout 32 | from finn.transformation.base import Transformation 33 | 34 | 35 | class MakeInputChannelsLast(Transformation): 36 | """For networks with an input using the NCx data layout, add a transpose node 37 | at the beginning and mark the input as using NxC (channels-last).""" 38 | 39 | def __init__(self): 40 | super().__init__() 41 | 42 | def apply(self, model): 43 | graph_in_name = model.graph.input[0].name 44 | graph_new_in_name = graph_in_name + "_transposed" 45 | orig_ishape = model.get_tensor_shape(graph_in_name) 46 | ndim = len(orig_ishape) 47 | if ndim == 2: 48 | # assume NC layout, no action needed 49 | return (model, False) 50 | elif ndim > 2: 51 | orig_layout = model.get_tensor_layout(graph_in_name) 52 | if orig_layout == data_layout.get_channels_last_layout_for_ndims(ndim): 53 | # already marked as channels-last, no action needed 54 | return (model, False) 55 | else: 56 | # determine channels-last shape and required permutation to 57 | # go from channels-last to previous format 58 | new_perm = list(range(ndim)) 59 | new_perm.remove(ndim - 1) 60 | new_perm.insert(1, ndim - 1) 61 | new_ishape = list(orig_ishape) 62 | new_ishape.remove(orig_ishape[1]) 63 | new_ishape.append(orig_ishape[1]) 64 | # create and insert transpose node 65 | t_trans_node = oh.make_node( 66 | "Transpose", [graph_in_name], [graph_new_in_name], perm=new_perm 67 | ) 68 | model.graph.node.insert(0, t_trans_node) 69 | # rewire all consumers of original input to transpose's output 70 | consumers = model.find_consumers(graph_in_name) 71 | for cons in consumers: 72 | if cons == t_trans_node: 73 | continue 74 | for i, ci in enumerate(cons.input): 75 | if ci == graph_in_name: 76 | cons.input[i] = graph_new_in_name 77 | # set tensor shapes and layouts 78 | model.set_tensor_shape(graph_in_name, new_ishape) 79 | model.set_tensor_shape(graph_new_in_name, orig_ishape) 80 | model.set_tensor_layout( 81 | graph_in_name, data_layout.get_channels_last_layout_for_ndims(ndim) 82 | ) 83 | model.set_tensor_layout( 84 | graph_new_in_name, 85 | data_layout.get_channels_first_layout_for_ndims(ndim), 86 | ) 87 | # single iteration is enough so return model_was_changed=False 88 | return (model, False) 89 | -------------------------------------------------------------------------------- /src/finn/transformation/remove.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | import numpy as np 31 | 32 | from finn.transformation.base import Transformation 33 | from finn.transformation.infer_shapes import InferShapes 34 | from finn.util.basic import get_by_name 35 | 36 | 37 | def remove_node_and_rewire(model, node): 38 | producer = model.find_producer(node.input[0]) 39 | if producer is not None: 40 | # wire output tensor to 41 | # output of producer node 42 | producer.output[0] = node.output[0] 43 | else: 44 | # node is first in graph 45 | successors = model.find_direct_successors(node) 46 | assert successors is not None, "Whole graph is one node." 47 | for succ in successors: 48 | for i, s_inp in enumerate(succ.input): 49 | if s_inp == node.output[0]: 50 | # rewire successor's input directly to graph input 51 | succ.input[i] = node.input[0] 52 | # remove node 53 | model.graph.node.remove(node) 54 | 55 | 56 | class RemoveIdentityOps(Transformation): 57 | """Remove identity ops like Add/Sub with zero or Mul/Div with one. A tolerance 58 | value (defaults to 1e-05) can be specified during init for the comparison 59 | to zero/one.""" 60 | 61 | def __init__(self, atol=1e-05): 62 | super().__init__() 63 | self.atol = atol 64 | 65 | def apply(self, model): 66 | graph = model.graph 67 | node_ind = 0 68 | graph_modified = False 69 | for n in graph.node: 70 | node_ind += 1 71 | if ( 72 | n.op_type in ["Add", "Sub"] 73 | and not model.is_fork_node(n) 74 | and not model.is_join_node(n) 75 | ): 76 | A = model.get_initializer(n.input[1]) 77 | if ( 78 | A is not None 79 | and np.isclose(A, np.zeros_like(A), atol=self.atol).all() 80 | ): 81 | remove_node_and_rewire(model, n) 82 | graph_modified = True 83 | break 84 | 85 | elif ( 86 | n.op_type in ["Mul", "Div"] 87 | and not model.is_fork_node(n) 88 | and not model.is_join_node(n) 89 | ): 90 | A = model.get_initializer(n.input[1]) 91 | if ( 92 | A is not None 93 | and np.isclose(A, np.ones_like(A), atol=self.atol).all() 94 | ): 95 | remove_node_and_rewire(model, n) 96 | graph_modified = True 97 | break 98 | elif ( 99 | n.op_type == "Pad" 100 | and not model.is_fork_node(n) 101 | and not model.is_join_node(n) 102 | ): 103 | pads = get_by_name(n.attribute, "pads") 104 | pads = np.asarray(pads.ints, dtype=np.int64) 105 | if (pads == 0).all(): 106 | remove_node_and_rewire(model, n) 107 | graph_modified = True 108 | break 109 | model = model.transform(InferShapes()) 110 | return (model, graph_modified) 111 | -------------------------------------------------------------------------------- /src/finn/util/config.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import json 30 | 31 | from finn.custom_op.registry import getCustomOp 32 | 33 | 34 | def extract_model_config_to_json(model, json_filename, attr_names_to_extract): 35 | """Create a json file with layer name -> attribute mappings extracted from the 36 | model. The created json file can be later applied on a model with 37 | finn.transform.general.ApplyConfig.""" 38 | 39 | cfg = dict() 40 | cfg["Defaults"] = dict() 41 | for n in model.graph.node: 42 | oi = getCustomOp(n) 43 | layer_dict = dict() 44 | for attr in attr_names_to_extract: 45 | try: 46 | layer_dict[attr] = oi.get_nodeattr(attr) 47 | except AttributeError: 48 | pass 49 | if len(layer_dict) > 0: 50 | cfg[n.name] = layer_dict 51 | with open(json_filename, "w") as f: 52 | json.dump(cfg, f, indent=2) 53 | -------------------------------------------------------------------------------- /src/finn/util/fpgadataflow.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from finn.util.basic import get_by_name, is_finn_op 30 | 31 | 32 | def is_fpgadataflow_node(node): 33 | """Returns True if given node is fpgadataflow node. Otherwise False.""" 34 | is_node = False 35 | if node is not None: 36 | if is_finn_op(node.domain): 37 | n_backend = get_by_name(node.attribute, "backend") 38 | if n_backend is not None: 39 | backend_value = n_backend.s.decode("UTF-8") 40 | if backend_value == "fpgadataflow": 41 | is_node = True 42 | 43 | return is_node 44 | -------------------------------------------------------------------------------- /src/finn/util/hls.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | import os 31 | import subprocess 32 | 33 | from finn.util.basic import which 34 | 35 | 36 | class CallHLS: 37 | """Call either vivado_hls or vitis_hls to run HLS build tcl scripts.""" 38 | 39 | def __init__(self, backend="vivado_hls"): 40 | self.tcl_script = "" 41 | self.ipgen_path = "" 42 | self.code_gen_dir = "" 43 | self.ipgen_script = "" 44 | assert backend in [ 45 | "vivado_hls", 46 | "vitis_hls", 47 | ], "Unrecognized backend for CallHLS" 48 | self.backend = backend 49 | 50 | def append_tcl(self, tcl_script): 51 | """Sets the tcl script to be executed.""" 52 | self.tcl_script = tcl_script 53 | 54 | def set_ipgen_path(self, path): 55 | """Sets member variable ipgen_path to given path.""" 56 | self.ipgen_path = path 57 | 58 | def build(self, code_gen_dir): 59 | """Builds the bash script with given parameters and saves it in given folder. 60 | To guarantee the generation in the correct folder the bash script contains a 61 | cd command.""" 62 | assert which(self.backend) is not None, "%s not found in PATH" % self.backend 63 | self.code_gen_dir = code_gen_dir 64 | self.ipgen_script = str(self.code_gen_dir) + "/ipgen.sh" 65 | working_dir = os.environ["PWD"] 66 | f = open(self.ipgen_script, "w") 67 | f.write("#!/bin/bash \n") 68 | f.write("cd {}\n".format(code_gen_dir)) 69 | f.write("%s %s\n" % (self.backend, self.tcl_script)) 70 | f.write("cd {}\n".format(working_dir)) 71 | f.close() 72 | bash_command = ["bash", self.ipgen_script] 73 | process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) 74 | process_compile.communicate() 75 | -------------------------------------------------------------------------------- /src/finn/util/inference_cost.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import clize 30 | import json 31 | 32 | import finn.analysis.inference_cost as infca 33 | from finn.core.datatype import DataType 34 | from finn.core.modelwrapper import ModelWrapper 35 | from finn.transformation.fold_constants import FoldConstants 36 | from finn.transformation.general import ( 37 | GiveReadableTensorNames, 38 | GiveUniqueNodeNames, 39 | GiveUniqueParameterTensors, 40 | RemoveStaticGraphInputs, 41 | RemoveUnusedTensors, 42 | ) 43 | from finn.transformation.infer_datatypes import InferDataTypes 44 | from finn.transformation.infer_shapes import InferShapes 45 | 46 | 47 | def compute_bops(inf_cost_dict): 48 | total_bops = 0.0 49 | for (k, v) in inf_cost_dict.items(): 50 | if k.startswith("op_mac"): 51 | comps = k.split("_") 52 | dt1 = DataType[comps[2]] 53 | dt2 = DataType[comps[3]] 54 | total_bops += dt1.bitwidth() * dt2.bitwidth() * v 55 | return total_bops 56 | 57 | 58 | def compute_mem_bits(inf_cost_dict, filter_string="mem_w"): 59 | total_mem_bits = 0.0 60 | for (k, v) in inf_cost_dict.items(): 61 | if k.startswith(filter_string): 62 | comps = k.split("_") 63 | dt = DataType[comps[2]] 64 | total_mem_bits += dt.bitwidth() * v 65 | return total_mem_bits 66 | 67 | 68 | def inference_cost( 69 | model_filename, 70 | *, 71 | output_json=None, 72 | output_onnx=None, 73 | preprocess=True, 74 | discount_sparsity=True 75 | ): 76 | """Print the inference cost estimate metric for given ONNX model. 77 | Supports the Quant op for weight/activation quantization. 78 | 79 | :param model_filename: Filename for ONNX model 80 | :param output_json: Optional JSON filename to save the inference cost dict 81 | :param output_onnx: Optional ONNX filename to save the final model after any 82 | preprocessing 83 | :param preprocess: If set, run preprocessing steps such as shape inference, 84 | datatype inference and constant folding. Strongly recommended. 85 | :param discount_sparsity: If set, will discount op cost of MAC ops with a 86 | constant zero weight, and the mem cost of constant zero weights. 87 | """ 88 | print("Inference cost for " + model_filename) 89 | model = ModelWrapper(model_filename) 90 | if preprocess: 91 | qnt_nodes = model.get_nodes_by_op_type("Quant") 92 | for qnt_node in qnt_nodes: 93 | qnt_node.domain = "finn.custom_op.general" 94 | model = model.transform(InferShapes()) 95 | model = model.transform(GiveUniqueParameterTensors()) 96 | model = model.transform(InferDataTypes()) 97 | model = model.transform(FoldConstants()) 98 | model = model.transform(RemoveUnusedTensors()) 99 | model = model.transform(RemoveStaticGraphInputs()) 100 | model = model.transform(InferDataTypes()) 101 | model = model.transform(GiveUniqueNodeNames()) 102 | model = model.transform(GiveReadableTensorNames()) 103 | if output_onnx is not None: 104 | model.save(output_onnx) 105 | ret = model.analysis(lambda x: infca.inference_cost(x, discount_sparsity)) 106 | bops = compute_bops(ret) 107 | mem_w_bits = compute_mem_bits(ret, "mem_w") 108 | mem_o_bits = compute_mem_bits(ret, "mem_o") 109 | ret["total_bops"] = bops 110 | ret["total_mem_w_bits"] = mem_w_bits 111 | ret["total_mem_o_bits"] = mem_o_bits 112 | 113 | if "unsupported" in ret: 114 | ret["unsupported"] = str(ret["unsupported"]) 115 | print(json.dumps(ret, sort_keys=True, indent=2)) 116 | 117 | if output_json is not None: 118 | with open(output_json, "w") as f: 119 | json.dump(ret, f, sort_keys=True, indent=2) 120 | 121 | 122 | def main(): 123 | clize.run(inference_cost) 124 | 125 | 126 | if __name__ == "__main__": 127 | main() 128 | -------------------------------------------------------------------------------- /src/finn/util/onnx.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx 31 | 32 | import finn.core.data_layout as DataLayout 33 | 34 | 35 | def valueinfo_to_tensor(vi): 36 | """Creates an all-zeroes numpy tensor from a ValueInfoProto.""" 37 | 38 | dims = [x.dim_value for x in vi.type.tensor_type.shape.dim] 39 | return np.zeros( 40 | dims, dtype=onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[vi.type.tensor_type.elem_type] 41 | ) 42 | 43 | 44 | def nchw_to_nhwc(t, model, idx, reverse=False): 45 | """Converts between NCHW <-> NHWC layouts for tensor t by inserting a transpose. 46 | If reverse=False, t is assumed NCHW and we insert transpose to convert NCHW -> NHWC 47 | If reverse=True, t is assumed NHWC and we insert transpose to convert NHWC -> NCHW. 48 | """ 49 | graph = model.graph 50 | # create new NHWC tensor 51 | t_shape = model.get_tensor_shape(t) 52 | bs = t_shape[0] 53 | ch = t_shape[1] 54 | height = t_shape[2] 55 | width = t_shape[3] 56 | t_trans = onnx.helper.make_tensor_value_info( 57 | model.make_new_valueinfo_name(), 58 | onnx.TensorProto.FLOAT, 59 | (bs, height, width, ch), # NHWC 60 | ) 61 | graph.value_info.append(t_trans) 62 | dt = model.get_tensor_datatype(t) 63 | t_trans = t_trans.name 64 | model.set_tensor_datatype(t_trans, dt) 65 | model.set_tensor_layout(t_trans, DataLayout.NHWC) 66 | # NCHW <-> NHWC transpose 67 | if reverse: 68 | t_trans_node = onnx.helper.make_node( 69 | "Transpose", [t_trans], [t], perm=[0, 3, 1, 2] 70 | ) 71 | else: 72 | t_trans_node = onnx.helper.make_node( 73 | "Transpose", [t], [t_trans], perm=[0, 2, 3, 1] 74 | ) 75 | graph.node.insert(idx, t_trans_node) 76 | return t_trans 77 | -------------------------------------------------------------------------------- /src/finn/util/vivado.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import os 30 | 31 | from finn.util.basic import launch_process_helper, which 32 | 33 | 34 | def out_of_context_synth( 35 | verilog_dir, 36 | top_name, 37 | fpga_part="xczu3eg-sbva484-1-e", 38 | clk_name="ap_clk_0", 39 | clk_period_ns=5.0, 40 | ): 41 | "Run out-of-context Vivado synthesis, return resources and slack." 42 | 43 | # ensure that the OH_MY_XILINX envvar is set 44 | if "OHMYXILINX" not in os.environ: 45 | raise Exception("The environment variable OHMYXILINX is not defined.") 46 | # ensure that vivado is in PATH: source $VIVADO_PATH/settings64.sh 47 | if which("vivado") is None: 48 | raise Exception("vivado is not in PATH, ensure settings64.sh is sourced.") 49 | omx_path = os.environ["OHMYXILINX"] 50 | script = "vivadocompile.sh" 51 | # vivadocompile.sh 52 | call_omx = "zsh %s/%s %s %s %s %f" % ( 53 | omx_path, 54 | script, 55 | top_name, 56 | clk_name, 57 | fpga_part, 58 | float(clk_period_ns), 59 | ) 60 | call_omx = call_omx.split() 61 | launch_process_helper(call_omx, proc_env=os.environ.copy(), cwd=verilog_dir) 62 | 63 | vivado_proj_folder = "%s/results_%s" % (verilog_dir, top_name) 64 | res_counts_path = vivado_proj_folder + "/res.txt" 65 | 66 | with open(res_counts_path, "r") as myfile: 67 | res_data = myfile.read().split("\n") 68 | ret = {} 69 | ret["vivado_proj_folder"] = vivado_proj_folder 70 | for res_line in res_data: 71 | res_fields = res_line.split("=") 72 | print(res_fields) 73 | try: 74 | ret[res_fields[0]] = float(res_fields[1]) 75 | except ValueError: 76 | ret[res_fields[0]] = 0 77 | except IndexError: 78 | ret[res_fields[0]] = 0 79 | if ret["WNS"] == 0: 80 | ret["fmax_mhz"] = 0 81 | else: 82 | ret["fmax_mhz"] = 1000.0 / (clk_period_ns - ret["WNS"]) 83 | return ret 84 | -------------------------------------------------------------------------------- /tests/analysis/test_is_linear.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import onnx.helper as oh 30 | from onnx import TensorProto 31 | 32 | import finn.analysis.topology as ta 33 | from finn.core.modelwrapper import ModelWrapper 34 | from finn.transformation.infer_shapes import InferShapes 35 | 36 | 37 | def test_is_linear_linear(): 38 | top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, [2]) 39 | add_param = oh.make_tensor_value_info("add_param", TensorProto.FLOAT, [2]) 40 | mul_param = oh.make_tensor_value_info("mul_param", TensorProto.FLOAT, [2]) 41 | top_out = oh.make_tensor_value_info("top_out", TensorProto.FLOAT, [2]) 42 | modelproto = oh.make_model( 43 | oh.make_graph( 44 | name="test", 45 | inputs=[top_in], 46 | outputs=[top_out], 47 | value_info=[add_param, mul_param], 48 | nodes=[ 49 | oh.make_node("Add", ["top_in", "add_param"], ["middle"]), 50 | oh.make_node("Mul", ["middle", "mul_param"], ["top_out"]), 51 | ], 52 | ) 53 | ) 54 | model = ModelWrapper(modelproto) 55 | model = model.transform(InferShapes()) 56 | ret = model.analysis(ta.is_linear) 57 | assert ret["is_linear"] is True 58 | 59 | 60 | def test_is_linear_forked_node_output(): 61 | top_in = oh.make_tensor_value_info("top_in", TensorProto.FLOAT, [2]) 62 | add_param = oh.make_tensor_value_info("add_param", TensorProto.FLOAT, [2]) 63 | mul0_param = oh.make_tensor_value_info("mul0_param", TensorProto.FLOAT, [2]) 64 | mul1_param = oh.make_tensor_value_info("mul1_param", TensorProto.FLOAT, [2]) 65 | mul0_res = oh.make_tensor_value_info("mul0_res", TensorProto.FLOAT, [2]) 66 | mul1_res = oh.make_tensor_value_info("mul1_res", TensorProto.FLOAT, [2]) 67 | top_out = oh.make_tensor_value_info("top_out", TensorProto.FLOAT, [2]) 68 | modelproto = oh.make_model( 69 | oh.make_graph( 70 | name="test", 71 | inputs=[top_in], 72 | outputs=[top_out], 73 | value_info=[add_param, mul0_param, mul1_param, mul0_res, mul1_res], 74 | nodes=[ 75 | oh.make_node("Add", ["top_in", "add_param"], ["middle"]), 76 | oh.make_node("Mul", ["middle", "mul0_param"], ["mul0_res"]), 77 | oh.make_node("Mul", ["middle", "mul1_param"], ["mul1_res"]), 78 | oh.make_node("Add", ["mul0_res", "mul1_res"], ["top_out"]), 79 | ], 80 | ) 81 | ) 82 | model = ModelWrapper(modelproto) 83 | model = model.transform(InferShapes()) 84 | ret = model.analysis(ta.is_linear) 85 | assert ret["is_linear"] is False 86 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Dummy conftest.py for finn_base. 4 | 5 | If you don't know what this is for, just leave it empty. 6 | Read more about conftest.py under: 7 | https://pytest.org/latest/plugins.html 8 | """ 9 | 10 | # import pytest 11 | -------------------------------------------------------------------------------- /tests/core/test_basic_onnx_exec.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx 31 | import onnx.numpy_helper as np_helper 32 | from pkgutil import get_data 33 | 34 | import finn.core.onnx_exec as oxe 35 | from finn.core.datatype import DataType 36 | from finn.core.modelwrapper import ModelWrapper 37 | from finn.transformation.infer_shapes import InferShapes 38 | from finn.util.basic import gen_finn_dt_tensor 39 | 40 | 41 | def test_mnist_onnx_download_extract_run(): 42 | # load the onnx model 43 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 44 | model = ModelWrapper(raw_m) 45 | model = model.transform(InferShapes()) 46 | # load one of the test vectors 47 | raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") 48 | raw_o = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/output_0.pb") 49 | input_tensor = onnx.load_tensor_from_string(raw_i) 50 | output_tensor = onnx.load_tensor_from_string(raw_o) 51 | # run using FINN-based execution (full graph) 52 | input_dict = {"Input3": np_helper.to_array(input_tensor)} 53 | output_dict = oxe.execute_onnx(model, input_dict, return_full_exec_context=True) 54 | assert np.isclose( 55 | np_helper.to_array(output_tensor), output_dict["Plus214_Output_0"], atol=1e-3 56 | ).all() 57 | # test subgraph execution 58 | start_node = model.graph.node[1] 59 | end_node = model.graph.node[3] 60 | subgraph_i_dict = {start_node.input[0]: output_dict[start_node.input[0]]} 61 | subgraph_o_dict = oxe.execute_onnx( 62 | model, 63 | subgraph_i_dict, 64 | return_full_exec_context=True, 65 | start_node=start_node, 66 | end_node=end_node, 67 | ) 68 | assert np.isclose( 69 | subgraph_o_dict[end_node.output[0]], output_dict[end_node.output[0]], atol=1e-3 70 | ).all() 71 | 72 | 73 | def test_onnx_exec_internal_rounding(): 74 | inp0 = onnx.helper.make_tensor_value_info("inp0", onnx.TensorProto.FLOAT, [2, 2]) 75 | inp1 = onnx.helper.make_tensor_value_info("inp1", onnx.TensorProto.FLOAT, [1]) 76 | outp = onnx.helper.make_tensor_value_info("outp", onnx.TensorProto.FLOAT, [2, 2]) 77 | mul_node = onnx.helper.make_node("Mul", inputs=["inp0", "inp1"], outputs=["outp"]) 78 | graph = onnx.helper.make_graph( 79 | nodes=[mul_node], name="mul_graph", inputs=[inp0, inp1], outputs=[outp] 80 | ) 81 | 82 | model = onnx.helper.make_model(graph, producer_name="mul-model") 83 | model = ModelWrapper(model) 84 | idt = DataType["INT2"] 85 | model.set_tensor_datatype("inp0", idt) 86 | model.set_tensor_datatype("inp1", idt) 87 | model.transform(InferShapes()) 88 | 89 | mul_value = np.asarray([-1], dtype=np.float32) 90 | inp_int = gen_finn_dt_tensor(idt, [2, 2]) 91 | scale = np.random.uniform(low=0, high=1, size=(2, 2)).astype(np.float32) 92 | inp_rounded = (inp_int * scale) / (scale + 1e-7) 93 | input_dict = {"inp0": inp_rounded, "inp1": mul_value} 94 | output_dict = oxe.execute_onnx(model, input_dict) 95 | produced = output_dict["outp"] 96 | expected = np.multiply(inp_int, mul_value) 97 | assert (produced == expected).all() 98 | -------------------------------------------------------------------------------- /tests/core/test_datatypes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | 31 | from finn.core.datatype import DataType 32 | 33 | 34 | def test_datatypes(): 35 | assert DataType["BIPOLAR"].allowed(-1) 36 | assert DataType["BIPOLAR"].allowed(0) is False 37 | assert DataType["BINARY"].allowed(-1) is False 38 | assert DataType["BINARY"].allowed(1) 39 | assert DataType["TERNARY"].allowed(2) is False 40 | assert DataType["TERNARY"].allowed(-1) 41 | assert DataType["UINT2"].allowed(2) 42 | assert DataType["UINT2"].allowed(10) is False 43 | assert DataType["UINT3"].allowed(5) 44 | assert DataType["UINT3"].allowed(-7) is False 45 | assert DataType["UINT4"].allowed(15) 46 | assert DataType["UINT4"].allowed(150) is False 47 | assert DataType["UINT8"].allowed(150) 48 | assert DataType["UINT8"].allowed(777) is False 49 | assert DataType["UINT8"].to_numpy_dt() == np.uint8 50 | assert DataType["UINT16"].allowed(14500) 51 | assert DataType["UINT16"].to_numpy_dt() == np.uint16 52 | assert DataType["UINT16"].allowed(-1) is False 53 | assert DataType["UINT32"].allowed(2**10) 54 | assert DataType["UINT32"].allowed(-1) is False 55 | assert DataType["UINT32"].to_numpy_dt() == np.uint32 56 | assert DataType["INT2"].allowed(-1) 57 | assert DataType["INT2"].allowed(-10) is False 58 | assert DataType["INT3"].allowed(5) is False 59 | assert DataType["INT3"].allowed(-2) 60 | assert DataType["INT4"].allowed(15) is False 61 | assert DataType["INT4"].allowed(-5) 62 | assert DataType["INT8"].allowed(150) is False 63 | assert DataType["INT8"].allowed(-127) 64 | assert DataType["INT8"].to_numpy_dt() == np.int8 65 | assert DataType["INT16"].allowed(-1.04) is False 66 | assert DataType["INT16"].allowed(-7777) 67 | assert DataType["INT16"].to_numpy_dt() == np.int16 68 | assert DataType["INT32"].allowed(7.77) is False 69 | assert DataType["INT32"].allowed(-5) 70 | assert DataType["INT32"].allowed(5) 71 | assert DataType["INT32"].to_numpy_dt() == np.int32 72 | assert DataType["BINARY"].signed() is False 73 | assert DataType["FLOAT32"].signed() 74 | assert DataType["BIPOLAR"].signed() 75 | assert DataType["TERNARY"].signed() 76 | assert str(DataType["TERNARY"]) == "TERNARY" 77 | 78 | 79 | def test_datatypes_fixedpoint(): 80 | assert DataType["FIXED<4,2>"].allowed(0.5) 81 | assert DataType["FIXED<4,2>"].to_numpy_dt() == np.float32 82 | assert DataType["FIXED<4,2>"].signed() 83 | assert DataType["FIXED<4,2>"].scale_factor() == 0.25 84 | assert DataType["FIXED<4,2>"].min() == -2.0 85 | assert DataType["FIXED<4,2>"].max() == 1.75 86 | assert DataType["FIXED<4,2>"].allowed(1.5) 87 | assert DataType["FIXED<4,2>"].allowed(-1.5) 88 | assert DataType["FIXED<4,2>"].allowed(1.8) is False 89 | assert DataType["FIXED<4,2>"].is_integer() is False 90 | assert DataType["FIXED<4,2>"].is_fixed_point() is True 91 | assert str(DataType["FIXED<4,2"]) == "FIXED<4,2>" 92 | 93 | 94 | def test_smallest_possible(): 95 | assert DataType.get_smallest_possible(1) == DataType["BINARY"] 96 | assert DataType.get_smallest_possible(1.1) == DataType["FLOAT32"] 97 | assert DataType.get_smallest_possible(-1) == DataType["BIPOLAR"] 98 | assert DataType.get_smallest_possible(-3) == DataType["INT3"] 99 | assert DataType.get_smallest_possible(-3.2) == DataType["FLOAT32"] 100 | -------------------------------------------------------------------------------- /tests/custom_op/test_xnorpopcountmatmul.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx.helper as helper 31 | from onnx import TensorProto 32 | 33 | import finn.core.onnx_exec as oxe 34 | from finn.core.datatype import DataType 35 | from finn.core.modelwrapper import ModelWrapper 36 | from finn.transformation.infer_datatypes import InferDataTypes 37 | from finn.transformation.infer_shapes import InferShapes 38 | 39 | export_onnx_path = "test_xnorpopcountmatmul.onnx" 40 | 41 | 42 | def test_xnorpopcountmatmul(): 43 | M = 1 44 | K = 3 45 | N = 3 46 | x = helper.make_tensor_value_info("x", TensorProto.FLOAT, [M, K]) 47 | W = helper.make_tensor_value_info("W", TensorProto.FLOAT, [K, N]) 48 | out = helper.make_tensor_value_info("out", TensorProto.FLOAT, ["x", "y"]) 49 | node_def = helper.make_node( 50 | "XnorPopcountMatMul", ["x", "W"], ["out"], domain="finn.custom_op.general" 51 | ) 52 | modelproto = helper.make_model( 53 | helper.make_graph([node_def], "test_model", [x], [out], value_info=[W]) 54 | ) 55 | model = ModelWrapper(modelproto) 56 | model.set_tensor_datatype("x", DataType["BINARY"]) 57 | model.set_tensor_datatype("W", DataType["BINARY"]) 58 | W_data = np.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32) 59 | model.set_initializer("W", W_data) 60 | # test shape inference 61 | model = model.transform(InferShapes()) 62 | assert model.get_tensor_shape("out") == [M, N] 63 | # test datatype inference 64 | assert model.get_tensor_datatype("out") == DataType["FLOAT32"] 65 | model = model.transform(InferDataTypes()) 66 | assert model.get_tensor_datatype("out") == DataType["UINT32"] 67 | # test execution 68 | x_data = np.asarray([[1, 0, 0]], dtype=np.float32) 69 | inp_dict = {"x": x_data} 70 | out_dict = oxe.execute_onnx(model, inp_dict) 71 | Wb = 2 * W_data - 1 72 | xb = 2 * x_data - 1 73 | rb = np.matmul(xb, Wb) 74 | assert (2 * out_dict["out"] - K == rb).all() 75 | -------------------------------------------------------------------------------- /tests/transformation/test_change_datalayout.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | import pytest 29 | 30 | from onnx import TensorProto, helper 31 | 32 | import finn.core.data_layout as DataLayout 33 | import finn.core.onnx_exec as oxe 34 | from finn.core.datatype import DataType 35 | from finn.core.modelwrapper import ModelWrapper 36 | from finn.custom_op.general.maxpoolnhwc import compute_pool_output_dim 37 | from finn.transformation.change_datalayout import ChangeDataLayoutQuantAvgPool2d 38 | from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames 39 | from finn.transformation.infer_data_layouts import InferDataLayouts 40 | from finn.transformation.infer_datatypes import InferDataTypes 41 | from finn.transformation.infer_shapes import InferShapes 42 | from finn.util.basic import gen_finn_dt_tensor, get_by_name 43 | 44 | 45 | # stride 46 | @pytest.mark.parametrize("s", [1, 2]) 47 | # kernel 48 | @pytest.mark.parametrize("k", [3, 4]) 49 | # ibits 50 | @pytest.mark.parametrize("ibits", [4, 8]) 51 | # obits 52 | @pytest.mark.parametrize("obits", [2, 4]) 53 | # signed 54 | @pytest.mark.parametrize("signed", [False, True]) 55 | # channels 56 | @pytest.mark.parametrize("c", [2, 3]) 57 | # input dimension 58 | @pytest.mark.parametrize("idim", [6, 7]) 59 | def test_change_datalayout_quantavgpool(s, k, ibits, obits, signed, c, idim): 60 | 61 | n = 1 62 | odim = compute_pool_output_dim(idim, k, s) 63 | # determine input FINN datatype 64 | if signed is True: 65 | prefix = "INT" 66 | else: 67 | prefix = "UINT" 68 | dt_name = prefix + str(ibits) 69 | dtype = DataType[dt_name] 70 | 71 | inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, [n, c, idim, idim]) 72 | outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, [n, c, odim, odim]) 73 | 74 | node = helper.make_node( 75 | "QuantAvgPool2d", 76 | ["inp"], 77 | ["outp"], 78 | domain="finn.custom_op.general", 79 | stride=s, 80 | kernel=k, 81 | ibits=ibits, 82 | obits=obits, 83 | signed=signed, 84 | data_layout="NCHW", 85 | ) 86 | graph = helper.make_graph( 87 | nodes=[node], name="single-quantavgpool", inputs=[inp], outputs=[outp] 88 | ) 89 | 90 | model = helper.make_model(graph) 91 | model = ModelWrapper(model) 92 | model = model.transform(InferShapes()) 93 | model = model.transform(InferDataTypes()) 94 | model = model.transform(InferDataLayouts()) 95 | model = model.transform(GiveUniqueNodeNames()) 96 | model = model.transform(GiveReadableTensorNames()) 97 | model_transformed = model.transform(ChangeDataLayoutQuantAvgPool2d()) 98 | model_transformed = model_transformed.transform(InferShapes()) 99 | model_transformed = model_transformed.transform(InferDataTypes()) 100 | model_transformed = model_transformed.transform(InferDataLayouts()) 101 | model_transformed = model_transformed.transform(GiveUniqueNodeNames()) 102 | model_transformed = model_transformed.transform(GiveReadableTensorNames()) 103 | inp_values = gen_finn_dt_tensor(dtype, [n, c, idim, idim]) 104 | idict = {"inp": inp_values} 105 | assert oxe.compare_execution(model, model_transformed, idict) 106 | assert len(model.graph.node) + 2 == len(model_transformed.graph.node) 107 | assert model_transformed.graph.node[-1].op_type == "Transpose" 108 | assert model_transformed.graph.node[0].op_type == "Transpose" 109 | # check if QuantAvgPool2d node has datalayout set correctly 110 | node = model_transformed.graph.node[1] 111 | d_layout = get_by_name(node.attribute, "data_layout").s.decode("UTF-8") 112 | assert d_layout == "NHWC" 113 | assert model_transformed.get_tensor_layout(node.input[0]) == DataLayout.NHWC 114 | assert model_transformed.get_tensor_layout(node.output[0]) == DataLayout.NHWC 115 | -------------------------------------------------------------------------------- /tests/transformation/test_fold_constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | import onnx 31 | import onnx.numpy_helper as np_helper 32 | from pkgutil import get_data 33 | 34 | import finn.core.onnx_exec as oxe 35 | from finn.core.modelwrapper import ModelWrapper 36 | from finn.transformation.fold_constants import FoldConstants 37 | from finn.transformation.infer_shapes import InferShapes 38 | 39 | 40 | def test_const_folding(): 41 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 42 | model = ModelWrapper(raw_m) 43 | model = model.transform(InferShapes()) 44 | model = model.transform(FoldConstants()) 45 | raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") 46 | raw_o = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/output_0.pb") 47 | input_tensor = onnx.load_tensor_from_string(raw_i) 48 | output_tensor = onnx.load_tensor_from_string(raw_o) 49 | input_dict = {"Input3": np_helper.to_array(input_tensor)} 50 | output_dict = oxe.execute_onnx(model, input_dict) 51 | assert np.isclose( 52 | np_helper.to_array(output_tensor), output_dict["Plus214_Output_0"], atol=1e-3 53 | ).all() 54 | 55 | 56 | def test_const_folding_shapes(): 57 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 58 | model = ModelWrapper(raw_m) 59 | model = model.transform(InferShapes()) 60 | mm_node_w_in = model.get_nodes_by_op_type("MatMul")[0].input[1] 61 | assert model.find_producer(mm_node_w_in) is not None 62 | assert model.find_producer(mm_node_w_in).op_type == "Reshape" 63 | assert model.get_initializer(mm_node_w_in) is None 64 | model = model.transform(FoldConstants()) 65 | assert model.find_producer(mm_node_w_in) is None 66 | assert model.get_initializer(mm_node_w_in) is not None 67 | -------------------------------------------------------------------------------- /tests/transformation/test_generic_partitioning.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import pytest 30 | 31 | import numpy as np 32 | from onnx import TensorProto, helper 33 | 34 | import finn.core.onnx_exec as oxe 35 | from finn.core.modelwrapper import ModelWrapper 36 | from finn.custom_op.registry import getCustomOp 37 | from finn.transformation.create_generic_partitions import PartitionFromDict 38 | from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames 39 | from finn.transformation.infer_shapes import InferShapes 40 | 41 | 42 | # select example partitioning 43 | @pytest.mark.parametrize("p", [0, 1, 2, 3]) 44 | def test_generic_partitioning(p): 45 | partitionings = [ 46 | {0: range(0, 4)}, 47 | {0: [0], 1: [3]}, 48 | {0: [1, 2]}, 49 | {"first": [0, 1], "last": [2, 3]}, 50 | ] 51 | partitioning = partitionings[p] 52 | 53 | # set up model 54 | shape = [1, 10] 55 | inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, shape) 56 | a0 = helper.make_tensor_value_info("a0", TensorProto.FLOAT, []) 57 | a1 = helper.make_tensor_value_info("a1", TensorProto.FLOAT, []) 58 | a2 = helper.make_tensor_value_info("a2", TensorProto.FLOAT, []) 59 | outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, shape) 60 | 61 | mul_node = helper.make_node("Mul", ["inp", "a0"], ["mul_out"]) 62 | div_node = helper.make_node("Div", ["mul_out", "a1"], ["div_out"]) 63 | sub_node = helper.make_node("Sub", ["div_out", "a2"], ["sub_out"]) 64 | add_node = helper.make_node("Add", ["sub_out", "mul_out"], ["outp"]) 65 | 66 | graph = helper.make_graph( 67 | nodes=[mul_node, div_node, sub_node, add_node], 68 | name="model-graph", 69 | inputs=[inp], 70 | outputs=[outp], 71 | value_info=[a0, a1, a2], 72 | ) 73 | 74 | model = helper.make_model(graph, producer_name="model") 75 | model = ModelWrapper(model) 76 | # initialize model 77 | a0_value = np.random.uniform(low=0, high=1, size=(1)).astype(np.float32) 78 | model.set_initializer("a0", a0_value) 79 | a1_value = np.random.uniform(low=0.1, high=1, size=(1)).astype(np.float32) 80 | model.set_initializer("a1", a1_value) 81 | a2_value = np.random.uniform(low=0.1, high=1, size=(1)).astype(np.float32) 82 | model.set_initializer("a2", a2_value) 83 | 84 | model = model.transform(InferShapes()) 85 | model = model.transform(GiveUniqueNodeNames()) 86 | model = model.transform(GiveReadableTensorNames()) 87 | 88 | # apply partitioning 89 | model_parent = model.transform(PartitionFromDict(partitioning)) 90 | 91 | # random input data 92 | inp_values = np.random.random_sample(shape).astype(np.float32) 93 | idict = {model.graph.input[0].name: inp_values} 94 | 95 | # test transformed model 96 | assert oxe.compare_execution(model, model_parent, idict) 97 | 98 | # examine created partitions 99 | num_nodes_expected = len(model.graph.node) 100 | for p_node in model_parent.get_nodes_by_op_type("GenericPartition"): 101 | p_node = getCustomOp(p_node) 102 | p_model_filename = p_node.get_nodeattr("model") 103 | model_child = ModelWrapper(p_model_filename) 104 | num_nodes_expected -= len(model_child.graph.node) - 1 105 | 106 | # count number of partitions 107 | assert len(model_parent.get_nodes_by_op_type("GenericPartition")) == len( 108 | partitioning 109 | ) 110 | # count number of nodes 111 | assert len(model_parent.graph.node) == num_nodes_expected 112 | -------------------------------------------------------------------------------- /tests/transformation/test_infer_data_layouts.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from pkgutil import get_data 30 | 31 | import finn.core.data_layout as DataLayout 32 | from finn.core.modelwrapper import ModelWrapper 33 | from finn.transformation.fold_constants import FoldConstants 34 | from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames 35 | from finn.transformation.infer_data_layouts import InferDataLayouts 36 | from finn.transformation.infer_shapes import InferShapes 37 | from finn.transformation.lower_convs_to_matmul import LowerConvsToMatMul 38 | 39 | 40 | def test_infer_data_layouts(): 41 | 42 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 43 | model = ModelWrapper(raw_m) 44 | model = model.transform(InferShapes()) 45 | model = model.transform(FoldConstants()) 46 | model = model.transform(GiveUniqueNodeNames()) 47 | model = model.transform(GiveReadableTensorNames()) 48 | model = model.transform(InferDataLayouts()) 49 | 50 | assert model.get_tensor_layout("global_in") == DataLayout.NCHW 51 | assert model.get_tensor_layout("Conv_0_out0") == DataLayout.NCHW 52 | assert model.get_tensor_layout("MaxPool_0_out0") == DataLayout.NCHW 53 | assert model.get_tensor_layout("Reshape_0_out0") == DataLayout.NC 54 | assert model.get_tensor_layout("MatMul_0_out0") == DataLayout.NC 55 | assert model.get_tensor_layout("global_out") == DataLayout.NC 56 | 57 | model = model.transform(LowerConvsToMatMul()) 58 | model = model.transform(GiveUniqueNodeNames()) 59 | model = model.transform(GiveReadableTensorNames()) 60 | model = model.transform(InferDataLayouts()) 61 | 62 | assert model.get_tensor_layout("global_in") == DataLayout.NCHW 63 | assert model.get_tensor_layout("Transpose_0_out0") == DataLayout.NHWC 64 | assert model.get_tensor_layout("Im2Col_0_out0") == DataLayout.NHWC 65 | assert model.get_tensor_layout("MatMul_0_out0") == DataLayout.NHWC 66 | assert model.get_tensor_layout("MaxPool_0_out0") == DataLayout.NCHW 67 | assert model.get_tensor_layout("Reshape_0_out0") == DataLayout.NC 68 | assert model.get_tensor_layout("MatMul_2_out0") == DataLayout.NC 69 | assert model.get_tensor_layout("global_out") == DataLayout.NC 70 | -------------------------------------------------------------------------------- /tests/transformation/test_infer_datatypes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from pkgutil import get_data 30 | 31 | from finn.core.datatype import DataType 32 | from finn.core.modelwrapper import ModelWrapper 33 | from finn.transformation.fold_constants import FoldConstants 34 | from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames 35 | from finn.transformation.infer_datatypes import InferDataTypes 36 | from finn.transformation.infer_shapes import InferShapes 37 | 38 | 39 | def test_infer_datatypes(): 40 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 41 | model = ModelWrapper(raw_m) 42 | model = model.transform(InferShapes()) 43 | model = model.transform(FoldConstants()) 44 | model = model.transform(GiveUniqueNodeNames()) 45 | model = model.transform(GiveReadableTensorNames()) 46 | # this model has no DataType info, so add some DataType annotation 47 | # to make things a bit more exciting 48 | model.set_tensor_datatype("global_in", DataType["UINT8"]) 49 | # Conv with int weights + inputs will have int output datatype 50 | model.set_tensor_datatype("Conv_0_param0", DataType["INT4"]) 51 | model = model.transform(InferDataTypes()) 52 | assert model.get_tensor_datatype("global_in") == DataType["UINT8"] 53 | assert model.get_tensor_datatype("Conv_0_out0") == DataType["INT32"] 54 | assert model.get_tensor_datatype("Relu_0_out0") == DataType["FLOAT32"] 55 | assert model.get_tensor_datatype("global_out") == DataType["FLOAT32"] 56 | -------------------------------------------------------------------------------- /tests/transformation/test_infer_shapes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | from onnx import TensorProto, helper 31 | from pkgutil import get_data 32 | 33 | import finn.util.basic as util 34 | from finn.core.modelwrapper import ModelWrapper 35 | from finn.transformation.infer_shapes import InferShapes 36 | 37 | 38 | def test_infer_shapes(): 39 | # load the onnx model 40 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 41 | model = ModelWrapper(raw_m) 42 | graph = model.graph 43 | 44 | # multi-thresholding node to be inserted between the first Relu and MaxPool node 45 | 46 | # get Relu node to use data 47 | Relu_node = graph.node[3] 48 | assert Relu_node.op_type == "Relu", "The wrong model was chosen for the check" 49 | 50 | # create thresholds tensor as constant 51 | mt_thresh0 = helper.make_tensor_value_info("mt_thresh0", TensorProto.FLOAT, [8, 7]) 52 | 53 | # random numbers for the thresholds 54 | # thresholds for one channel have to be sorted to guarantee the correct behavior 55 | mt_thresh0_values = np.empty([8, 7], dtype=np.float32) 56 | for i in range(len(mt_thresh0_values)): 57 | mt_thresh0_values[i] = np.sort(np.random.random_sample(7) * 10) 58 | 59 | model.set_initializer(mt_thresh0.name, mt_thresh0_values) 60 | 61 | # add multi-thresholding node and change Relu node 62 | mt_node = helper.make_node( 63 | "MultiThreshold", 64 | ["mt_v0", "mt_thresh0"], 65 | [Relu_node.output[0]], 66 | domain="finn.custom_op.general", 67 | ) 68 | Relu_node.output[0] = "mt_v0" 69 | 70 | # explicitly remove any present shape from ReLU and MultiThreshold outputs 71 | util.remove_by_name(model.graph.value_info, Relu_node.output[0]) 72 | util.remove_by_name(model.graph.value_info, mt_node.output[0]) 73 | graph.node.insert(4, mt_node) 74 | 75 | # first check routine 76 | # check if at least one shape is not specified 77 | assert not ( 78 | model.check_all_tensor_shapes_specified() 79 | ), "All tensors are already specified before the shape inference execution" 80 | 81 | # perform shape inference on mixed model 82 | model = model.transform(InferShapes()) 83 | 84 | # second check routine 85 | # now all shapes should be specified and mt_node output shape is (1,8,28,28) 86 | assert ( 87 | model.check_all_tensor_shapes_specified() 88 | ), "There are still tensors that are not specified" 89 | assert (model.get_tensor_shape(mt_node.output[0])) == ( 90 | [1, 8, 28, 28] 91 | ), "output of multi-thresholding node has wrong shape" 92 | -------------------------------------------------------------------------------- /tests/transformation/test_make_input_chanlast.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | 30 | from pkgutil import get_data 31 | 32 | import finn.core.data_layout as data_layout 33 | from finn.core.modelwrapper import ModelWrapper 34 | from finn.transformation.make_input_chanlast import MakeInputChannelsLast 35 | 36 | 37 | def test_make_input_chanlast(): 38 | # load the onnx model 39 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 40 | model = ModelWrapper(raw_m) 41 | iname = model.graph.input[0].name 42 | assert tuple(model.get_tensor_shape(iname)) == (1, 1, 28, 28) 43 | model = model.transform(MakeInputChannelsLast()) 44 | assert model.graph.node[0].op_type == "Transpose" 45 | assert tuple(model.get_tensor_shape(iname)) == (1, 28, 28, 1) 46 | assert model.get_tensor_layout(iname) == data_layout.NHWC 47 | -------------------------------------------------------------------------------- /tests/transformation/test_remove_identity_ops.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | from onnx import TensorProto, helper 5 | 6 | import finn.core.onnx_exec as oxe 7 | from finn.core.datatype import DataType 8 | from finn.core.modelwrapper import ModelWrapper 9 | from finn.transformation.infer_datatypes import InferDataTypes 10 | from finn.transformation.infer_shapes import InferShapes 11 | from finn.transformation.remove import RemoveIdentityOps 12 | from finn.util.basic import gen_finn_dt_tensor 13 | 14 | 15 | def insert_identity_op(model, op, as_first_node, approx): 16 | if approx: 17 | zero_val = 0.000001 18 | one_val = 0.999999 19 | else: 20 | zero_val = 0.0 21 | one_val = 1.0 22 | if op in ["Add", "Sub"]: 23 | val = np.asarray([zero_val], dtype=np.float32) 24 | elif op in ["Mul", "Div"]: 25 | val = np.asarray([one_val], dtype=np.float32) 26 | else: 27 | return 28 | 29 | graph = model.graph 30 | if as_first_node: 31 | identity_node = helper.make_node(op, ["inp", "value"], ["ident_out"]) 32 | graph.node.insert(0, identity_node) 33 | graph.node[1].input[0] = "ident_out" 34 | else: 35 | identity_node = helper.make_node(op, ["div_out", "value"], ["ident_out"]) 36 | graph.node.insert(3, identity_node) 37 | graph.node[-1].input[0] = "ident_out" 38 | model.set_initializer("value", val) 39 | 40 | return model 41 | 42 | 43 | # identity operations to be inserted 44 | @pytest.mark.parametrize("op", ["Add", "Sub", "Mul", "Div"]) 45 | @pytest.mark.parametrize("approx", [False, True]) 46 | @pytest.mark.parametrize("as_first_node", [False, True]) 47 | def test_remove_identity_ops(op, as_first_node, approx): 48 | 49 | # set up onnx model 50 | inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, [1, 4, 1, 1]) 51 | mul = helper.make_tensor_value_info("mul", TensorProto.FLOAT, []) 52 | shape = helper.make_tensor_value_info("shape", TensorProto.FLOAT, [2]) 53 | div = helper.make_tensor_value_info("div", TensorProto.FLOAT, []) 54 | matmul = helper.make_tensor_value_info("matmul", TensorProto.FLOAT, [4, 2]) 55 | outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, [1, 2]) 56 | 57 | mul_node = helper.make_node("Mul", ["inp", "mul"], ["mul_out"]) 58 | reshape_node = helper.make_node("Reshape", ["mul_out", "shape"], ["reshape_out"]) 59 | div_node = helper.make_node("Div", ["reshape_out", "div"], ["div_out"]) 60 | matmul_node = helper.make_node("MatMul", ["div_out", "matmul"], ["outp"]) 61 | 62 | graph = helper.make_graph( 63 | nodes=[mul_node, reshape_node, div_node, matmul_node], 64 | name="identity-graph", 65 | inputs=[inp], 66 | outputs=[outp], 67 | value_info=[mul, shape, div, matmul], 68 | ) 69 | 70 | model = helper.make_model(graph, producer_name="mulpastconv-model") 71 | model = ModelWrapper(model) 72 | inp_values = gen_finn_dt_tensor(DataType["INT2"], [1, 4, 1, 1]) 73 | mul_values = np.random.uniform(low=0.1, high=0.99, size=(1)).astype(np.float32) 74 | shape_values = np.asarray([1, -1], dtype=np.int64) 75 | div_values = np.random.uniform(low=0.1, high=0.99, size=(1)).astype(np.float32) 76 | matmul_values = gen_finn_dt_tensor(DataType["INT2"], [4, 2]) 77 | model.set_initializer("mul", mul_values) 78 | model.set_initializer("shape", shape_values) 79 | model.set_initializer("div", div_values) 80 | model.set_initializer("matmul", matmul_values) 81 | insert_identity_op(model, op, as_first_node, approx) 82 | model = model.transform(InferShapes()) 83 | model = model.transform(InferDataTypes()) 84 | idict = {"inp": inp_values} 85 | odict = oxe.execute_onnx(model, idict) 86 | out_before = odict["outp"] 87 | num_of_nodes_before = len(model.graph.node) 88 | 89 | model = model.transform(RemoveIdentityOps()) 90 | num_of_nodes_after = len(model.graph.node) 91 | assert num_of_nodes_before - 1 == num_of_nodes_after 92 | 93 | odict = oxe.execute_onnx(model, idict) 94 | out_after = odict["outp"] 95 | assert np.isclose(out_before, out_after, atol=1e-3).all() 96 | -------------------------------------------------------------------------------- /tests/transformation/test_renaming.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import pytest 30 | 31 | import numpy as np 32 | import onnx 33 | import onnx.numpy_helper as np_helper 34 | import os 35 | import urllib.request as ureq 36 | from pkgutil import get_data 37 | 38 | import finn.core.onnx_exec as oxe 39 | from finn.core.modelwrapper import ModelWrapper 40 | from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames 41 | from finn.transformation.infer_shapes import InferShapes 42 | 43 | 44 | def test_renaming(): 45 | # load the onnx model 46 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 47 | model = ModelWrapper(raw_m) 48 | model = model.transform(InferShapes()) 49 | model = model.transform(GiveUniqueNodeNames()) 50 | model = model.transform(GiveReadableTensorNames()) 51 | # do some basic checks 52 | assert model.graph.input[0].name == "global_in" 53 | assert model.graph.output[0].name == "global_out" 54 | assert model.graph.node[1].op_type == "Conv" 55 | assert model.graph.node[1].name == "Conv_0" 56 | assert model.graph.node[1].input[1] == "Conv_0_param0" 57 | assert model.graph.node[6].op_type == "Add" 58 | assert model.graph.node[6].name == "Add_1" 59 | assert model.graph.node[6].input[1] == "Add_1_param0" 60 | # ensure running renaming twice still yields the same names 61 | model = model.transform(GiveUniqueNodeNames()) 62 | model = model.transform(GiveReadableTensorNames()) 63 | assert model.graph.node[1].op_type == "Conv" 64 | assert model.graph.node[1].name == "Conv_0" 65 | assert model.graph.node[1].input[1] == "Conv_0_param0" 66 | assert model.graph.node[6].op_type == "Add" 67 | assert model.graph.node[6].name == "Add_1" 68 | assert model.graph.node[6].input[1] == "Add_1_param0" 69 | # run renamed model to make sure we did not mess up the topology 70 | raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") 71 | raw_o = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/output_0.pb") 72 | input_tensor = onnx.load_tensor_from_string(raw_i) 73 | output_tensor = onnx.load_tensor_from_string(raw_o) 74 | input_dict = {"global_in": np_helper.to_array(input_tensor)} 75 | output_dict = oxe.execute_onnx(model, input_dict) 76 | assert np.isclose( 77 | np_helper.to_array(output_tensor), output_dict["global_out"], atol=1e-3 78 | ).all() 79 | 80 | 81 | def test_rename_multi_io_tinyyolov3(): 82 | download_url = ( 83 | "https://github.com/onnx/models/raw/main/vision/object_detection_segmentation" 84 | ) 85 | download_url += "/tiny-yolov3/model/tiny-yolov3-11.onnx" 86 | export_onnx_path = download_url.split("/")[-1] 87 | ureq.urlretrieve(download_url, export_onnx_path) 88 | if not os.path.isfile(export_onnx_path): 89 | pytest.skip("Couldn't download ONNX model, skipping") 90 | model = ModelWrapper(export_onnx_path) 91 | model = model.transform(GiveUniqueNodeNames()) 92 | model = model.transform(GiveReadableTensorNames()) 93 | assert len(model.graph.input) == 2 94 | assert model.graph.input[0].name == "global_in" 95 | assert model.graph.input[1].name == "global_in_1" 96 | assert len(model.graph.output) == 3 97 | assert model.graph.output[0].name == "global_out" 98 | assert model.graph.output[1].name == "global_out_1" 99 | assert model.graph.output[2].name == "global_out_2" 100 | os.remove(export_onnx_path) 101 | -------------------------------------------------------------------------------- /tests/transformation/test_sort_graph.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | from onnx import TensorProto, helper 5 | 6 | import finn.analysis.topology as ta 7 | from finn.core.modelwrapper import ModelWrapper 8 | from finn.transformation.general import SortGraph 9 | from finn.transformation.infer_shapes import InferShapes 10 | 11 | 12 | def make_randomly_sorted_linear_model(num_of_nodes, seed=None): 13 | if seed is not None: 14 | np.random.seed(seed) 15 | 16 | ch = 2 17 | ifmdim = 16 18 | input_shape = (1, ch, ifmdim, ifmdim) 19 | 20 | top_in = helper.make_tensor_value_info("t0", TensorProto.FLOAT, input_shape) 21 | top_out = helper.make_tensor_value_info( 22 | "t" + str(num_of_nodes), TensorProto.FLOAT, input_shape 23 | ) 24 | 25 | value_info = [] 26 | nodes = [] 27 | for i in range(num_of_nodes): 28 | nodes += [ 29 | helper.make_node("Add", ["t" + str(i), "p" + str(i)], ["t" + str(i + 1)]) 30 | ] 31 | value_info += [ 32 | helper.make_tensor_value_info("p" + str(i), TensorProto.FLOAT, input_shape) 33 | ] 34 | 35 | nodes = np.random.permutation(nodes) 36 | 37 | modelproto = helper.make_model( 38 | helper.make_graph( 39 | name="test", 40 | inputs=[top_in], 41 | outputs=[top_out], 42 | value_info=value_info, 43 | nodes=nodes, 44 | ) 45 | ) 46 | model = ModelWrapper(modelproto) 47 | model = model.transform(InferShapes()) 48 | 49 | for i in range(num_of_nodes): 50 | model.set_initializer( 51 | "p" + str(i), np.random.rand(*input_shape).astype(np.float32) 52 | ) 53 | 54 | return model 55 | 56 | 57 | @pytest.mark.parametrize("num_of_nodes", [64]) 58 | def test_sort_linear_graph(num_of_nodes): 59 | model = make_randomly_sorted_linear_model(num_of_nodes, seed=0) 60 | new_model = model.transform(SortGraph()) 61 | 62 | # Test 63 | ret = new_model.analysis(ta.nodes_topologically_sorted) 64 | assert ret["nodes_topologically_sorted"], "Nodes are not topologically sorted." 65 | 66 | 67 | def test_sort_nonlinear_graph(): 68 | ch = 2 69 | ifmdim = 16 70 | input_shape = (1, ch, ifmdim, ifmdim) 71 | 72 | top_in = helper.make_tensor_value_info("top_in", TensorProto.FLOAT, input_shape) 73 | top_out = helper.make_tensor_value_info("top_out", TensorProto.FLOAT, input_shape) 74 | 75 | num_of_params = 8 76 | value_info = [] 77 | for i in range(num_of_params): 78 | value_info += [ 79 | helper.make_tensor_value_info("p" + str(i), TensorProto.FLOAT, input_shape) 80 | ] 81 | 82 | modelproto = helper.make_model( 83 | helper.make_graph( 84 | name="test", 85 | inputs=[top_in], 86 | outputs=[top_out], 87 | value_info=value_info, 88 | nodes=[ 89 | # Not sorted nodes 90 | helper.make_node("Mul", ["fork1", "p2"], ["t3"]), 91 | helper.make_node("Add", ["t4", "p3"], ["t5"]), 92 | helper.make_node("Add", ["t2", "t3"], ["t4"]), 93 | helper.make_node("Add", ["t6", "t7"], ["t8"]), 94 | helper.make_node("Add", ["fork3", "fork3"], ["top_out"]), 95 | helper.make_node("Mul", ["t5", "p4"], ["fork2"]), 96 | helper.make_node("Add", ["top_in", "p0"], ["fork1"]), 97 | helper.make_node("Mul", ["fork1", "p1"], ["t2"]), 98 | helper.make_node("Add", ["fork2", "p5"], ["t6"]), 99 | helper.make_node("Add", ["fork2", "p6"], ["t7"]), 100 | helper.make_node("Mul", ["t8", "p7"], ["fork3"]), 101 | ], 102 | ) 103 | ) 104 | model = ModelWrapper(modelproto) 105 | model = model.transform(InferShapes()) 106 | 107 | np.random.seed(0) 108 | for i in range(num_of_params): 109 | model.set_initializer( 110 | "p" + str(i), np.random.rand(*input_shape).astype(np.float32) 111 | ) 112 | 113 | new_model = model.transform(SortGraph()) 114 | 115 | # Test 116 | ret = new_model.analysis(ta.nodes_topologically_sorted) 117 | assert ret["nodes_topologically_sorted"], "Nodes are not topologically sorted." 118 | 119 | 120 | if __name__ == "__main__": 121 | import time 122 | 123 | sizes = [10, 50, 100, 500, 1000] 124 | times = [] 125 | reps = 10 126 | 127 | print("SortGraph performance test:") 128 | print("Test sizes", sizes) 129 | print("Repetitions per size:", reps) 130 | for sz in sizes: 131 | acc_time = 0 132 | print(" Testing size ", sz) 133 | for i in range(reps): 134 | # it should take the same time even with the sorted one 135 | # but better new model each time as it is a more general approach 136 | model = make_randomly_sorted_linear_model(sz) # new model as seed is None 137 | bef = time.time() 138 | new_model = model.transform(SortGraph(), make_deepcopy=False) 139 | acc_time += time.time() - bef 140 | 141 | times += [acc_time / reps] 142 | 143 | # print csv 144 | print("\nnum_of_nodes, seconds") 145 | for sz, tm in zip(sizes, times): 146 | print("{:12d}, {:6.4e}".format(sz, tm)) 147 | 148 | # plot 149 | # import matplotlib.pyplot as plt 150 | # plt.plot(sizes,times,"--o") 151 | # plt.grid(True) 152 | -------------------------------------------------------------------------------- /tests/transformation/test_topk_insert.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | import onnx 5 | import onnx.numpy_helper as nph 6 | from pkgutil import get_data 7 | 8 | import finn.core.onnx_exec as oxe 9 | from finn.core.modelwrapper import ModelWrapper 10 | from finn.transformation.fold_constants import FoldConstants 11 | from finn.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames 12 | from finn.transformation.infer_datatypes import InferDataTypes 13 | from finn.transformation.infer_shapes import InferShapes 14 | from finn.transformation.insert_topk import InsertTopK 15 | 16 | 17 | @pytest.mark.parametrize("k", [1, 2]) 18 | def test_topk_insert(k): 19 | raw_m = get_data("finn.data", "onnx/mnist-conv/model.onnx") 20 | model = ModelWrapper(raw_m) 21 | model.model.opset_import[0].version = 11 22 | 23 | # do transformations (no topk) 24 | model = model.transform(InferShapes()) 25 | model = model.transform(FoldConstants()) 26 | model = model.transform(GiveUniqueNodeNames()) 27 | model = model.transform(GiveReadableTensorNames()) 28 | model = model.transform(InferDataTypes()) 29 | 30 | # verification: generate random input, run through net, streamline, 31 | # run again, check that output is top-k 32 | raw_i = get_data("finn.data", "onnx/mnist-conv/test_data_set_0/input_0.pb") 33 | input_tensor = onnx.load_tensor_from_string(raw_i) 34 | input_tensor = nph.to_array(input_tensor) 35 | input_dict = {"global_in": input_tensor} 36 | output_golden = oxe.execute_onnx(model, input_dict)["global_out"] 37 | output_golden_topk = np.flip(output_golden.flatten().argsort())[:k] 38 | output_golden_topk = output_golden_topk.flatten() 39 | 40 | # insert top-k 41 | model = model.transform(InsertTopK(k)) 42 | model = model.transform(GiveUniqueNodeNames()) 43 | model = model.transform(GiveReadableTensorNames()) 44 | model = model.transform(InferShapes()) 45 | 46 | # verify output of top-k 47 | output_dict_topk = oxe.execute_onnx(model, input_dict) 48 | output_pysim_topk = output_dict_topk[list(output_dict_topk.keys())[0]] 49 | output_pysim_topk = output_pysim_topk.astype(np.int).flatten() 50 | 51 | assert np.array_equal(output_golden_topk, output_pysim_topk) 52 | -------------------------------------------------------------------------------- /tests/util/test_gen_finn_dt_tensor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import finn.util.basic as util 30 | from finn.core.datatype import DataType 31 | 32 | 33 | def test_finn_tensor_generator(): 34 | # bipolar 35 | shape_bp = [2, 2] 36 | dt_bp = DataType["BIPOLAR"] 37 | tensor_bp = util.gen_finn_dt_tensor(dt_bp, shape_bp) 38 | # test shape 39 | for i in range(len(shape_bp)): 40 | assert ( 41 | shape_bp[i] == tensor_bp.shape[i] 42 | ), """Shape of generated tensor 43 | does not match the desired shape""" 44 | # test if elements are FINN datatype 45 | for value in tensor_bp.flatten(): 46 | assert dt_bp.allowed( 47 | value 48 | ), """Data type of generated tensor 49 | does not match the desired Data type""" 50 | 51 | # binary 52 | shape_b = [4, 2, 3] 53 | dt_b = DataType["BINARY"] 54 | tensor_b = util.gen_finn_dt_tensor(dt_b, shape_b) 55 | # test shape 56 | for i in range(len(shape_b)): 57 | assert ( 58 | shape_b[i] == tensor_b.shape[i] 59 | ), """Shape of generated tensor 60 | does not match the desired shape""" 61 | # test if elements are FINN datatype 62 | for value in tensor_b.flatten(): 63 | assert dt_b.allowed( 64 | value 65 | ), """Data type of generated tensor 66 | does not match the desired Data type""" 67 | 68 | # ternary 69 | shape_t = [7, 1, 3, 1] 70 | dt_t = DataType["TERNARY"] 71 | tensor_t = util.gen_finn_dt_tensor(dt_t, shape_t) 72 | # test shape 73 | for i in range(len(shape_t)): 74 | assert ( 75 | shape_t[i] == tensor_t.shape[i] 76 | ), """Shape of generated tensor 77 | does not match the desired shape""" 78 | # test if elements are FINN datatype 79 | for value in tensor_t.flatten(): 80 | assert dt_t.allowed( 81 | value 82 | ), """Data type of generated tensor 83 | does not match the desired Data type""" 84 | 85 | # int2 86 | shape_int2 = [7, 4] 87 | dt_int2 = DataType["INT2"] 88 | tensor_int2 = util.gen_finn_dt_tensor(dt_int2, shape_int2) 89 | # test shape 90 | for i in range(len(shape_int2)): 91 | assert ( 92 | shape_int2[i] == tensor_int2.shape[i] 93 | ), """Shape of generated tensor 94 | does not match the desired shape""" 95 | # test if elements are FINN datatype 96 | for value in tensor_int2.flatten(): 97 | assert value in [ 98 | -2, 99 | -1, 100 | 0, 101 | 1, 102 | ], """Data type of generated tensor 103 | does not match the desired Data type""" 104 | 105 | # import pdb; pdb.set_trace() 106 | 107 | # fixed point 108 | dt_t = DataType["FIXED<9,6>"] 109 | tensor_t = util.gen_finn_dt_tensor(dt_t, shape_t) 110 | # test shape 111 | for i in range(len(shape_t)): 112 | assert ( 113 | shape_t[i] == tensor_t.shape[i] 114 | ), """Shape of generated tensor 115 | does not match the desired shape""" 116 | # test if elements are FINN datatype 117 | for value in tensor_t.flatten(): 118 | assert dt_t.allowed( 119 | value 120 | ), """Data type of generated tensor 121 | does not match the desired Data type""" 122 | -------------------------------------------------------------------------------- /tests/util/test_padding.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | 31 | from finn.util.basic import pad_tensor_to_multiple_of 32 | 33 | 34 | def test_pad_tensor_to_multiple_of(): 35 | A = np.eye(3) 36 | B = pad_tensor_to_multiple_of(A, [2, 2], val=-1) 37 | assert B.shape == (4, 4) 38 | assert (B[:3, :3] == A).all() 39 | assert (B[3, :] == -1).all() 40 | assert (B[:, 3] == -1).all() 41 | B = pad_tensor_to_multiple_of(A, [5, 5], val=-1, distr_pad=True) 42 | assert B.shape == (5, 5) 43 | assert (B[1:4, 1:4] == A).all() 44 | assert (B[0, :] == -1).all() 45 | assert (B[:, 0] == -1).all() 46 | assert (B[4, :] == -1).all() 47 | assert (B[:, 4] == -1).all() 48 | # using -1 in pad_to parameter should give an unpadded dimension 49 | B = pad_tensor_to_multiple_of(A, [-1, 5], val=-1, distr_pad=True) 50 | assert B.shape == (3, 5) 51 | assert (B[:, 1:4] == A).all() 52 | assert (B[:, 0] == -1).all() 53 | assert (B[:, 4] == -1).all() 54 | # if odd number of padding pixels required, 1 more should go after existing 55 | B = pad_tensor_to_multiple_of(A, [6, 6], val=-1, distr_pad=True) 56 | assert B.shape == (6, 6) 57 | assert (B[1:4, 1:4] == A).all() 58 | -------------------------------------------------------------------------------- /tests/util/test_pyverilator.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020, Xilinx 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of FINN nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import pkg_resources as pk 30 | 31 | from pyverilator import PyVerilator 32 | 33 | from finn.util.pyverilator import axilite_read, axilite_write, reset_rtlsim 34 | 35 | 36 | def test_pyverilator_axilite(): 37 | example_root = pk.resource_filename("finn.data", "verilog/myadd") 38 | # load example verilog: takes two 32-bit integers as AXI lite mem mapped 39 | # registers, adds them together and return result 40 | sim = PyVerilator.build( 41 | "myadd_myadd.v", 42 | verilog_path=[example_root], 43 | top_module_name="myadd_myadd", 44 | ) 45 | ifname = "s_axi_control_" 46 | expected_signals = [ 47 | "AWVALID", 48 | "AWREADY", 49 | "AWADDR", 50 | "WVALID", 51 | "WREADY", 52 | "WDATA", 53 | "WSTRB", 54 | "ARVALID", 55 | "ARREADY", 56 | "ARADDR", 57 | "RVALID", 58 | "RREADY", 59 | "RDATA", 60 | "RRESP", 61 | "BVALID", 62 | "BREADY", 63 | "BRESP", 64 | ] 65 | for signal_name in expected_signals: 66 | assert ifname + signal_name in sim.io 67 | reset_rtlsim(sim) 68 | # initial values 69 | sim.io[ifname + "WVALID"] = 0 70 | sim.io[ifname + "AWVALID"] = 0 71 | sim.io[ifname + "ARVALID"] = 0 72 | sim.io[ifname + "BREADY"] = 0 73 | sim.io[ifname + "RREADY"] = 0 74 | # write + verify first parameter in AXI lite memory mapped regs 75 | val_a = 3 76 | addr_a = 0x18 77 | axilite_write(sim, addr_a, val_a) 78 | ret_data = axilite_read(sim, addr_a) 79 | assert ret_data == val_a 80 | # write + verify second parameter in AXI lite memory mapped regs 81 | val_b = 5 82 | addr_b = 0x20 83 | axilite_write(sim, addr_b, val_b) 84 | ret_data = axilite_read(sim, addr_b) 85 | assert ret_data == val_b 86 | # launch accelerator and wait for completion 87 | addr_ctrl_status = 0x00 88 | # check for ap_idle 89 | assert axilite_read(sim, addr_ctrl_status) and (1 << 2) != 0 90 | # set ap_start 91 | axilite_write(sim, addr_ctrl_status, 1) 92 | # wait until ap_done 93 | while 1: 94 | ap_done = axilite_read(sim, addr_ctrl_status) and (1 << 1) 95 | if ap_done != 0: 96 | break 97 | # read out and verify result 98 | addr_return = 0x10 99 | val_ret = axilite_read(sim, addr_return) 100 | assert val_ret == val_a + val_b 101 | -------------------------------------------------------------------------------- /tests/util/test_rtlsim2npy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | 31 | from finn.core.datatype import DataType 32 | from finn.util.data_packing import unpack_innermost_dim_from_hex_string 33 | 34 | 35 | def test_unpack_innermost_dim_from_hex_string(): 36 | # BINARY 37 | A = np.asarray(["0x0e", "0x06"]) 38 | dtype = DataType["BINARY"] 39 | shape = (1, 2, 4) 40 | eA = [[1, 1, 1, 0], [0, 1, 1, 0]] 41 | A_unpacked = unpack_innermost_dim_from_hex_string(A, dtype, shape, 8) 42 | assert (A_unpacked == eA).all() 43 | 44 | A = np.asarray(["0x0e", "0x06"]) 45 | eA_flipped = [[0, 1, 1, 1], [0, 1, 1, 0]] 46 | A_unpacked_flipped = unpack_innermost_dim_from_hex_string( 47 | A, dtype, shape, 8, reverse_inner=True 48 | ) 49 | assert (A_unpacked_flipped == eA_flipped).all() 50 | 51 | # UINT2 52 | B = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) 53 | dtype = DataType["UINT2"] 54 | shape = (1, 2, 2, 2) 55 | eB = [[[3, 3], [3, 3]], [[1, 3], [3, 1]]] 56 | B_unpacked = unpack_innermost_dim_from_hex_string(B, dtype, shape, 8) 57 | assert (B_unpacked == eB).all() 58 | 59 | B = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) 60 | eB_flipped = [[[3, 3], [3, 3]], [[3, 1], [1, 3]]] 61 | B_unpacked_flipped = unpack_innermost_dim_from_hex_string( 62 | B, dtype, shape, 8, reverse_inner=True 63 | ) 64 | assert (B_unpacked_flipped == eB_flipped).all() 65 | 66 | # INT2 67 | C = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) 68 | dtype = DataType["INT2"] 69 | shape = (1, 2, 2, 2) 70 | eC = [[[-1, -1], [-1, -1]], [[1, -1], [-1, 1]]] 71 | C_unpacked = unpack_innermost_dim_from_hex_string(C, dtype, shape, 8) 72 | assert (C_unpacked == eC).all() 73 | 74 | C = np.asarray([["0x0f", "0x0f"], ["0x07", "0x0d"]]) 75 | dtype = DataType["INT2"] 76 | shape = (1, 2, 2, 2) 77 | eC = [[[-1, -1], [-1, -1]], [[-1, 1], [1, -1]]] 78 | C_unpacked = unpack_innermost_dim_from_hex_string( 79 | C, dtype, shape, 8, reverse_inner=True 80 | ) 81 | assert (C_unpacked == eC).all() 82 | 83 | # INT4 84 | D = np.asarray(["0x0e", "0x06"]) 85 | dtype = DataType["INT4"] 86 | shape = (2, 1) 87 | eD = [[-2], [6]] 88 | D_unpacked = unpack_innermost_dim_from_hex_string(D, dtype, shape, 8) 89 | assert (D_unpacked == eD).all() 90 | 91 | D_unpacked = unpack_innermost_dim_from_hex_string( 92 | D, dtype, shape, 8, reverse_inner=True 93 | ) 94 | assert (D_unpacked == eD).all() 95 | 96 | # INT32 97 | E = np.asarray(["0xffffffff", "0xfffffffe", "0x02", "0xffffffef"]) 98 | dtype = DataType["INT32"] 99 | shape = (1, 4, 1) 100 | eE = [[[-1], [-2], [2], [-17]]] 101 | E_unpacked = unpack_innermost_dim_from_hex_string(E, dtype, shape, 32) 102 | assert (E_unpacked == eE).all() 103 | 104 | E_unpacked = unpack_innermost_dim_from_hex_string( 105 | E, dtype, shape, 32, reverse_inner=True 106 | ) 107 | assert (E_unpacked == eE).all() 108 | -------------------------------------------------------------------------------- /tests/util/test_shape_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Xilinx, Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Xilinx nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import numpy as np 30 | 31 | import finn.util.basic as util 32 | 33 | 34 | def test_interleave_matrix_outer_dim_from_partitions(): 35 | A = np.eye(10) 36 | n_parts = 2 37 | Ax = util.interleave_matrix_outer_dim_from_partitions(A, n_parts) 38 | part_size = 10 // n_parts 39 | assert Ax.shape == (n_parts, part_size, 10) 40 | for r_ind in range(A.shape[0]): 41 | assert (A[r_ind] == Ax[r_ind % n_parts][r_ind // n_parts]).all() 42 | --------------------------------------------------------------------------------