├── .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 |
--------------------------------------------------------------------------------