diff --git a/README.md b/README.md index da3b0fd..ea9cc38 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ -## Dataflow Accelerator Examples -*for PYNQ on Zynq and Alveo* +# Dataflow Accelerator Examples +

for PYNQ on Zynq and Alveo

+

+ + + + + + + + + + + + +

drawing @@ -18,34 +32,81 @@ Need help with a problem in this repo, or got a question? Feel free to ask for h In the past, we also had a [Gitter channel](https://gitter.im/xilinx-finn/community). Please be aware that this is no longer maintained by us but can still be used to search for questions previous users had. ## Quickstart +*We recommend PYNQ version 3.0.1, but older installations of PYNQ should also work. For PYNQ v2.6.1, please refer for set-up instructions to [FINN-examples v0.0.5](https://github.com/Xilinx/finn-examples/tree/v0.0.5).* +### Zynq +*For ZYNQ boards, all commands below must be prefixed with `sudo` or by first going into `sudo su`.* -*For Alveo we recommend setting up everything inside a virtualenv as described [here](https://pynq.readthedocs.io/en/v2.6.1/getting_started/alveo_getting_started.html?highlight=alveo#install-conda).* -*For PYNQ boards, all commands below must be prefixed with `sudo` or by first going into `sudo su`. We recommend PYNQ version 2.6.1 as some installation issues have been reported for PYNQ version 2.7.* +First, source the PYNQ and XRT virtual environment: + +```shell +source /etc/profile.d/pynq_venv.sh +source /etc/profile.d/xrt_setup.sh +``` -First, ensure that your `pip` and `setuptools` installations are up-to-date -on your PYNQ board or Alveo server: +Next, ensure that your `pip` and `setuptools` installations are up-to-date +on your PYNQ board: ```shell -python3 -m pip install --upgrade pip setuptools +python3 -m pip install pip==23.0 setuptools==67.1.0 +``` + +Since we are going to install finn-examples without build-isolation, we need to ensure all dependencies are installed. For that, install `setuptools_csm` as well: + +```shell +python3 -m pip install setuptools_scm==7.1.0 ``` Install the `finn-examples` package using `pip`: ```shell # remove previous versions with: pip3 uninstall finn-examples -pip3 install finn-examples +pip3 install finn-examples --no-build-isolation # to install particular git branch: -# pip3 install git+https://github.com/Xilinx/finn-examples.git@dev +# pip3 install git+https://github.com/Xilinx/finn-examples.git@dev --no-build-isolation ``` -Retrieve the example Jupyter notebooks using the PYNQ get-notebooks command: +Retrieve the example Jupyter notebooks using the PYNQ get-notebooks command. An example of how to run the Jupyter notebook server, assuming we are forwarding port 8888 from the target to some port on our local machine, is also shown below: ```shell # on PYNQ boards, first cd /home/xilinx/jupyter_notebooks pynq get-notebooks --from-package finn-examples -p . --force +jupyter-notebook --no-browser --allow-root --port=8888 +``` + +### Alveo +*For Alveo we recommend setting up everything inside a virtualenv as described [here](https://pynq.readthedocs.io/en/v2.6.1/getting_started/alveo_getting_started.html?highlight=alveo#install-conda).* + +First, create & source a virtual environment: +```shell +conda create -n python=3.10 +conda activate +``` + +Next, ensure that your `pip` and `setuptools` installations are up-to-date: +```shell +python3 -m pip install --upgrade pip==23.0 setuptools==67.2.0 +``` + +Finally, we can now install Pynq, FINN-examples and Jupyter (please note to source the XRT environment before): +```shell +pip3 install pynq==3.0.1 +python3 -m pip install setuptools_scm==7.1.0 ipython==8.9.0 +pip3 install finn-examples --no-build-isolation +# to install particular git branch: +# pip3 install git+https://github.com/Xilinx/finn-examples.git@dev --no-build-isolation +python3 -m pip install jupyter==1.0.0 +``` + +Retrieve the example Jupyter notebooks using the PYNQ get-notebooks command. An example of how to run the Jupyter notebook server is also shown below: + +```shell +pynq get-notebooks --from-package finn-examples -p . --force +jupyter-notebook --no-browser --port=8888 ``` +*** + You can now navigate the provided Jupyter notebook examples, or just use the provided accelerators as part of your own Python program: @@ -56,7 +117,7 @@ import numpy as np # instantiate the accelerator accel = models.cnv_w2a2_cifar10() # generate an empty numpy array to use as input -dummy_in = np.empty(accel.ishape_normal, dtype=np.uint8) +dummy_in = np.empty(accel.ishape_normal(), dtype=np.uint8) # perform inference and get output dummy_out = accel.execute(dummy_in) ``` @@ -64,15 +125,17 @@ dummy_out = accel.execute(dummy_in) ## Example Neural Network Accelerators | Dataset | Topology | Quantization | Supported boards | Supported build flows |----------------------------------------------------------------|-------------------------|------------------------------------------------------------|------------------|------------------| -|
CIFAR-10 | CNV (VGG-11-like) | several variants:
1/2-bit weights/activations | all | Pynq-Z1
ZCU104
Ultra96 | -|

MNIST | 3-layer fully-connected | several variants:
1/2-bit weights/activations | all | Pynq-Z1
ZCU104
Ultra96 | -|

ImageNet | MobileNet-v1 | 4-bit weights and activations
8-bit first layer weights | Alveo U250
ZCU104 | ZCU104 | +|
CIFAR-10 | CNV (VGG-11-like) | several variants:
1/2-bit weights/activations | Pynq-Z1
ZCU104
Ultra96
U250 | Pynq-Z1
ZCU104
Ultra96
U250 | +|

MNIST | 3-layer fully-connected | several variants:
1/2-bit weights/activations | Pynq-Z1
ZCU104
Ultra96
U250 | Pynq-Z1
ZCU104
Ultra96
U250 | +|

ImageNet | MobileNet-v1 | 4-bit weights & activations
8-bit first layer weights | Alveo U250 | Alveo U250 | |

ImageNet | ResNet-50 | 1-bit weights 2-bit activations
4-bit residuals
8-bit first/last layer weights | Alveo U250 | - | -|

RadioML 2018 | 1D CNN (VGG10) | 4-bit weights and activations | ZCU104 | ZCU104 | -|

MaskedFace-Net | [BinaryCoP](https://arxiv.org/pdf/2102.03456)
*Contributed by TU Munich+BMW* | 1-bit weights and activations | Pynq-Z1 | Pynq-Z1 | -|

Google Speech Commands v2 | 3-layer fully-connected | 3-bit weights and activations | Pynq-Z1 | Pynq-Z1 | +|

RadioML 2018 | 1D CNN (VGG10) | 4-bit weights & activations | ZCU104 | ZCU104 | +|

MaskedFace-Net | [BinaryCoP](https://arxiv.org/pdf/2102.03456)
*Contributed by TU Munich+BMW* | 1-bit weights & activations | Pynq-Z1 | Pynq-Z1 | +|

Google Speech Commands v2 | 3-layer fully-connected | 3-bit weights & activations | Pynq-Z1 | Pynq-Z1 | +|

UNSW-NB15 | 4-layer fully-connected | 2-bit weights & activations | Pynq-Z1
ZCU104
Ultra96 | Pynq-Z1
ZCU104
Ultra96 | -*Please note that for the non-supported Alveo build flows, you can use the pre-built FPGA bitfiles generated with older versions of the Vitis/Vivado tools. These bitfiles target the following Alveo U250 platform: [xilinx_u250_xdma_201830_2](https://www.xilinx.com/products/boards-and-kits/alveo/package-files-archive/u250-2018-3-1.html). +*Please note that the build flow for ResNet-50 for the Alveo U250 has known issues and we're currently working on resolving them. However, you can still execute the associated notebook, as we provide a pre-built FPGA bitfile generated with an older Vivado (/FINN) version targeting the [xilinx_u250_xdma_201830_2](https://www.xilinx.com/products/boards-and-kits/alveo/package-files-archive/u250-2018-3-1.html) platform.*
+*Furthermore, please note that you can target other boards (such as the Pynq-Z2 or ZCU102) by changing the build script manually, but these accelerators have not been tested.* We welcome community contributions to add more examples to this repo! @@ -82,7 +145,7 @@ We welcome community contributions to add more examples to this repo! `finn-examples` provides pre-built FPGA bitfiles for the following boards: -* **Edge:** Pynq-Z1, Pynq-Z2, Ultra96 and ZCU104 +* **Edge:** Pynq-Z1, Ultra96 and ZCU104 * **Datacenter:** Alveo U250 It's possible to generate Vivado IP for the provided examples to target *any* diff --git a/build/bnn-pynq/README.md b/build/bnn-pynq/README.md index 85b0fe1..0c2b65a 100644 --- a/build/bnn-pynq/README.md +++ b/build/bnn-pynq/README.md @@ -32,7 +32,7 @@ FINN_EXAMPLES=/path/to/finn-examples # cd into finn submodule cd $FINN_EXAMPLES/build/finn # launch the build on the bnn-pynq folder -./run-docker.sh build_custom /path/to/finn-examples/build/bnn-pynq +./run-docker.sh build_custom $FINN_EXAMPLES/build/bnn-pynq ``` 5. The generated outputs will be under `bnn-pynq/output__`. You can find a description of the generated files [here](https://finn-dev.readthedocs.io/en/latest/command_line.html#simple-dataflow-build-mode). @@ -40,7 +40,7 @@ cd $FINN_EXAMPLES/build/finn ## Where did those ONNX model files come from? The BNN-PYNQ networks are part of the -[Brevitas examples](https://github.com/Xilinx/brevitas/tree/master/brevitas_examples/bnn_pynq). You can find the details on quantization, accuracy, layers used in the Brevitas repo, as well as the training scripts if you'd like to retrain them yourself. +[Brevitas examples](https://github.com/Xilinx/brevitas/tree/master/src/brevitas_examples/bnn_pynq). You can find the details on quantization, accuracy, layers used in the Brevitas repo, as well as the training scripts if you'd like to retrain them yourself. Subsequently, those trained networks are [exported to ONNX](https://github.com/Xilinx/finn/blob/master/notebooks/basics/1_brevitas_network_import.ipynb). In addition, the particular versions used here have two additions, as described in the "Adding Pre- and Postprocessing" section of [this notebook](https://github.com/Xilinx/finn/blob/master/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb): diff --git a/build/build-all.sh b/build/build-all.sh index 886eecb..4fc2652 100755 --- a/build/build-all.sh +++ b/build/build-all.sh @@ -33,7 +33,7 @@ SCRIPT=$(readlink -f "$0") # absolute path this script is in, thus /home/user/bin SCRIPTPATH=$(dirname "$SCRIPT") # subdirs for all finn-examples build folders -BUILD_FOLDERS="bnn-pynq kws mobilenet-v1 resnet50 vgg10-radioml" +BUILD_FOLDERS="bnn-pynq kws mobilenet-v1 resnet50 vgg10-radioml cybersecurity-mlp" # all HW platforms we build for PLATFORMS="Pynq-Z1 Ultra96 ZCU104 U250" diff --git a/build/cybersecurity-mlp/README.md b/build/cybersecurity-mlp/README.md new file mode 100644 index 0000000..5a15d0e --- /dev/null +++ b/build/cybersecurity-mlp/README.md @@ -0,0 +1,22 @@ +# The multilayer perceptron for cybersecurity use-cases +The multi layer perceptron (MLP) for the cybersecurity use-case is based on the three-part tutorial for training a quantized MLP and deploying it with FINN, which is provided in the FINN [end-to-end example repository](https://github.com/Xilinx/finn/tree/main/notebooks/end2end_example). The MLP consists of four fully-connected layers in total: three hidden layers with 64 neurons, and a final output layer with a single output, all using 2-bit weights. For more information on training the network, or more details behind what's happening under the hood, the notebooks provided in the FINN end-to-end example repository serve as an excellent starting point. + +# Build bitfiles for MLP example +0. Ensure you have performed the *Setup* steps in the top-level README for setting up the FINN requirements and environment variables. + +1. Edit the `mlp-cybersecurity/build.py` to restrict the platform variables to the ones that you are interested in, e.g. `platforms_to_build = ["Pynq-Z1"]`. You can also change the other build configuration options, see the [FINN docs](https://finn-dev.readthedocs.io/en/latest/source_code/finn.util.html#finn.util.build_dataflow.DataflowBuildConfig) for a full explanation. + +2. Launch the build as follows: +```shell +# update this according to where you cloned this repo: +FINN_EXAMPLES=/path/to/finn-examples +# cd into finn submodule +cd $FINN_EXAMPLES/build/finn +# launch the build on the cybersecurity-mlp folder +./run-docker.sh build_custom $FINN_EXAMPLES/build/cybersecurity-mlp +``` + +3. The generated outputs will be under `cybersecurity-mlp/output__`. You can find a description of the generated files [here](https://finn-dev.readthedocs.io/en/latest/command_line.html#simple-dataflow-build-mode). + +# Where did the ONNX model file come from? +The ONNX model is created and exported prior to the build flow is launched. You can find the details of this process in the `cybersecurity-mlp/custom_steps.py` file. \ No newline at end of file diff --git a/build/cybersecurity-mlp/build.py b/build/cybersecurity-mlp/build.py new file mode 100644 index 0000000..e5f4181 --- /dev/null +++ b/build/cybersecurity-mlp/build.py @@ -0,0 +1,78 @@ +import finn.builder.build_dataflow as build +import finn.builder.build_dataflow_config as build_cfg +from finn.util.basic import alveo_default_platform +import os +import shutil +from custom_steps import custom_step_mlp_export + +# Which platforms to build the networks for +zynq_platforms = ["Pynq-Z1", "Ultra96", "ZCU104"] +alveo_platforms = [] + +# Note: only zynq platforms currently tested +platforms_to_build = zynq_platforms + alveo_platforms + +# determine which shell flow to use for a given platform +def platform_to_shell(platform): + if platform in zynq_platforms: + return build_cfg.ShellFlowType.VIVADO_ZYNQ + elif platform in alveo_platforms: + return build_cfg.ShellFlowType.VITIS_ALVEO + else: + raise Exception("Unknown platform, can't determine ShellFlowType") + +# Define model name +model_name = "unsw_nb15-mlp-w2a2" + +# Create a release dir, used for finn-examples release packaging +os.makedirs("release", exist_ok=True) + +for platform_name in platforms_to_build: + shell_flow_type = platform_to_shell(platform_name) + if shell_flow_type == build_cfg.ShellFlowType.VITIS_ALVEO: + vitis_platform = alveo_default_platform[platform_name] + # for Alveo, use the Vitis platform name as the release name + # e.g. xilinx_u250_xdma_201830_2 + release_platform_name = vitis_platform + else: + vitis_platform = None + # for Zynq, use the board name as the release name + # e.g. ZCU104 + release_platform_name = platform_name + platform_dir = "release/%s" % release_platform_name + os.makedirs(platform_dir, exist_ok=True) + # Set up the build configuration for this model + cfg = build_cfg.DataflowBuildConfig( + output_dir = "output_%s_%s" % (model_name, release_platform_name), + mvau_wwidth_max = 80, + target_fps = 1000000, + synth_clk_period_ns = 10.0, + board = platform_name, + shell_flow_type = shell_flow_type, + vitis_platform = vitis_platform, + vitis_opt_strategy = build_cfg.VitisOptStrategyCfg.PERFORMANCE_BEST, + generate_outputs = [ + build_cfg.DataflowOutputType.PYNQ_DRIVER, + build_cfg.DataflowOutputType.ESTIMATE_REPORTS, + build_cfg.DataflowOutputType.BITFILE, + build_cfg.DataflowOutputType.DEPLOYMENT_PACKAGE, + ], + save_intermediate_models=True + ) + + # Export MLP model to FINN-ONNX + model = custom_step_mlp_export(model_name) + # Launch FINN compiler to generate bitfile + build.build_dataflow_cfg(model, cfg) + # Copy bitfiles into release dir if found + bitfile_gen_dir = cfg.output_dir + "/bitfile" + filtes_to_check_and_copy = [ + "finn-accel.bit", + "finn-accel.hwh", + "finn-accel.xclbin" + ] + for f in filtes_to_check_and_copy: + src_file = bitfile_gen_dir + "/" + f + dst_file = platform_dir + "/" + f.replace("finn-accel", model_name) + if os.path.isfile(src_file): + shutil.copy(src_file, dst_file) diff --git a/build/cybersecurity-mlp/custom_steps.py b/build/cybersecurity-mlp/custom_steps.py new file mode 100644 index 0000000..17a0d4f --- /dev/null +++ b/build/cybersecurity-mlp/custom_steps.py @@ -0,0 +1,93 @@ +import numpy as np +import pkg_resources as pk +import os + +from brevitas.nn import QuantLinear, QuantReLU, QuantIdentity +import torch +import torch.nn as nn +import brevitas.onnx as bo +from brevitas.quant_tensor import QuantTensor + +# Define export wrapper +class CybSecMLPForExport(nn.Module): + def __init__(self, my_pretrained_model): + super(CybSecMLPForExport, self).__init__() + self.pretrained = my_pretrained_model + self.qnt_output = QuantIdentity( + quant_type='binary', + scaling_impl_type='const', + bit_width=1, + min_val=-1.0, + max_val=1.0 + ) + + def forward(self, x): + # assume x contains bipolar {-1,1} elems + # shift from {-1,1} -> {0,1} since that is the + # input range for the trained network + x = (x + torch.tensor([1.0]).to("cpu")) / 2.0 + out_original = self.pretrained(x) + out_final = self.qnt_output(out_original) # output as {-1, 1} + return out_final + +def custom_step_mlp_export(model_name): + # Define model parameters + input_size = 593 + hidden1 = 64 + hidden2 = 64 + hidden3 = 64 + weight_bit_width = 2 + act_bit_width = 2 + num_classes = 1 + + # Create model definition from Brevitas library + model = nn.Sequential( + QuantLinear(input_size, hidden1, bias=True, weight_bit_width=weight_bit_width), + nn.BatchNorm1d(hidden1), + nn.Dropout(0.5), + QuantReLU(act_bit_width=act_bit_width), + QuantLinear(hidden1, hidden2, bias=True, weight_bit_width=weight_bit_width), + nn.BatchNorm1d(hidden2), + nn.Dropout(0.5), + QuantReLU(bit_width=act_bit_width), + QuantLinear(hidden2, hidden3, bias=True, weight_bit_width=weight_bit_width), + nn.BatchNorm1d(hidden3), + nn.Dropout(0.5), + QuantReLU(bit_width=act_bit_width), + QuantLinear(hidden3, num_classes, bias=True, weight_bit_width=weight_bit_width) + ) + + # Load pre-trained weights + assets_dir = pk.resource_filename("finn.qnn-data", "cybsec-mlp/") + trained_state_dict = torch.load(assets_dir+"/state_dict.pth")["models_state_dict"][0] + model.load_state_dict(trained_state_dict, strict=False) + + # Network surgery: pad input size from 593 to 600 and convert bipolar to binary + W_orig = model[0].weight.data.detach().numpy() + W_new = np.pad(W_orig, [(0, 0), (0, 7)]) + model[0].weight.data = torch.from_numpy(W_new) + + model_for_export = CybSecMLPForExport(model) + + # Create directory to save model + os.makedirs("models", exist_ok=True) + ready_model_filename = "models/%s.onnx" % model_name + input_shape = (1, 600) + + # create a QuantTensor instance to mark input as bipolar during export + input_a = np.random.randint(0, 1, size=input_shape).astype(np.float32) + input_a = 2 * input_a - 1 + scale = 1.0 + input_t = torch.from_numpy(input_a * scale) + input_qt = QuantTensor( + input_t, scale=torch.tensor(scale), bit_width=torch.tensor(1.0), signed=True + ) + + # Export to ONNX + bo.export_finn_onnx( + model_for_export, export_path=ready_model_filename, input_t=input_qt + ) + + return ready_model_filename + + \ No newline at end of file diff --git a/build/get-finn.sh b/build/get-finn.sh index 1f9a67c..9708119 100755 --- a/build/get-finn.sh +++ b/build/get-finn.sh @@ -30,7 +30,7 @@ # URL for git repo to be cloned REPO_URL=https://github.com/Xilinx/finn # commit hash for repo -REPO_COMMIT=96c0f5e3678abd7b1eaab2a2b4f8e937ac1f48b8 +REPO_COMMIT=cdc5ec4b0dde59d5d8de0a5359aae529816376af # directory (under the same folder as this script) to clone to REPO_DIR=finn diff --git a/build/kws/README.md b/build/kws/README.md index 9588204..3b5eb4b 100644 --- a/build/kws/README.md +++ b/build/kws/README.md @@ -1,4 +1,4 @@ -# The KWS examplee +# The KWS example The KWS example includes an MLP for the Google SpeechCommandsV2 dataset. @@ -15,7 +15,7 @@ FINN_EXAMPLES=/path/to/finn-examples # cd into finn submodule cd $FINN_EXAMPLES/build/finn # launch the build on the bnn-pynq folder -bash run-docker.sh build_custom /path/to/finn-examples/build/kws +bash run-docker.sh build_custom $FINN_EXAMPLES/build/kws ``` 3. The generated outputs will be under `kws/_output__`. diff --git a/build/kws/build.py b/build/kws/build.py index 2b72de5..63f15c2 100644 --- a/build/kws/build.py +++ b/build/kws/build.py @@ -39,6 +39,7 @@ import datetime from glob import glob import os +import shutil # Inject the preprocessing step into FINN to enable json serialization later on @@ -168,7 +169,7 @@ def step_preprocess(model: ModelWrapper, cfg: DataflowBuildConfig): pre_processed_inputs = pre_processed_inputs.astype(np.int8) # Save data - export_path = "models/" + f_name.replace(".npz", "_{}_len_{}.npy") + export_path = f_name.replace(".npz", "_{}_len_{}.npy") print(f"Saving data to: {export_path}") np.save( export_path.format("inputs", len(pre_processed_inputs)), pre_processed_inputs diff --git a/build/mobilenet-v1/README.md b/build/mobilenet-v1/README.md index 3ff1a92..f31ad57 100644 --- a/build/mobilenet-v1/README.md +++ b/build/mobilenet-v1/README.md @@ -17,7 +17,7 @@ It requires about 2 MB of weight storage and 1.1 GMACs per inference, yielding Due to the depthwise separable convolutions in MobileNet-v1, we use a specialized build script that replaces a few of the standard steps in FINN with custom ones. -**MobileNet-v1 is currently only supported on Alveo U250 and ZCU104.** +**MobileNet-v1 is currently only supported on Alveo U250.** We also provide a folding configuration for the **ZCU102**, but there is no pre-built Pynq image available for this board. 0. Ensure you have performed the *Setup* steps in the top-level README for setting up the FINN requirements and environment variables. @@ -33,7 +33,7 @@ FINN_EXAMPLES=/path/to/finn-examples # cd into finn submodule cd $FINN_EXAMPLES/build/finn # launch the build on the mobilenet-v1 folder -./run-docker.sh build_custom /path/to/finn-examples/build/mobilenet-v1 +./run-docker.sh build_custom $FINN_EXAMPLES/build/mobilenet-v1 ``` 5. The generated outputs will be under `mobilenet-v1/output__`. You can find a description of the generated files [here](https://finn-dev.readthedocs.io/en/latest/command_line.html#simple-dataflow-build-mode). @@ -49,5 +49,5 @@ Subsequently, the trained networks is [exported to ONNX](https://github.com/Xili * A top-K node with k=5 is added at the output (to return the top-5 class indices instead of logits) These modifications are done as part of the end2end MobileNet-v1 test in FINN. -You can [see more here](https://github.com/Xilinx/finn/blob/bf9a67eee6ff5a797ea3a0bd866706d7518c3c6f/tests/end2end/test_end2end_mobilenet_v1.py#L102) +You can [see more here](https://github.com/Xilinx/finn/blob/41740ed1a953c09dd2f87b03ebfde5f9d8a7d4f0/tests/end2end/test_end2end_mobilenet_v1.py#L91) for further reference. diff --git a/build/mobilenet-v1/build.py b/build/mobilenet-v1/build.py index 5c8f58c..8433308 100644 --- a/build/mobilenet-v1/build.py +++ b/build/mobilenet-v1/build.py @@ -44,7 +44,8 @@ model_name = "mobilenetv1-w4a4" # which platforms to build the networks for -zynq_platforms = ["ZCU102", "ZCU104"] +#zynq_platforms = ["ZCU102", "ZCU104"] +zynq_platforms = ["ZCU102"] #alveo_platforms = ["U50", "U200", "U250", "U280"] alveo_platforms = ["U250"] platforms_to_build = zynq_platforms + alveo_platforms diff --git a/build/resnet50/README.md b/build/resnet50/README.md index afd98e3..3eab06f 100644 --- a/build/resnet50/README.md +++ b/build/resnet50/README.md @@ -16,7 +16,7 @@ finn-experimental](https://github.com/Xilinx/finn-experimental). This allows 16x 1. Download the pretrained Resnet 50 ONNX model from the releases page, and extract the zipfile under `resnet50/models`. You should have e.g. `resnet50/models∕resnet50_w1a2_exported.onnx` as a result. -You can use the provided `resnet50/models/download_resnet50.sh` script for this. +You can use the provided `resnet50/models/download-model.sh` script for this. 2. Launch the build as follows: ```SHELL @@ -25,7 +25,7 @@ FINN_EXAMPLES=/path/to/finn-examples # cd into finn submodule cd $FINN_EXAMPLES/build/finn # launch the build on the resnet50 folder -./run-docker.sh build_custom /path/to/finn-examples/build/resnet50 +./run-docker.sh build_custom $FINN_EXAMPLES/build/resnet50 ``` 5. The generated outputs will be under `resnet50/output__`. You can find a description of the generated files [here](https://finn-dev.readthedocs.io/en/latest/command_line.html#simple-dataflow-build-mode). diff --git a/build/resnet50/build.py b/build/resnet50/build.py index d09c26d..ca86cd9 100644 --- a/build/resnet50/build.py +++ b/build/resnet50/build.py @@ -113,7 +113,7 @@ def platform_to_shell(platform): # throughput parameters (auto-folding) mvau_wwidth_max = 24, target_fps = target_fps, - folding_config_file = folding_config_file, + folding_config_file = folding_config_file, # enable extra performance optimizations (physopt) vitis_opt_strategy=build_cfg.VitisOptStrategyCfg.PERFORMANCE_BEST, generate_outputs=[ diff --git a/build/resnet50/custom_steps.py b/build/resnet50/custom_steps.py index c9c2950..8e0b08c 100644 --- a/build/resnet50/custom_steps.py +++ b/build/resnet50/custom_steps.py @@ -107,6 +107,7 @@ from finn.transformation.fpgadataflow.set_fifo_depths import ( InsertAndSetFIFODepths, RemoveShallowFIFOs, + SplitLargeFIFOs, ) from finn.transformation.fpgadataflow.insert_dwc import InsertDWC from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO @@ -265,8 +266,10 @@ def step_resnet50_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig) model = model.transform(GiveReadableTensorNames()) if cfg.folding_config_file is not None: model = model.transform(ApplyConfig(cfg.folding_config_file)) - # remove any shallow FIFOs - model = model.transform(RemoveShallowFIFOs()) + # split large FIFOs into multiple FIFOs + model = model.transform(SplitLargeFIFOs()) + # remove any shallow FIFOs + model = model.transform(RemoveShallowFIFOs()) # extract the final configuration and save it as json hw_attrs = [ diff --git a/build/resnet50/folding_config/U250_folding_config.json b/build/resnet50/folding_config/U250_folding_config.json index 7e096f1..da4f7da 100644 --- a/build/resnet50/folding_config/U250_folding_config.json +++ b/build/resnet50/folding_config/U250_folding_config.json @@ -1,8 +1,8 @@ { "Defaults": { - "outFIFODepth":[32,"all"], - "inFIFODepth":[32,"all"], - "mem_mode":["decoupled",["MatrixVectorActivation"]] + "outFIFODepths":[[32],"all"], + "inFIFODepths":[[32],"all"], + "mem_mode":["decoupled",["MatrixVectorActivation"]] }, "ConvDoublePacked_Batch_0": { "SIMD": 3, @@ -19,7 +19,8 @@ "PE": 64 }, "DuplicateStreams_Batch_0": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "MatrixVectorActivation_1": { "PE": 32, @@ -44,13 +45,15 @@ "SIMD": 32 }, "AddStreams_Batch_0": { - "PE": 32 + "PE": 32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_0": { "PE": 32 }, "DuplicateStreams_Batch_1": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_1": { "PE": 32 @@ -77,13 +80,15 @@ "SIMD": 32 }, "AddStreams_Batch_1": { - "PE": 32 + "PE": 32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_3": { "PE": 32 }, "DuplicateStreams_Batch_2": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_4": { "PE": 32 @@ -110,7 +115,8 @@ "SIMD": 64 }, "AddStreams_Batch_2": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_6": { "PE": 32 @@ -119,7 +125,8 @@ "PE": 32 }, "DuplicateStreams_Batch_3": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "DownSampler_0": { "SIMD": 64 @@ -147,13 +154,15 @@ "SIMD": 32 }, "AddStreams_Batch_3": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_8": { "PE": 32 }, "DuplicateStreams_Batch_4": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_9": { "PE": 32 @@ -180,13 +189,15 @@ "SIMD": 32 }, "AddStreams_Batch_4": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_11": { "PE": 32 }, "DuplicateStreams_Batch_5": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_12": { "PE": 32 @@ -213,13 +224,15 @@ "SIMD": 32 }, "AddStreams_Batch_5": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_14": { "PE": 32 }, "DuplicateStreams_Batch_6": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_15": { "PE": 32 @@ -246,7 +259,8 @@ "SIMD": 32 }, "AddStreams_Batch_6": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_17": { "PE": 32 @@ -255,7 +269,8 @@ "PE": 32 }, "DuplicateStreams_Batch_7": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "DownSampler_1": { "SIMD": 64 @@ -283,13 +298,15 @@ "SIMD": 32 }, "AddStreams_Batch_7": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_19": { "PE": 32 }, "DuplicateStreams_Batch_8": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_20": { "PE": 32 @@ -316,13 +333,15 @@ "SIMD": 32 }, "AddStreams_Batch_8": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_22": { "PE": 32 }, "DuplicateStreams_Batch_9": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_23": { "PE": 32 @@ -349,13 +368,15 @@ "SIMD": 32 }, "AddStreams_Batch_9": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_25": { "PE": 32 }, "DuplicateStreams_Batch_10": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_26": { "PE": 32 @@ -382,13 +403,15 @@ "SIMD": 32 }, "AddStreams_Batch_10": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_28": { "PE": 32 }, "DuplicateStreams_Batch_11": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_29": { "PE": 32 @@ -415,13 +438,15 @@ "SIMD": 32 }, "AddStreams_Batch_11": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_31": { "PE": 32 }, "DuplicateStreams_Batch_12": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_32": { "PE": 32 @@ -448,7 +473,8 @@ "SIMD": 32 }, "AddStreams_Batch_12": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_34": { "PE": 32 @@ -457,7 +483,8 @@ "PE": 32 }, "DuplicateStreams_Batch_13": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "DownSampler_2": { "SIMD": 64 @@ -485,13 +512,15 @@ "SIMD": 32 }, "AddStreams_Batch_13": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_36": { "PE": 32 }, "DuplicateStreams_Batch_14": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_37": { "PE": 32 @@ -518,13 +547,15 @@ "SIMD": 32 }, "AddStreams_Batch_14": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_39": { "PE": 32 }, "DuplicateStreams_Batch_15": { - "PE": 32 + "PE": 32, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_40": { "PE": 32 @@ -551,7 +582,8 @@ "SIMD": 32 }, "AddStreams_Batch_15": { - "PE":32 + "PE":32, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_42": { "PE": 32 diff --git a/build/resnet50/folding_config/U250_folding_config_no_doublepack_pe_folded_16.json b/build/resnet50/folding_config/U250_folding_config_no_doublepack_pe_folded_16.json index 11573aa..09aa2dc 100644 --- a/build/resnet50/folding_config/U250_folding_config_no_doublepack_pe_folded_16.json +++ b/build/resnet50/folding_config/U250_folding_config_no_doublepack_pe_folded_16.json @@ -1,11 +1,11 @@ { "Defaults": { - "outFIFODepth": [ - 32, + "outFIFODepths": [ + [32], "all" ], - "inFIFODepth": [ - 32, + "inFIFODepths": [ + [32], "all" ], "mem_mode": [ @@ -35,7 +35,8 @@ "PE": 4 }, "DuplicateStreams_Batch_0": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "MatrixVectorActivation_2": { "PE": 2, @@ -60,13 +61,15 @@ "SIMD": 32 }, "AddStreams_Batch_0": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_0": { "PE": 2 }, "DuplicateStreams_Batch_1": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_1": { "PE": 2 @@ -93,13 +96,15 @@ "SIMD": 32 }, "AddStreams_Batch_1": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_3": { "PE": 2 }, "DuplicateStreams_Batch_2": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_4": { "PE": 2 @@ -126,7 +131,8 @@ "SIMD": 64 }, "AddStreams_Batch_2": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_6": { "PE": 2 @@ -135,7 +141,8 @@ "PE": 2 }, "DuplicateStreams_Batch_3": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "DownSampler_0": { "SIMD": 4 @@ -163,13 +170,15 @@ "SIMD": 32 }, "AddStreams_Batch_3": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_8": { "PE": 2 }, "DuplicateStreams_Batch_4": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_9": { "PE": 2 @@ -196,13 +205,15 @@ "SIMD": 32 }, "AddStreams_Batch_4": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_11": { "PE": 2 }, "DuplicateStreams_Batch_5": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_12": { "PE": 2 @@ -229,13 +240,15 @@ "SIMD": 32 }, "AddStreams_Batch_5": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_14": { "PE": 2 }, "DuplicateStreams_Batch_6": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_15": { "PE": 2 @@ -262,7 +275,8 @@ "SIMD": 32 }, "AddStreams_Batch_6": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_17": { "PE": 2 @@ -271,7 +285,8 @@ "PE": 2 }, "DuplicateStreams_Batch_7": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "DownSampler_1": { "SIMD": 4 @@ -299,13 +314,15 @@ "SIMD": 32 }, "AddStreams_Batch_7": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_19": { "PE": 2 }, "DuplicateStreams_Batch_8": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_20": { "PE": 2 @@ -332,13 +349,15 @@ "SIMD": 32 }, "AddStreams_Batch_8": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_22": { "PE": 2 }, "DuplicateStreams_Batch_9": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_23": { "PE": 2 @@ -365,13 +384,15 @@ "SIMD": 32 }, "AddStreams_Batch_9": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_25": { "PE": 2 }, "DuplicateStreams_Batch_10": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_26": { "PE": 2 @@ -398,13 +419,15 @@ "SIMD": 32 }, "AddStreams_Batch_10": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_28": { "PE": 2 }, "DuplicateStreams_Batch_11": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_29": { "PE": 2 @@ -431,13 +454,15 @@ "SIMD": 32 }, "AddStreams_Batch_11": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_31": { "PE": 2 }, "DuplicateStreams_Batch_12": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_32": { "PE": 2 @@ -464,7 +489,8 @@ "SIMD": 32 }, "AddStreams_Batch_12": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_34": { "PE": 2 @@ -473,7 +499,8 @@ "PE": 2 }, "DuplicateStreams_Batch_13": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "DownSampler_2": { "SIMD": 4 @@ -501,13 +528,15 @@ "SIMD": 32 }, "AddStreams_Batch_13": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_36": { "PE": 2 }, "DuplicateStreams_Batch_14": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_37": { "PE": 2 @@ -534,13 +563,15 @@ "SIMD": 32 }, "AddStreams_Batch_14": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_39": { "PE": 2 }, "DuplicateStreams_Batch_15": { - "PE": 2 + "PE": 2, + "outFIFODepths": [32, 32] }, "Thresholding_Batch_40": { "PE": 2 @@ -567,7 +598,8 @@ "SIMD": 32 }, "AddStreams_Batch_15": { - "PE": 2 + "PE": 2, + "inFIFODepths": [32, 32] }, "Thresholding_Batch_42": { "PE": 2 diff --git a/docs/img/unsw-nb15.jpg b/docs/img/unsw-nb15.jpg new file mode 100644 index 0000000..6941fe4 Binary files /dev/null and b/docs/img/unsw-nb15.jpg differ diff --git a/finn_examples/bitfiles/bitfiles.zip.link b/finn_examples/bitfiles/bitfiles.zip.link index 9e93b92..c8d3762 100644 --- a/finn_examples/bitfiles/bitfiles.zip.link +++ b/finn_examples/bitfiles/bitfiles.zip.link @@ -1,22 +1,22 @@ { "Pynq-Z1": { - "url": "https://github.com/Xilinx/finn-examples/releases/download/kws/Pynq-Z1.zip", - "md5sum": "bf1df783bae7a1a477797d2eaa61eb9f" - }, - "Pynq-Z2": { - "url": "https://github.com/Xilinx/finn-examples/releases/download/v0.0.1a/Pynq-Z2.zip", - "md5sum": "8b6519073bcc830ca1660dc647dabd79" + "url": "https://github.com/Xilinx/finn-examples/releases/download/v0.0.6/Pynq-Z1.zip", + "md5sum": "54b4691e5195ff10fb0c5339b78d6ea5" }, "Ultra96": { - "url": "https://github.com/Xilinx/finn-examples/releases/download/v0.0.1a/Ultra96.zip", - "md5sum": "59598d7f36ffdc74a0a0262f5b67423c" + "url": "https://github.com/Xilinx/finn-examples/releases/download/v0.0.6/Ultra96.zip", + "md5sum": "a297d1f5657f624ffce16c901f1f3f30" }, "ZCU104": { - "url": "https://github.com/Xilinx/finn-examples/releases/download/radioml/ZCU104.zip", - "md5sum": "9b7edad0511da9cb3c834a289d6797a2" + "url": "https://github.com/Xilinx/finn-examples/releases/download/v0.0.6/ZCU104.zip", + "md5sum": "17dd6ba3d24002275b6622ae0c098378" }, "xilinx_u250_xdma_201830_2": { "url": "https://github.com/Xilinx/finn-examples/releases/download/rn50-u250/xilinx_u250_xdma_201830_2.zip", "md5sum": "042cc5602c8a39d7541f1d79946c0b68" + }, + "xilinx_u250_gen3x16_xdma_2_1_202010_1": { + "url": "https://github.com/Xilinx/finn-examples/releases/download/v0.0.6/xilinx_u250_gen3x16_xdma_2_1_202010_1.zip", + "md5sum": "59a61f233376ab0e340835034db50449" } } diff --git a/finn_examples/driver.py b/finn_examples/driver.py index b28321e..2096760 100644 --- a/finn_examples/driver.py +++ b/finn_examples/driver.py @@ -31,12 +31,9 @@ import time from pynq import Overlay, allocate from pynq.ps import Clocks - from qonnx.core.datatype import DataType -from qonnx.util.basic import ( - gen_finn_dt_tensor, - roundup_to_integer_multiple -) +from qonnx.util.basic import gen_finn_dt_tensor + from finn.util.data_packing import ( finnpy_to_packed_bytearray, packed_bytearray_to_finnpy, @@ -88,24 +85,27 @@ def __init__( self.platform = platform self.batch_size = batch_size self.fclk_mhz = fclk_mhz - if self.platform == "alveo": - if "input_dma_name" in io_shape_dict.keys(): - self.idma = getattr(self, io_shape_dict["input_dma_name"]) - else: - self.idma = self.idma0 - self.odma = self.odma0 - self.odma_handle = None - elif self.platform == "zynq-iodma": - if "input_dma_name" in io_shape_dict.keys(): - self.idma = getattr(self, io_shape_dict["input_dma_name"]) - else: - self.idma = self.idma0 - self.odma = self.odma0 + self.idma = [] + self.odma = [] + self.odma_handle = [] + if "input_dma_name" in io_shape_dict.keys(): + for idma_name in io_shape_dict["input_dma_name"]: + self.idma.append(getattr(self, idma_name)) + else: + self.idma = [self.idma0] + if "output_dma_name" in io_shape_dict.keys(): + for odma_name in io_shape_dict["output_dma_name"]: + self.odma.append(getattr(self, odma_name)) + if self.platform == "alveo": + self.odma_handle.append(None) + else: + self.odma = [self.odma0] + if self.platform == "alveo": + self.odma_handle.append(None) + if self.platform == "zynq-iodma": # set the clock frequency as specified by user during transformations if self.fclk_mhz > 0: Clocks.fclk0_mhz = self.fclk_mhz - else: - raise ValueError("Supported platforms are zynq-iodma alveo") # load any external + runtime weights self.load_external_weights() self.load_runtime_weights() @@ -207,50 +207,50 @@ def load_runtime_weights(self, flush_accel=True, verify=True): # run accelerator to flush any stale weights from weight streamer FIFOs self.execute_on_buffers() - @property - def idt(self): - return self._io_shape_dict["idt"] + def idt(self, ind=0): + return self._io_shape_dict["idt"][ind] - @property - def odt(self): - return self._io_shape_dict["odt"] + def odt(self, ind=0): + return self._io_shape_dict["odt"][ind] - @property - def ishape_normal(self): - ret = list(self._io_shape_dict["ishape_normal"]) + def ishape_normal(self, ind=0): + ret = list(self._io_shape_dict["ishape_normal"][ind]) ret[0] = self.batch_size return tuple(ret) - @property - def oshape_normal(self): - ret = list(self._io_shape_dict["oshape_normal"]) + def oshape_normal(self, ind=0): + ret = list(self._io_shape_dict["oshape_normal"][ind]) ret[0] = self.batch_size return tuple(ret) - @property - def ishape_folded(self): - ret = list(self._io_shape_dict["ishape_folded"]) + def ishape_folded(self, ind=0): + ret = list(self._io_shape_dict["ishape_folded"][ind]) ret[0] = self.batch_size return tuple(ret) - @property - def oshape_folded(self): - ret = list(self._io_shape_dict["oshape_folded"]) + def oshape_folded(self, ind=0): + ret = list(self._io_shape_dict["oshape_folded"][ind]) ret[0] = self.batch_size return tuple(ret) - @property - def ishape_packed(self): - ret = list(self._io_shape_dict["ishape_packed"]) + def ishape_packed(self, ind=0): + ret = list(self._io_shape_dict["ishape_packed"][ind]) ret[0] = self.batch_size return tuple(ret) - @property - def oshape_packed(self): - ret = list(self._io_shape_dict["oshape_packed"]) + def oshape_packed(self, ind=0): + ret = list(self._io_shape_dict["oshape_packed"][ind]) ret[0] = self.batch_size return tuple(ret) + @property + def num_inputs(self): + return self._io_shape_dict["num_inputs"] + + @property + def num_outputs(self): + return self._io_shape_dict["num_outputs"] + @property def batch_size(self): return self._batch_size @@ -264,68 +264,72 @@ def batch_size(self, value): self.ibuf_packed_device = None if self.obuf_packed_device is not None: self.obuf_packed_device = None - if self.platform == "alveo": - self.ibuf_packed_device = allocate(shape=self.ishape_packed, dtype=np.uint8) - self.obuf_packed_device = allocate(shape=self.oshape_packed, dtype=np.uint8) - else: - self.ibuf_packed_device = allocate( - shape=self.ishape_packed, dtype=np.uint8, cacheable=True + cacheable = {"alveo": False, "zynq-iodma": True}[self.platform] + self.ibuf_packed_device = [] + self.obuf_packed_device = [] + self.obuf_packed = [] + for i in range(self.num_inputs): + new_packed_ibuf = allocate( + shape=self.ishape_packed(i), dtype=np.uint8, cacheable=cacheable ) - self.obuf_packed_device = allocate( - shape=self.oshape_packed, dtype=np.uint8, cacheable=True + self.ibuf_packed_device.append(new_packed_ibuf) + for o in range(self.num_outputs): + new_packed_obuf = allocate( + shape=self.oshape_packed(o), dtype=np.uint8, cacheable=cacheable ) - self.obuf_packed = np.empty_like(self.obuf_packed_device) + self.obuf_packed_device.append(new_packed_obuf) + self.obuf_packed.append(np.empty_like(new_packed_obuf)) - def fold_input(self, ibuf_normal): + def fold_input(self, ibuf_normal, ind=0): """Reshapes input in desired shape. Gets input data (ibuf_normal), checks if data is in expected normal shape. Returns folded input.""" # ensure that shape is as expected - assert ibuf_normal.shape == self.ishape_normal + assert ibuf_normal.shape == self.ishape_normal(ind) # convert to folded form - ibuf_folded = ibuf_normal.reshape(self.ishape_folded) + ibuf_folded = ibuf_normal.reshape(self.ishape_folded(ind)) return ibuf_folded - def pack_input(self, ibuf_folded): + def pack_input(self, ibuf_folded, ind=0): """Packs folded input and reverses both SIMD dim and endianness. Gets input data in folded shape and returns packed input data.""" ibuf_packed = finnpy_to_packed_bytearray( ibuf_folded, - self.idt, + self.idt(ind), reverse_endian=True, reverse_inner=True, fast_mode=True, ) return ibuf_packed - def unpack_output(self, obuf_packed): + def unpack_output(self, obuf_packed, ind=0): """Unpacks the packed output buffer from accelerator. Gets packed output and returns output data in folded shape.""" obuf_folded = packed_bytearray_to_finnpy( obuf_packed, - self.odt, - self.oshape_folded, + self.odt(ind), + self.oshape_folded(ind), reverse_endian=True, reverse_inner=True, fast_mode=True, ) return obuf_folded - def unfold_output(self, obuf_folded): + def unfold_output(self, obuf_folded, ind=0): """Unfolds output data to normal shape. Gets folded output data and returns output data in normal shape.""" - obuf_normal = obuf_folded.reshape(self.oshape_normal) + obuf_normal = obuf_folded.reshape(self.oshape_normal(ind)) return obuf_normal - def copy_input_data_to_device(self, data): + def copy_input_data_to_device(self, data, ind=0): """Copies given input data to PYNQ buffer.""" - np.copyto(self.ibuf_packed_device, data) - self.ibuf_packed_device.flush() + np.copyto(self.ibuf_packed_device[ind], data) + self.ibuf_packed_device[ind].flush() - def copy_output_data_from_device(self, data): + def copy_output_data_from_device(self, data, ind=0): """Copies PYNQ output buffer from device.""" - self.obuf_packed_device.invalidate() - np.copyto(data, self.obuf_packed_device) + self.obuf_packed_device[ind].invalidate() + np.copyto(data, self.obuf_packed_device[ind]) def execute_on_buffers(self, asynch=False, batch_size=None): """Executes accelerator by setting up the DMA(s) on pre-allocated buffers. @@ -341,24 +345,36 @@ def execute_on_buffers(self, asynch=False, batch_size=None): batch_size = self.batch_size assert batch_size <= self.batch_size, "Specified batch_size is too large." if self.platform == "zynq-iodma": - assert self.odma.read(0x00) & 0x4 != 0, "Output DMA is not idle" + for o in range(self.num_outputs): + assert ( + self.odma[o].read(0x00) & 0x4 != 0 + ), "Output DMA %d is not idle" % (o) # manually launch IODMAs since signatures are missing for iwdma, iwbuf, iwdma_name in self.external_weights: iwdma.write(0x10, iwbuf.device_address) iwdma.write(0x1C, batch_size) iwdma.write(0x00, 1) - self.idma.write(0x10, self.ibuf_packed_device.device_address) - self.idma.write(0x1C, batch_size) - self.odma.write(0x10, self.obuf_packed_device.device_address) - self.odma.write(0x1C, batch_size) - self.idma.write(0x00, 1) - self.odma.write(0x00, 1) + for o in range(self.num_outputs): + self.odma[o].write(0x10, self.obuf_packed_device[o].device_address) + self.odma[o].write(0x1C, batch_size) + self.odma[o].write(0x00, 1) + for i in range(self.num_inputs): + self.idma[i].write(0x10, self.ibuf_packed_device[i].device_address) + self.idma[i].write(0x1C, batch_size) + self.idma[i].write(0x00, 1) elif self.platform == "alveo": - assert self.odma_handle is None, "Output DMA is already running" - self.idma.start(self.ibuf_packed_device, batch_size) + for o in range(self.num_outputs): + assert self.odma_handle[o] is None, ( + "Output DMA %d is already running" % o + ) + for i in range(self.num_inputs): + self.idma[i].start(self.ibuf_packed_device[i], batch_size) for iwdma, iwbuf, iwdma_name in self.external_weights: iwdma.start(iwbuf, batch_size) - self.odma_handle = self.odma.start(self.obuf_packed_device, batch_size) + for o in range(self.num_outputs): + self.odma_handle[o] = self.odma[o].start( + self.obuf_packed_device[o], batch_size + ) else: raise Exception("Unrecognized platform: %s" % self.platform) # blocking behavior depends on asynch parameter @@ -366,31 +382,48 @@ def execute_on_buffers(self, asynch=False, batch_size=None): self.wait_until_finished() def wait_until_finished(self): - "Block until the output DMA has finished writing." + "Block until all output DMAs have finished writing." if self.platform == "zynq-iodma": # check if output IODMA is finished via register reads - status = self.odma.read(0x00) - while status & 0x2 == 0: - status = self.odma.read(0x00) + for o in range(self.num_outputs): + status = self.odma[o].read(0x00) + while status & 0x2 == 0: + status = self.odma[o].read(0x00) elif self.platform == "alveo": - assert self.odma_handle is not None, "No odma_handle to wait on" - self.odma_handle.wait() - self.odma_handle = None + assert all( + [x is not None for x in self.odma_handle] + ), "No odma_handle to wait on" + for o in range(self.num_outputs): + self.odma_handle[o].wait() + self.odma_handle[o] = None else: raise Exception("Unrecognized platform: %s" % self.platform) def execute(self, input_npy): - """Given input numpy array, first perform necessary packing and copying - to device buffers, execute on accelerator, then unpack output and return - output numpy array from accelerator.""" - ibuf_folded = self.fold_input(input_npy) - ibuf_packed = self.pack_input(ibuf_folded) - self.copy_input_data_to_device(ibuf_packed) + """Given a single or a list of input numpy array, first perform necessary + packing and copying to device buffers, execute on accelerator, then unpack + output and return output numpy array from accelerator.""" + # if single input, convert to list to normalize how we process the input + if not type(input_npy) is list: + input_npy = [input_npy] + assert self.num_inputs == len( + input_npy + ), "Not all accelerator inputs are specified." + for i in range(self.num_inputs): + ibuf_folded = self.fold_input(input_npy[i], ind=i) + ibuf_packed = self.pack_input(ibuf_folded, ind=i) + self.copy_input_data_to_device(ibuf_packed, ind=i) self.execute_on_buffers() - self.copy_output_data_from_device(self.obuf_packed) - obuf_folded = self.unpack_output(self.obuf_packed) - obuf_normal = self.unfold_output(obuf_folded) - return obuf_normal + outputs = [] + for o in range(self.num_outputs): + self.copy_output_data_from_device(self.obuf_packed[o], ind=o) + obuf_folded = self.unpack_output(self.obuf_packed[o], ind=o) + obuf_normal = self.unfold_output(obuf_folded, ind=o) + outputs.append(obuf_normal) + if self.num_outputs == 1: + return outputs[0] + else: + return outputs def throughput_test(self): """Run accelerator with empty inputs to measure throughput and other metrics. @@ -403,19 +436,17 @@ def throughput_test(self): runtime = end - start res["runtime[ms]"] = runtime * 1000 res["throughput[images/s]"] = self.batch_size / runtime - # the _packed arrays always consist of bytes so no need to - # take i/o bitwidths into account (which is baked into the shape) - res["DRAM_in_bandwidth[MB/s]"] = ( - np.prod(self.ishape_packed) * 0.000001 / runtime - ) - res["DRAM_out_bandwidth[MB/s]"] = ( - np.prod(self.oshape_packed) * 0.000001 / runtime - ) + total_in = 0 + for i in range(self.num_inputs): + total_in += np.prod(self.ishape_packed(i)) + res["DRAM_in_bandwidth[MB/s]"] = total_in * 0.000001 / runtime + total_out = 0 + for o in range(self.num_outputs): + total_out += np.prod(self.oshape_packed(o)) + res["DRAM_out_bandwidth[MB/s]"] = total_out * 0.000001 / runtime for iwdma, iwbuf, iwdma_name in self.external_weights: - # Bit-width of the elements of the array are always 8-bit; - # see function load_external_weights res["DRAM_extw_%s_bandwidth[MB/s]" % iwdma_name] = ( - self.batch_size * np.prod(iwbuf.shape) * 0.000001 / runtime * 8 + self.batch_size * np.prod(iwbuf.shape) * 0.000001 / runtime ) if self.platform == "zynq-iodma": res["fclk[mhz]"] = Clocks.fclk0_mhz @@ -423,11 +454,11 @@ def throughput_test(self): res["fclk[mhz]"] = self.clock_dict["clock0"]["frequency"] res["batch_size"] = self.batch_size # also benchmark driver-related overheads - input_npy = gen_finn_dt_tensor(self.idt, self.ishape_normal) + input_npy = gen_finn_dt_tensor(self.idt(), self.ishape_normal()) # provide as int8/uint8 to support fast packing path where possible - if self.idt == DataType["UINT8"]: + if self.idt() == DataType["UINT8"]: input_npy = input_npy.astype(np.uint8) - elif self.idt == DataType["INT8"]: + elif self.idt() == DataType["INT8"]: input_npy = input_npy.astype(np.int8) start = time.time() ibuf_folded = self.fold_input(input_npy) @@ -448,13 +479,13 @@ def throughput_test(self): res["copy_input_data_to_device[ms]"] = runtime * 1000 start = time.time() - self.copy_output_data_from_device(self.obuf_packed) + self.copy_output_data_from_device(self.obuf_packed[0]) end = time.time() runtime = end - start res["copy_output_data_from_device[ms]"] = runtime * 1000 start = time.time() - obuf_folded = self.unpack_output(self.obuf_packed) + obuf_folded = self.unpack_output(self.obuf_packed[0]) end = time.time() runtime = end - start res["unpack_output[ms]"] = runtime * 1000 diff --git a/finn_examples/models.py b/finn_examples/models.py index fa83010..09264af 100644 --- a/finn_examples/models.py +++ b/finn_examples/models.py @@ -36,85 +36,133 @@ from finn_examples.driver import FINNExampleOverlay _mnist_fc_io_shape_dict = { - "idt": DataType["UINT8"], - "odt": DataType["UINT8"], - "ishape_normal": (1, 784), - "oshape_normal": (1, 1), - "ishape_folded": (1, 1, 784), - "oshape_folded": (1, 1, 1), - "ishape_packed": (1, 1, 784), - "oshape_packed": (1, 1, 1), + "idt" : [DataType['UINT8']], + "odt" : [DataType['UINT8']], + "ishape_normal" : [(1, 784)], + "oshape_normal" : [(1, 1)], + "ishape_folded" : [(1, 16, 49)], + "oshape_folded" : [(1, 1, 1)], + "ishape_packed" : [(1, 16, 49)], + "oshape_packed" : [(1, 1, 1)], + "input_dma_name" : ['idma0'], + "output_dma_name" : ['odma0'], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, } _cifar10_cnv_io_shape_dict = { - "idt": DataType["UINT8"], - "odt": DataType["UINT8"], - "ishape_normal": (1, 32, 32, 3), - "oshape_normal": (1, 1), - "ishape_folded": (1, 1, 32, 32, 1, 3), - "oshape_folded": (1, 1, 1), - "ishape_packed": (1, 1, 32, 32, 1, 3), - "oshape_packed": (1, 1, 1), + "idt" : [DataType['UINT8']], + "odt" : [DataType['UINT8']], + "ishape_normal" : [(1, 32, 32, 3)], + "oshape_normal" : [(1, 1)], + "ishape_folded" : [(1, 32, 32, 3, 1)], + "oshape_folded" : [(1, 1, 1)], + "ishape_packed" : [(1, 32, 32, 3, 1)], + "oshape_packed" : [(1, 1, 1)], + "input_dma_name" : ['idma0'], + "output_dma_name" : ['odma0'], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, } _bincop_cnv_io_shape_dict = { - "idt": DataType["UINT8"], - "odt": DataType["UINT8"], - "ishape_normal": (1, 72, 72, 3), - "oshape_normal": (1, 1), - "ishape_folded": (1, 1, 72, 72, 1, 3), - "oshape_folded": (1, 1, 1), - "ishape_packed": (1, 1, 72, 72, 1, 3), - "oshape_packed": (1, 1, 1), + "idt": [DataType["UINT8"]], + "odt": [DataType["UINT8"]], + "ishape_normal": [(1, 72, 72, 3)], + "oshape_normal": [(1, 1)], + "ishape_folded": [(1, 1, 72, 72, 1, 3)], + "oshape_folded": [(1, 1, 1)], + "ishape_packed": [(1, 1, 72, 72, 1, 3)], + "oshape_packed": [(1, 1, 1)], + "input_dma_name" : ["idma0"], + "output_dma_name" : ["odma0"], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, } _imagenet_top5inds_io_shape_dict = { - "idt": DataType["UINT8"], - "odt": DataType["UINT16"], - "ishape_normal": (1, 224, 224, 3), - "oshape_normal": (1, 1, 1, 5), - "ishape_folded": (1, 224, 224, 1, 3), - "oshape_folded": (1, 1, 1, 1, 5), - "ishape_packed": (1, 224, 224, 1, 3), - "oshape_packed": (1, 1, 1, 1, 10), + "idt" : [DataType['UINT8']], + "odt" : [DataType['UINT16']], + "ishape_normal" : [(1, 224, 224, 3)], + "oshape_normal" : [(1, 1, 1, 5)], + "ishape_folded" : [(1, 224, 224, 3, 1)], + "oshape_folded" : [(1, 1, 1, 5, 1)], + "ishape_packed" : [(1, 224, 224, 3, 1)], + "oshape_packed" : [(1, 1, 1, 5, 2)], + "input_dma_name" : ['idma0'], + "output_dma_name" : ['odma0'], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, } # resnet50 uses a different io_shape_dict due to # external weights for last layer _imagenet_resnet50_top5inds_io_shape_dict = { - "idt": DataType["UINT8"], - "odt": DataType["UINT16"], - "ishape_normal": (1, 224, 224, 3), - "oshape_normal": (1, 5), - "ishape_folded": (1, 224, 224, 3), - "oshape_folded": (1, 5, 1), - "ishape_packed": (1, 224, 224, 3), - "oshape_packed": (1, 5, 2), - "input_dma_name": "idma1", + "idt": [DataType["UINT8"]], + "odt": [DataType["UINT16"]], + "ishape_normal": [(1, 224, 224, 3)], + "oshape_normal": [(1, 5)], + "ishape_folded": [(1, 224, 224, 3)], + "oshape_folded": [(1, 5, 1)], + "ishape_packed": [(1, 224, 224, 3)], + "oshape_packed": [(1, 5, 2)], + "input_dma_name" : ['idma1'], + "output_dma_name" : ['odma1'], "number_of_external_weights": 1, + "num_inputs" : 1, + "num_outputs" : 1, } _radioml_io_shape_dict = { - "idt": DataType["INT8"], - "odt": DataType["UINT8"], - "ishape_normal": (1, 1024, 1, 2), - "oshape_normal": (1, 1), - "ishape_folded": (1, 1024, 1, 1, 2), - "oshape_folded": (1, 1, 1), - "ishape_packed": (1, 1024, 1, 1, 2), - "oshape_packed": (1, 1, 1), + "idt" : [DataType['INT8']], + "odt" : [DataType['UINT8']], + "ishape_normal" : [(1, 1024, 1, 2)], + "oshape_normal" : [(1, 1)], + "ishape_folded" : [(1, 1024, 1, 1, 2)], + "oshape_folded" : [(1, 1, 1)], + "ishape_packed" : [(1, 1024, 1, 1, 2)], + "oshape_packed" : [(1, 1, 1)], + "input_dma_name" : ['idma0'], + "output_dma_name" : ['odma0'], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, } _gscv2_mlp_io_shape_dict = { - "idt" : DataType["INT8"], - "odt" : DataType["UINT8"], - "ishape_normal" : (1, 490), - "oshape_normal" : (1, 1), - "ishape_folded" : (1, 49, 10), - "oshape_folded" : (1, 1, 1), - "ishape_packed" : (1, 49, 10), - "oshape_packed" : (1, 1, 1), - "input_dma_name" : 'idma0', + "idt" : [DataType['INT8']], + "odt" : [DataType['UINT8']], + "ishape_normal" : [(1, 490)], + "oshape_normal" : [(1, 1)], + "ishape_folded" : [(1, 49, 10)], + "oshape_folded" : [(1, 1, 1)], + "ishape_packed" : [(1, 49, 10)], + "oshape_packed" : [(1, 1, 1)], + "input_dma_name" : ['idma0'], + "output_dma_name" : ['odma0'], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, +} + +_unsw_nb15_mlp_io_shape_dict = { + "idt" : [DataType['BIPOLAR']], + "odt" : [DataType['BIPOLAR']], + "ishape_normal" : [(1, 600)], + "oshape_normal" : [(1, 1)], + "ishape_folded" : [(1, 15, 40)], + "oshape_folded" : [(1, 1, 1)], + "ishape_packed" : [(1, 15, 5)], + "oshape_packed" : [(1, 1, 1)], + "input_dma_name" : ['idma0'], + "output_dma_name" : ['odma0'], + "number_of_external_weights": 0, + "num_inputs" : 1, + "num_outputs" : 1, } # from https://github.com/Xilinx/PYNQ-HelloWorld/blob/master/setup.py @@ -296,3 +344,17 @@ def vgg10_w4a4_radioml(target_platform=None): _radioml_io_shape_dict, fclk_mhz=fclk_mhz, ) + + +def mlp_w2a2_unsw_nb15(target_platform=None): + target_platform = resolve_target_platform(target_platform) + driver_mode = get_driver_mode() + model_name = "unsw_nb15-mlp-w2a2" + filename = find_bitfile(model_name, target_platform) + fclk_mhz = 100.0 + return FINNExampleOverlay( + filename, + driver_mode, + _unsw_nb15_mlp_io_shape_dict, + fclk_mhz=fclk_mhz + ) diff --git a/finn_examples/notebooks/0_mnist_with_fc_networks.ipynb b/finn_examples/notebooks/0_mnist_with_fc_networks.ipynb index a9d6cb7..0c140ce 100644 --- a/finn_examples/notebooks/0_mnist_with_fc_networks.ipynb +++ b/finn_examples/notebooks/0_mnist_with_fc_networks.ipynb @@ -2,91 +2,88 @@ "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "# Initialize the accelerator" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 1, - "source": [ - "from finn_examples import models\n", - "print(list(filter(lambda x: \"mnist\" in x, dir(models))))" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "['_mnist_fc_io_shape_dict', 'tfc_w1a1_mnist', 'tfc_w1a2_mnist', 'tfc_w2a2_mnist']\n" ] } ], - "metadata": {} + "source": [ + "from finn_examples import models\n", + "print(list(filter(lambda x: \"mnist\" in x, dir(models))))" + ] }, { "cell_type": "code", "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ "accel = models.tfc_w1a1_mnist()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 3, - "source": [ - "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal), str(accel.idt)))\n", - "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal), str(accel.odt)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Expected input shape and datatype: (1, 784) DataType.UINT8\n", "Expected output shape and datatype: (1, 1) DataType.UINT8\n" ] } ], - "metadata": {} + "source": [ + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Load the MNIST dataset\n", "\n", "Use the `dataset_loading` package to get easy Python access to MNIST dataset:" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 4, - "source": [ - "from dataset_loading import mnist\n", - "trainx, trainy, testx, testy, valx, valy = mnist.load_mnist_data(\"/tmp\", download=True, one_hot=False)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Looking for Train Imgs\n", "Download URL: http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz\n", @@ -107,158 +104,172 @@ ] } ], - "metadata": {} + "source": [ + "from dataset_loading import mnist\n", + "trainx, trainy, testx, testy, valx, valy = mnist.load_mnist_data(\"/tmp\", download=True, one_hot=False)" + ] }, { "cell_type": "code", "execution_count": 5, - "source": [ - "testx.shape" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "(10000, 28, 28, 1)" ] }, + "execution_count": 5, "metadata": {}, - "execution_count": 5 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "testx.shape" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Classify a single image" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 6, + "metadata": {}, + "outputs": [], "source": [ "test_single_x = testx[0].reshape(28, 28)\n", "test_single_y = testy[0]" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 8, - "source": [ - "%matplotlib inline\n", - "from matplotlib import pyplot as plt\n", - "\n", - "plt.imshow(test_single_x, cmap='gray')\n", - "plt.show()" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "image/png": "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", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "%matplotlib inline\n", + "from matplotlib import pyplot as plt\n", + "\n", + "plt.imshow(test_single_x, cmap='gray')\n", + "plt.show()" + ] }, { "cell_type": "code", "execution_count": 9, - "source": [ - "print(\"Expected class is %d\" % test_single_y)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Expected class is 7\n" ] } ], - "metadata": {} + "source": [ + "print(\"Expected class is %d\" % test_single_y)" + ] }, { "cell_type": "code", "execution_count": 10, - "source": [ - "accel_in = test_single_x.reshape(accel.ishape_normal)\n", - "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Input buffer shape is (1, 784) and datatype is uint8\n" ] } ], - "metadata": {} + "source": [ + "accel_in = test_single_x.reshape(accel.ishape_normal())\n", + "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" + ] }, { "cell_type": "code", "execution_count": 11, + "metadata": {}, + "outputs": [], "source": [ "accel_out = accel.execute(accel_in)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 12, - "source": [ - "print(\"Returned class is %d\" % accel_out)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is 7\n" ] } ], - "metadata": {} + "source": [ + "print(\"Returned class is %d\" % accel_out)" + ] }, { "cell_type": "code", "execution_count": 13, - "source": [ - "%%timeit\n", - "accel_out = accel.execute(accel_in)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "1000 loops, best of 3: 808 µs per loop\n" ] } ], - "metadata": {} + "source": [ + "%%timeit\n", + "accel_out = accel.execute(accel_in)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Validate accuracy on entire MNIST test set" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ready to run validation, test images tensor has shape (10, 1000, 784)\n", + "Accelerator buffer shapes are (1000, 1, 784) for input, (1000, 1, 1) for output\n" + ] + } + ], "source": [ "import numpy as np\n", "batch_size = 1000\n", @@ -270,39 +281,17 @@ "batch_labels = testy.reshape(n_batches, batch_size)\n", "obuf_normal = np.empty_like(accel.obuf_packed_device)\n", "print(\"Ready to run validation, test images tensor has shape %s\" % str(batch_imgs.shape))\n", - "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed), str(accel.oshape_packed)) )" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Ready to run validation, test images tensor has shape (10, 1000, 784)\n", - "Accelerator buffer shapes are (1000, 1, 784) for input, (1000, 1, 1) for output\n" - ] - } - ], - "metadata": {} + "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed()), str(accel.oshape_packed())) )" + ] }, { "cell_type": "code", "execution_count": 22, - "source": [ - "ok = 0\n", - "nok = 0\n", - "for i in range(n_batches):\n", - " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal)\n", - " exp = batch_labels[i]\n", - " obuf_normal = accel.execute(ibuf_normal)\n", - " ret = np.bincount(obuf_normal.flatten() == exp.flatten())\n", - " nok += ret[0]\n", - " ok += ret[1]\n", - " print(\"batch %d / %d : total OK %d NOK %d\" % (i, n_batches, ok, nok))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "batch 0 / 10 : total OK 913 NOK 87\n", "batch 1 / 10 : total OK 1800 NOK 200\n", @@ -317,89 +306,97 @@ ] } ], - "metadata": {} + "source": [ + "ok = 0\n", + "nok = 0\n", + "for i in range(n_batches):\n", + " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal())\n", + " exp = batch_labels[i]\n", + " obuf_normal = accel.execute(ibuf_normal)\n", + " ret = np.bincount(obuf_normal.flatten() == exp.flatten())\n", + " nok += ret[0]\n", + " ok += ret[1]\n", + " print(\"batch %d / %d : total OK %d NOK %d\" % (i, n_batches, ok, nok))" + ] }, { "cell_type": "code", "execution_count": 23, - "source": [ - "acc = 100.0 * ok / (total)\n", - "print(\"Final accuracy: {}%\".format(acc))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Final accuracy: 92.96%\n" ] } ], - "metadata": {} + "source": [ + "acc = 100.0 * ok / (total)\n", + "print(\"Final accuracy: {}%\".format(acc))" + ] }, { "cell_type": "code", "execution_count": 26, + "metadata": {}, + "outputs": [], "source": [ "def run_validation():\n", " for i in range(n_batches):\n", - " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal)\n", + " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal())\n", " exp = batch_labels[i]\n", " accel.execute(ibuf_normal)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 27, - "source": [ - "full_validation_time = %timeit -n 5 -o run_validation()" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "5 loops, best of 3: 22.6 ms per loop\n" ] } ], - "metadata": {} + "source": [ + "full_validation_time = %timeit -n 5 -o run_validation()" + ] }, { "cell_type": "code", "execution_count": 28, - "source": [ - "print(\"%f images per second including data movement\" % (total / float(full_validation_time.best)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "441567.114603 images per second including data movement\n" ] } ], - "metadata": {} + "source": [ + "print(\"%f images per second including data movement\" % (total / float(full_validation_time.best)))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Run some more built-in benchmarks" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 29, - "source": [ - "accel.throughput_test()" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "{'DRAM_in_bandwidth[Mb/s]': 656.2231762123328,\n", @@ -416,18 +413,21 @@ " 'unpack_output[ms]': 0.0006036758422851562}" ] }, + "execution_count": 29, "metadata": {}, - "execution_count": 29 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "accel.throughput_test()" + ] }, { "cell_type": "code", "execution_count": null, - "source": [], + "metadata": {}, "outputs": [], - "metadata": {} + "source": [] } ], "metadata": { @@ -451,4 +451,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/finn_examples/notebooks/1_cifar10_with_cnv_networks.ipynb b/finn_examples/notebooks/1_cifar10_with_cnv_networks.ipynb index af305ff..39ade56 100644 --- a/finn_examples/notebooks/1_cifar10_with_cnv_networks.ipynb +++ b/finn_examples/notebooks/1_cifar10_with_cnv_networks.ipynb @@ -2,91 +2,99 @@ "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "# Initialize the accelerator" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 1, - "source": [ - "from finn_examples import models\n", - "print(list(filter(lambda x: \"cifar10\" in x, dir(models))))" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "['_cifar10_cnv_io_shape_dict', 'cnv_w1a1_cifar10', 'cnv_w1a2_cifar10', 'cnv_w2a2_cifar10']\n" ] } ], - "metadata": {} + "source": [ + "from finn_examples import models\n", + "print(list(filter(lambda x: \"cifar10\" in x, dir(models))))" + ] }, { "cell_type": "code", "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ "accel = models.cnv_w1a1_cifar10()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 3, - "source": [ - "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal), str(accel.idt)))\n", - "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal), str(accel.odt)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Expected input shape and datatype: (1, 32, 32, 3) DataType.UINT8\n", "Expected output shape and datatype: (1, 1) DataType.UINT8\n" ] } ], - "metadata": {} + "source": [ + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Load the CIFAR-10 dataset\n", "\n", "Use the `dataset_loading` package to get easy Python access to CIFAR-10 dataset:" - ], - "metadata": {} + ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ - "from dataset_loading import cifar\n", - "trainx, trainy, testx, testy, valx, valy = cifar.load_cifar_data(\"/tmp\", download=True, one_hot=False)" - ], + "# Uncomment the following lines (to disable SSL verification) if you encounter an SSLCertVerificationError when downloading the cifar-10 dataset.\n", + "#import ssl\n", + "#ssl._create_default_https_context = ssl._create_unverified_context" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Tar File found in dest_dir. Not Downloading again\n", "Extracting Python CIFAR10 data.\n", @@ -94,159 +102,173 @@ ] } ], - "metadata": {} + "source": [ + "from dataset_loading import cifar\n", + "trainx, trainy, testx, testy, valx, valy = cifar.load_cifar_data(\"/tmp\", download=True, one_hot=False)" + ] }, { "cell_type": "code", "execution_count": 6, - "source": [ - "testx.shape" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "(10000, 32, 32, 3)" ] }, + "execution_count": 6, "metadata": {}, - "execution_count": 6 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "testx.shape" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Classify a single image" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 7, + "metadata": {}, + "outputs": [], "source": [ "test_single_x = testx[0]\n", "test_single_y = testy[0]\n", "cifar10_class_names = ['Airplane', 'Automobile', 'Bird', 'Cat', 'Deer', 'Dog', 'Frog', 'Horse', 'Ship', 'Truck']\n" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 9, - "source": [ - "%matplotlib inline\n", - "from matplotlib import pyplot as plt\n", - "\n", - "plt.imshow(test_single_x)\n", - "plt.show()" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "image/png": "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", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "%matplotlib inline\n", + "from matplotlib import pyplot as plt\n", + "\n", + "plt.imshow(test_single_x)\n", + "plt.show()" + ] }, { "cell_type": "code", "execution_count": 10, - "source": [ - "print(\"Expected class is %d (%s)\" % (test_single_y, cifar10_class_names[test_single_y]))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Expected class is 3 (Cat)\n" ] } ], - "metadata": {} + "source": [ + "print(\"Expected class is %d (%s)\" % (test_single_y, cifar10_class_names[test_single_y]))" + ] }, { "cell_type": "code", "execution_count": 11, - "source": [ - "accel_in = test_single_x.reshape(accel.ishape_normal)\n", - "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Input buffer shape is (1, 32, 32, 3) and datatype is uint8\n" ] } ], - "metadata": {} + "source": [ + "accel_in = test_single_x.reshape(accel.ishape_normal())\n", + "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" + ] }, { "cell_type": "code", "execution_count": 12, + "metadata": {}, + "outputs": [], "source": [ "accel_out = accel.execute(accel_in)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 13, - "source": [ - "print(\"Returned class is %d\" % accel_out)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is 3\n" ] } ], - "metadata": {} + "source": [ + "print(\"Returned class is %d\" % accel_out)" + ] }, { "cell_type": "code", "execution_count": 14, - "source": [ - "%%timeit\n", - "accel_out = accel.execute(accel_in)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "100 loops, best of 3: 2.34 ms per loop\n" ] } ], - "metadata": {} + "source": [ + "%%timeit\n", + "accel_out = accel.execute(accel_in)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Validate accuracy on entire CIFAR-10 test set" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ready to run validation, test images tensor has shape (10, 1000, 3072)\n", + "Accelerator buffer shapes are (1000, 1, 32, 32, 1, 3) for input, (1000, 1, 1) for output\n" + ] + } + ], "source": [ "import numpy as np\n", "\n", @@ -259,39 +281,17 @@ "batch_labels = testy.reshape(n_batches, batch_size)\n", "obuf_normal = np.empty_like(accel.obuf_packed_device)\n", "print(\"Ready to run validation, test images tensor has shape %s\" % str(batch_imgs.shape))\n", - "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed), str(accel.oshape_packed)) )" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Ready to run validation, test images tensor has shape (10, 1000, 3072)\n", - "Accelerator buffer shapes are (1000, 1, 32, 32, 1, 3) for input, (1000, 1, 1) for output\n" - ] - } - ], - "metadata": {} + "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed()), str(accel.oshape_packed())))" + ] }, { "cell_type": "code", "execution_count": 16, - "source": [ - "ok = 0\n", - "nok = 0\n", - "for i in range(n_batches):\n", - " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal)\n", - " exp = batch_labels[i]\n", - " obuf_normal = accel.execute(ibuf_normal)\n", - " ret = np.bincount(obuf_normal.flatten() == exp.flatten())\n", - " nok += ret[0]\n", - " ok += ret[1]\n", - " print(\"batch %d / %d : total OK %d NOK %d\" % (i, n_batches, ok, nok))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "batch 0 / 10 : total OK 851 NOK 149\n", "batch 1 / 10 : total OK 1683 NOK 317\n", @@ -306,89 +306,97 @@ ] } ], - "metadata": {} + "source": [ + "ok = 0\n", + "nok = 0\n", + "for i in range(n_batches):\n", + " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal())\n", + " exp = batch_labels[i]\n", + " obuf_normal = accel.execute(ibuf_normal)\n", + " ret = np.bincount(obuf_normal.flatten() == exp.flatten())\n", + " nok += ret[0]\n", + " ok += ret[1]\n", + " print(\"batch %d / %d : total OK %d NOK %d\" % (i, n_batches, ok, nok))" + ] }, { "cell_type": "code", "execution_count": 17, - "source": [ - "acc = 100.0 * ok / (total)\n", - "print(\"Final accuracy: {}%\".format(acc))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Final accuracy: 84.19%\n" ] } ], - "metadata": {} + "source": [ + "acc = 100.0 * ok / (total)\n", + "print(\"Final accuracy: {}%\".format(acc))" + ] }, { "cell_type": "code", "execution_count": 18, + "metadata": {}, + "outputs": [], "source": [ "def run_validation():\n", " for i in range(n_batches):\n", - " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal)\n", + " ibuf_normal = batch_imgs[i].reshape(accel.ishape_normal())\n", " exp = batch_labels[i]\n", " accel.execute(ibuf_normal)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 20, - "source": [ - "full_validation_time = %timeit -n 1 -o run_validation()" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "1 loop, best of 3: 3.34 s per loop\n" ] } ], - "metadata": {} + "source": [ + "full_validation_time = %timeit -n 1 -o run_validation()" + ] }, { "cell_type": "code", "execution_count": 21, - "source": [ - "print(\"%f images per second including data movement\" % (total / float(full_validation_time.best)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "2995.076851 images per second including data movement\n" ] } ], - "metadata": {} + "source": [ + "print(\"%f images per second including data movement\" % (total / float(full_validation_time.best)))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## More benchmarking" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 23, - "source": [ - "accel.throughput_test()" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "{'DRAM_in_bandwidth[Mb/s]': 9.278965852281484,\n", @@ -405,18 +413,21 @@ " 'unpack_output[ms]': 0.0006213188171386719}" ] }, + "execution_count": 23, "metadata": {}, - "execution_count": 23 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "accel.throughput_test()" + ] }, { "cell_type": "code", "execution_count": null, - "source": [], + "metadata": {}, "outputs": [], - "metadata": {} + "source": [] } ], "metadata": { @@ -440,4 +451,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/finn_examples/notebooks/2_imagenet_with_cnns.ipynb b/finn_examples/notebooks/2_imagenet_with_cnns.ipynb index 04a80b1..7e653ad 100755 --- a/finn_examples/notebooks/2_imagenet_with_cnns.ipynb +++ b/finn_examples/notebooks/2_imagenet_with_cnns.ipynb @@ -68,8 +68,8 @@ } ], "source": [ - "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal), str(accel.idt)))\n", - "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal), str(accel.odt)))" + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" ] }, { @@ -226,7 +226,7 @@ } ], "source": [ - "accel_in = test_single_x.reshape(accel.ishape_normal)\n", + "accel_in = test_single_x.reshape(accel.ishape_normal())\n", "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" ] }, @@ -297,7 +297,7 @@ "source": [ "batch_size = 100\n", "accel.batch_size = batch_size\n", - "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed), str(accel.oshape_packed)) )" + "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed()), str(accel.oshape_packed())))" ] }, { @@ -837,7 +837,7 @@ " imgs = np.array(imgs)\n", " exp = np.array(lbls)\n", " \n", - " ibuf_normal = imgs.reshape(accel.ishape_normal)\n", + " ibuf_normal = imgs.reshape(accel.ishape_normal())\n", " obuf_normal = accel.execute(ibuf_normal)\n", " obuf_normal = obuf_normal.reshape(batch_size, -1)[:,0]\n", " ret = np.bincount(obuf_normal.flatten() == exp.flatten())\n", diff --git a/finn_examples/notebooks/3_binarycop_mask_detection.ipynb b/finn_examples/notebooks/3_binarycop_mask_detection.ipynb index 15688c1..43f490f 100644 --- a/finn_examples/notebooks/3_binarycop_mask_detection.ipynb +++ b/finn_examples/notebooks/3_binarycop_mask_detection.ipynb @@ -3,74 +3,76 @@ { "cell_type": "code", "execution_count": 1, - "source": [ - "from finn_examples import models\n", - "import os\n", - "from PIL import Image\n", - "import numpy as np\n", - "import cv2" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "from finn_examples import models\n", + "import os\n", + "from PIL import Image\n", + "import numpy as np\n", + "import cv2" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Initialize the Accelerator" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 2, + "metadata": { + "scrolled": true + }, + "outputs": [], "source": [ "# Note: the face mask detection example is only available on Pynq-Z1 at the moment\n", "accel = models.bincop_cnv()" - ], - "outputs": [], - "metadata": { - "scrolled": true - } + ] }, { "cell_type": "code", "execution_count": 3, - "source": [ - "class_dict = {0: \"Correctly Masked\", 1: \"Incorrectly Worn\", 2: \"No Mask\"}\n", - "\n", - "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal), str(accel.idt)))\n", - "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal), str(accel.odt)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Expected input shape and datatype: (1, 72, 72, 3) DataType.UINT8\n", "Expected output shape and datatype: (1, 1) DataType.UINT8\n" ] } ], - "metadata": {} + "source": [ + "class_dict = {0: \"Correctly Masked\", 1: \"Incorrectly Worn\", 2: \"No Mask\"}\n", + "\n", + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Load Mask Examples" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 4, + "metadata": {}, + "outputs": [], "source": [ "mask_examples_dir = \"/tmp/mask_examples\"\n", "if not os.path.exists(mask_examples_dir):\n", @@ -79,20 +81,20 @@ "for i in range(6):\n", " if \"{}.jpg\".format(i+1) not in os.listdir(mask_examples_dir):\n", " os.system(\"wget -P \" + mask_examples_dir + \" https://github.com/NaelF/BinaryCoP/raw/master/notebook/pictures/{}.jpg\".format(i+1))" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Run Inference" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 5, + "metadata": {}, + "outputs": [], "source": [ "def resize(img):\n", " img = np.array(img)\n", @@ -100,153 +102,153 @@ " resized_img = cv2.resize(img,(72,72))\n", " return (resized_img) \n", " else: return img" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 6, - "source": [ - "im = Image.open(mask_examples_dir + '/1.jpg')\n", - "im = resize(im)\n", - "accel_in = im.reshape(accel.ishape_normal)\n", - "im = Image.fromarray(im, 'RGB')\n", - "display(im)\n", - "accel_out = accel.execute(accel_in)\n", - "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", - "\n", - "im = Image.open(mask_examples_dir + '/2.jpg')\n", - "im = resize(im)\n", - "accel_in = im.reshape(accel.ishape_normal)\n", - "im = Image.fromarray(im, 'RGB')\n", - "display(im)\n", - "accel_out = accel.execute(accel_in)\n", - "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", - "\n", - "im = Image.open(mask_examples_dir + '/3.jpg')\n", - "im = resize(im)\n", - "accel_in = im.reshape(accel.ishape_normal)\n", - "im = Image.fromarray(im, 'RGB')\n", - "display(im)\n", - "accel_out = accel.execute(accel_in)\n", - "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", - "\n", - "im = Image.open(mask_examples_dir + '/4.jpg')\n", - "im = resize(im)\n", - "accel_in = im.reshape(accel.ishape_normal)\n", - "im = Image.fromarray(im, 'RGB')\n", - "display(im)\n", - "accel_out = accel.execute(accel_in)\n", - "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", - "\n", - "im = Image.open(mask_examples_dir + '/5.jpg')\n", - "im = resize(im)\n", - "accel_in = im.reshape(accel.ishape_normal)\n", - "im = Image.fromarray(im, 'RGB')\n", - "display(im)\n", - "accel_out = accel.execute(accel_in)\n", - "print(\"Returned class is: \" + class_dict[int(accel_out)])" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAIAAADajyQQAAAvPElEQVR4nGW6abCl2VUduPeZvvHO9775vXz5MivHGlJVlVUIqSQ0oYFJSMJg2YBxu6MZjQw02O62saFDpnHTIQOGaAK6A4eBFjKyMGoBQhKSSrNUWZVVmZWV8/Dm++58v/kMu38Ulonw+Xf+nIgTe6+999proYqxKggMyFDprAIOACDrECNjzrUaQV66orSEzFfSUgEsUsoXjAiUMVr5whKV5bgg2wpqCAwxcIYqk3NUypNpOsl1EvudxV672Yr29wdEbpYUnpLWuul8HHkRCqsN5aWOo6Yuh5OZDv1ASu6ryBgzn0+lQuYxwrImmpxLQG9hoZsk2Ww29zwvjiMhoMyqJM0m00RKGQYBRnVmjassMEBrgCwBAvjQqHGPbBCElSNDlWPkCceATxOrCaKAGY3kXEVEAASgGAScJykQsqYfB5EHJLrtZjpPGTfKD8KATycp50JbkgyFMEh0fKW31x836/7e4STRQggczUbkVC2qzedTzhUiWG2VJ60zQnDP85qN+nQ6jaKoKKqi1HEUeIIryWdJmRWZLrQQDKQSunDtJg6mFAfSC/z+wYwIsMCptq0e6iKrOFiHjEFmQKAtDYBglXWlBWcRGRoCCZCXlJF1KcQNjwmlK/I8Pklmpc7KSR74MTXj9dWeclRYC6Tz+WD7MJlPZ17YVL5EqQJKK8YMaWeBIcVRFAd+XpXaGo6CizCORRxHe7v9WliTykcEY11ZaOdbriLGOBeKhOYMhERRVTCakq2wRHvsxDHGtmeTaZYBcpjm4PtQGCRLVKIXYRiQLlgxdLoBrAKmyJQIjOKIK5+P00ozKKvcBtYyV5bo5pocW1s6/libA9ZW2o2j/f3UQBUmd2faD4IHh+Mu+PUQO61uv0/JYIcZUspf6Tbu7hxiLQq4p+elMVW7Gc+zajQ+IMdK67LplAjIUlYWNnF5YIXgxlhDxmpOjATjID2utauM6+8fSBU0moJwWDBwjrIKAgGlBa6Y71GqSQoqASgB5oPHIfKREXicOrU6uYJ8qkwZhHyWV56VUX2pW2+/+bGTN69ct2Z4f57d3ttZOnbMS5unl8Qo0fUoNlUWRUrY8cn1Whht5Xnea3eSwgpPOOeypDhz4vS1V25kmUbkQCg4CsGU9Ix1mS4E44HvG2scWSGpKBiB5QjIGDTb9arKa3Gz3QqyIieLlU6Nzo4MkQNAUAwrTUqis2hzFwWYFqAkNBveeFKcOtasqrworYUAAFCXYX3pXW99+2DnYDgdNJAX6WhvnKTDgwsnNhfa4deuHy4tr8pYEq/fenDrnW995v6Dl3cOdrZOnr63ff9wkHVqNSKazucE3sNnTl+5cc1qhowRCEuaMREEvpRcMBhOEq1dLZBM8LysDMzRqKKEOI6E8qHIZq1WZ2NzNctmepSgbBTaJGMK6lAy8AiYJGuhITw/avarg4ChX0NCkETdmK3Vo9v708Xmymxavutd73nrxQuf+/gfFte/fP0bNztLCwPBJ/3BYJ6GMrh2/7AsQ2eLZHQQJnKU3MbE3Ht5/x/8019pdRr/6B/+QK8ZHOuBQj1PwDkLND396Nkvv/hcr9EKffngIG3VG5PZVEoB4MJGo1GHNE2bzSaCCXyVzKtK6kj6jDtUHuoKpCTOOYDzBWbGiYgDWmZgPoXVLrPgjIbCQhS2PMURkcoUhAIgPwrPL9qdQ/HEE2/phubEWveLf/Vf9o+SPNGZrcYzbYULWMRCRgZ9LCqDZVm2uytFlqTaBoJJhIWF7hNv/PYzF1+39tBD+XTvX/7Pfy+UcmN5/cqD26GUtXrt7r7rdHvTeZqVdn1lRUrceuiUc3bn/n2ji7KykS8qQ47KUXaInpHoIQBwAR5HLiDJoBEIJw0KjuTqCorCCY/7SnmSpbmZZ8YZG8eRgwp5SAR1Tz62XPvh//U3Pv37vzI+PNg5TKbTssiThh9ooHluRtM8jqXnSyYVVXkURck8SaZVp1ubTNLVlTV0ZVYW/cPp2sYiOrNx6vEf/de/+Ov/9uez/fvc98k5KeJm3TsczHdGc4Oq3Ww4IktcMJelc0Qehl6jURdSkil3Dvcdq4RH6AVCl8Y5EByMBQCQEoBQGwRwAOALJA6cY+SDAWCMKUZlhUHYspSfXj31wd/40Ef+zU9Px7Pdw+z+7qTVFJHvFxpIm0SXQoluqzYcjNrtdppme7vj9fWWRJzNEq2p3mrO5ll/lBmACWJbCVfpk6vd//PDn/iDP/iN3ct/fTCrAsE2F3sPhkMGCvx4NM1qjcbgaHLm9Opkku0OJo0w8ALljLEoJ9OjsqikYgj/3WEMEFFJJjmlubMOAJhUrtGQjFtXutDnWUrtVuf1F574oZ/9wB9/8McP96rbR3mRW8Q0mZtGLSiKorVQ74RydXWlWZfjo8FgMio1f/j0yTyfXrl6z1ruGO0fFprzo8r6wC1YjdCN/KOk2BD89z79xRdf+MQ3/vTD49kwL7HVrO+OTbdbu3p3sNT0kjStIKZyXqEfxGG9HghEISHP0/5o6pgRiEAEzVZMSMk0c0RAgALQukKT8tA6MNo1WgGynEnBHHEue9368frCt77rO371x9+9c7/0Oh3rsrLMTYWrS+H93XRxudVr+E9eOFtv1GuK9FpvPpk1O+0Xn7vUXVq5+PDW4eEobnd0evtwqtsAQ7SaAAnmaUkAt637wA98/wd/93fuXbD80u/lYRT53JC7u33QFK4VB/Xa4mhWJJa6NX80z10tVL7PBTCuGoaVYoDLq+1hf8oEWAeCM2OMMUBECMAlj0KKmqA1WoOltb7giokY8OmHlz712XtNn6o5NDv17mJjcDhOTLnSaqXJaPPY2pkTa09efLizviE4kAPJbFkU2XScFuW4f9Tf3TFOzyfF3b3DJGdpXh0OKxnwSlvmBXeOkpKoJvjpBXX1QP3Cj124f/XKXqI3FxYOpsQYG6amHrAo8Avj4nrr+u4QAaJaAwkAODFXmjnGEUoOnudbxpxOyYLnYZaRjAVXiAx0ZSSTRWa0dd1mXGbZI4tBZ3Hz6pWXmZXNKKR6K8+LWX/QbtaydP66h48/8y3nNs6fbdRawKUKAgBE68hpImfJOqLpdDA5OlTIdWX6/f7hoP+Vb9y7tzcUXsgsJSAOjmYlMutIMLIYvOcJVvqB0dxq2211qzLd3FzPJkf7w6wKlkqCw+GMc2ReyBlxzgHn2O1AwDljqMk5IAboBLPclprHniSEeZ4zAEFMct4Q7Kk138qVnYNZ/3CXKgjaLSFqAWR5Ngu5furh46tLrddceLyxtMykV2stE0POBThjdc7IgQyqqhBSaaudNtYUybiv8zRJ0suXLl1+8e7NnZJ8WVUujOp3D8exxw5S87p1eOI1K7nWvqjnxgAjrDIIF/uzYlRSrqmswPMYSuQ8clRomOHGAkMhnTEgKXeEyKIwHM3mXIK1ILkix5HpRsR8Ix9fjytdm+ZErrp+dbtWU/HCUn+UtnkWMnrP257o9qK1tdXG0nFAiFsL5FAKHzkxxhwAmYKIACU4cs4BgCUHzlY2v/PKDcinmaZPfv6LX/7STqqhALAAW4v+S4dFWA9OSmjFZbvd9MKgQA7SGw0Gxo+RiUI740xKuZSKo0cI5GWCUDriFTPakeerSlvjvCAsCI2ogAnmMU8JHorC4zpL7GA6xyh4/qXtrW7zbn+i0r3I46N59a53n19baq+dPQnkpB9LyTlDJj2jrfICIgdEnIfOOQJtnQNgRMRQgBK7L18TjC2eezSdzd7Xaayvv3D7lduXXho5Lq/1y5MNMbbuzlCvssZMm2aUX3jy/Hy6w5aWR5nWBEzP6pEHhZcVuVfnJdOj/lxsnjg5L3Jt80k1rLTzPbTV2DlATkEQOMRawOdpWVPeWjPM++U042VWNWJ5rT/xlAABvLLv+94LD22utY+voVBx1ACwznEUIQFJyYEYIjAhnDNgEYgh0wyZQwJHCOzE6bPJ+Ag4V8pvL66+5jHLEDN9a+8gVbX4wdG80/CmUKbEF5ZX7ly7MU0udTq1U6vYWFb3xpnwoNAjCrwwZpWeZZnN50YYawn0IB0S464qrXaAqJTiviQixplmulXnk7nuW8sKS4wVSTJLDEPItCHD3/5E9/hKZ2VjOW50sTBC1lQQAPeQA4CoypxbJ5VPwBk64FiUBWcMhUCtkTFnrbMg651sPNemclVeC4LzZ87NS/vg4Dln7NxBOs4MwCQvX3npWr9yh6Wp7Wf9fXHhfG+hxS4f5KY0iOQr6QfBNBmUJWezfD6ZTZK5LrMC0TEROSApvSp185nO5oWepR7zepEPXFSGsqTkYcsyrAgYsnc+vnBybc0ZN68qicL368AkAWMEnPvkwAtbzlkUEgAsIAFTwkcmAQA4c4jIGKIUBLP+/SD0lBepKPZCVQ/8U6e30Lo6wEqneWaxkafz1VNnLqyqtzzdSQTtz2hnBNPrRydCVaXlYJYdjqZ5mSx02hYdqwfNE2sbnciXnuSKO5kjYlpokE5IF3Boxu1QcEaKFfPAs8RocnTEGPmCvenMwrkT68tLjfOPPba03IPSqc4C485ZMOQsESAHS15Uc7YkZAgSjCVkDMAacES2LK2pjM7LbNZZWUHjpOBE1lNidXlJQbVxbLEmMB/Po+bSQih27l3f261u3Bg8eQ60Nrv97O5BtdroLrRqZ5d7S1GtTI0x5sRWjT0YPTgYjY2wnlTpzFIpBcNWU+nK5DlJlGVZ7M8plLTU7e4fVWVZoYUY+GvWO10PG0r1uissZGVhsdW0JtWVmU73KZ2VkymZ0iEQcmTC5DNnShASjXOGiEinBQIjS+gIuQAAcgaslah0OheS12rR7et73V4DBat7HAojUhu01IMh7N8ABkKXc8vV0X6yWo8lwbgwioX9vWlapCwI/EaLB4z7fnlibbkeMxUFeVrWgnqnE4U+9+o+YMkZvnx9ezaluNFYWG406sJD4/nc8/3GYt0VVVCrCURwbjYb16ImRg0ZRqZIqvF2NZs4Z3VRUJlW86m12uiCiKQfMYaECAiMkHNOkogsMdNaXOAA3XbztW84K1Cj1jcebN8jTAGmYx0Rv1+A4zbPKZ2UN+9NsllpdblcD6JI+B63mkR/NBS0oLwwborZLOs1/P4AFheDwWQiuEWucp3VvABKx0GMyfqlHQ1n5zZaCw21trSmra216iCU78VA5uDBg7VjD1FREvHS5SpsS9lExp0znudl8xmQ5QDcC6Aoq6rkKIy2IlJAxhnp82CeJl4clFXuBwwZ29/e0aYKa2I0nTPCAWII0JCAGshSbmFjpTGfz3XlTwwZqioN07n1alI0a16hC0uzYuiRFQcmQb1SzXhdNa0dJ2le84LQkhL+4XDUCLyj4azri3oQBIFaW1s6cXKVy6jMUhHCrUvfeOjiM1xIEhK5F8qQrDV56gg93wcu4nbHVhqcc5Vhns/9htMVV8YhAQhPicpB2EBrrGI8kKrTqAVBAJSXpQUCB4QEFmBf2wigBGh5UCKqpkIhW9IDqQ6GkzSFLKtYVuYGLUeczfKj8fzwyJa61KDicKnmddFBW9agqu7tzFOCwlYlAGkrGUVhvLDUqS8tkct8X1hLJ86dVcJzgNyPhRDkGAATfiSVZ4wBYGQMEz4TCqVkXCE46QfOAiISyqwsgKSUgXMOOFdKSHS9Tqgk6BI1AAIgQAmAABkASU6+Go+mXPOWMLqoDodVbqpO06/5kQhDbzyZhwJchQ4gLyif9MfF0XqxutQUyggzpRJECc5HCqMoorTdjasq2zz+GFOSMcZ4yHnNi0J0rbLMhR8DMWu1qTT3AkThGHOVNmAY8wmJQBA4Qk6cWWtISiLjtPGDBjkChlJV1hTWWkaErqwM+ehmgADgiDgAAvPQkXHrm6cPd+7Xmt54lB9fbtScN0waqTVQWEbWKsDIrwchjxQGAleXaluLG+vHul7QrnlLlklfxbGPQvHpPGOGhoO5DMKFXse5ipwR3DdQ6cIYYsh8tFzrwjgQyiP0LDGBivt1Qg6ceUFsrEMvqgyRE2mSIHHrOHlRXtE8TdM0dQYY99vLy37odeLAGmq2IyAiIkR0iAguBYg4u33nbri4VBqvdAAo2gLyeQVGMimFNdSqtXoLrY3113ztuU80VH1jY30674+m42yedUEOp9l4Mu10a3lpU4ImooxZrx2D063WEpPKMZEMZyb0LDdkgUxWa7Ull4acrSqPQ6lLFdRRSCKYHfWZ8Io8caAMMlL1SZpMZzMVyrvXb3XqsSnJzHda6xtSFI1We215bXXh3ks3UwlQAhARAARCaqunzj1cg6svX/cB3vuet+7cvy9Cu3L6IebH3PewvQ5xGLXi5SQZgiizsjDGGUAGVBO1Zb95sH8Ye3KeZrqCg5wWAE6d7HzPWx9bXVhZP72ZZ9XBne3bewf9UZGRSnOmAhQm78bhya2t9WOr9UatVvf9oM6ZtKYwxhSTcUV6f/9od7v/3Auv7E+Lw9FRHNcW4vDvv++d9Xao/LqD4kt/9ZkTW8tFMnyw++Df/b/XCrI5QL3VGo/HbQCGbEJupdnstlqTo3vTBM+cO9bqBPXV4yA9qyT6PdZQ4POlrByqFgGR1boiyHMMDa5GTaNtnk6lUvO0GmVwfrH2xPnVZ5440eoufeWrL33n33s/gEBbOmdmo2mSTKoi3T8YEReBrHfbte5Co1GPPL/OVWCqwtlC52le6eFwNk3TKjeckWIYR3EUhODIazYKsDYrJ9PRV5/96wtn1zmn3//4ly7dKMZEr25lVgNvlpdt6U88miY6RFgKuR/z7kK3EMHaiS0ZN/HchXj3MIlFi3iihUFkgZRhLSzmOeciTuDoaN6qh0f7WVhno6l7w4WV73rm4sJSlCZVc/WheqeJpUkniRS2LOYOEJydzebknClzAazWrrUXl1Ug/agH6IosSafDycFgNpnmRUkgW70mgHDInClrUVBVxgArSucYcq4xHyNUu3sPfvkPb2QICOiIWoiaKEQcEDUCNs4dADQAOdDGqR40w6heE7yKAj8DLzfWMpJENku00ZOkcHVPdGVIGq01QgICB3SjcdXuen7kay2M0YP7u54nXJXkiTnc3dfgNxe6g0E6GI23NlZl6O0fjqyFpeUFI+ZCSlskyXh82B86US8Z98LWJKm0FEeJNRXFfnr/6ovH1xYllxh4cchbjTqURbe3oeCG4bw0DgAUx4hzWxlEiBUurEQ37mRTohaitba9sNatdYUFvzLOGc2FE0KDUw7KWQpSYjYyaVQ4B5pY1ABdkAewsbzWXV4s0kxIl6VHWX/MSF9+6c766Ufv7OV//bWvh3HgtH7y/KnTUf3ug7v1MJqnekmpqN7jnJdFUlV6WhjfqwSyL3/p6+/4yZ/+D//bP782na/4fHfOl+tNJ8VmN1DW9+o9wZmM4ryYMQYnu+2rB0cAINsLspgsby4f3NjZm9qnHztJty8DwJjo286cdwozk7Lh9H7UCFrdUElfyEgqPwwacU0s1Lpnzq0tLS0IxZDJ0Pc7Cz1H7KknHmUyrHXbKmQunRibvPTK1cNS//ZHP1u0um1/aMf7J473vudnfuHe3vD6tdv12FOBh6QYSmuAMc86rLfi/t4rG6ee7C33xtduSlY80ix91fiJ/+mnTnb4i5dfHk4TJVirEYWBVB4oLzy+FhwdHP3Jv3rvU03cHxz2Tp4lrxV5EgFOnjqr2N8sRRuLKwcH805nFdfONoo0c9yiFtLzwLm1xcVG3F7uNAaDB6Pt2d0b/eXNtqvKIIjv3jn8vX/70yvLde6hzueHe/uHDwb3d/fSwrVai9aoisPmxnIkeTodOGc5YtSoNerh6uZWrbmMDJLJ0cH23fE8Zah0YRkDa8lawxCzPCESuS486bgzzVq8fnLDOTc+PJyNhh959oU0r73nqTUK1L/+7U/3AT3BkejcuVWv2f3Ss5eU5FrbNzzzmvbqokZPHF88s7Xe2jvcVl4r8pQvYhWq2SypNVrXXrpWZnlUR0C5sLgIwjbnNow84YdCcIGs3XZx1OotLxkDyLiUHgqvLHJjTBgobczK2rKSrNVeBFDVeCKiyJZVvdEGYx0w0a6NB+NK586gH/Iw7Ghb1UuQjOrddq3T8nyvKoswFNlM7e4OHOX1ztNFMlxYWTjc61fGdGN/98HOM5snGYCw7vTJ1cJU8yRlyol3vf27+v3tvZ1hbtTwoJ/lt43DLNfCIwYYh3ErrAVKPti+vzdInzh/VkoJAACcy1atF5HTtUaWz2fTSVKVVTFN/FAC0NrWZq1eN0XmBzXph+U08dvLzpq8PzRSBXGDoRFCNY6vVrqcDebW2SotJIP28mIYRVwy1Wg7FJIlFgMZlPXYC7yYO+w1mgsrS7DXdwCKUWHx2U993gfQjrYeeciARObqnQXxG7/1IUROvNJOqihkAoAjkx4oFIyNbt8bjnSnGeRJ7iFGQYAMhVDAAyaksp4xBQ8ZgfXDCBGttV4QcRGAswRWRg0V1meHO4HfRORcsdrC4nB4FDTaURSUsyERhGG7cWbFZik5w5hgQCgUyNCBdVkO5JxlWle9XqcdB7JWV77pb99BAAIoS7242i6SauNE/dKVndEscZxlyXy3PxBioZlXZcQUEUifSy9EIRUKybQoy8QYj4tknHOBIcCpjS6QsRVKSUREznmqZpxpNpfIAXHJlLAG0Zgqn4HNiKls3Gci0MvnnueranGtBl9Ip4kZ9KsjaC53ZRRXFXIneBARzaXfZNZZ6xwYM88xVGacE9HRUT/w+MOnz02mRxEPS+MRZAAu9IJTm6uf+Mzl9mLr3d//DhXFpiymDT9QDRHX6yGizjNpLIIrslRJX4SRZD6zM6NxZu2SD2kFSqql5S7jjJwBCAUQWgEOvLCDjjHODMpyYatYPFcYnmaVda7WDG02D8PQOndWgiNMm28JH/tO6ev+IBl78e0HuyeObSIYdJl/dDee3eflSHBRFA4YK2cJIgm0XEXdTmv9+DFhJ/3d/SybcwRLsDfP7Jev/uiP/zCpUNYiAlxeWlVcEEdRzCaWrGC8FgZhECq/0Wx1dZ6DLUCZwN9eJBBSewU8eXa512vpQqfg5NlnhrVjibHgNHD50rWXF9tLzU6t44lQcl+iLxknAWRUHJUVEOAwN0JKaT1jCyjlRqeljW6cXpKSjEUOoa6dK9x543m2NAblly9d2b5/69HHHt2q1x8+vLL7e780PDwIqtnta1eq0jhyAGCIJrn5wmc/11juWYZx6M+TM7WoHoSx6C32wqDuQAaBd7h7e3//yo2rpWDcQwi0SabVek9NxjQlePt3vvn1/8OvfOHm3r27L3/vwlbIwascY74v+PLFRy2AAEREl5hE57l2eZ63F9pf/8YLFy4+tXOUkKDIZ7PJfGO5MxkP43r8wnOX1jePc+QSgQuB4Cxh6PuOkCv7LY+cfOrMZiDUblKwjYuUJXXRLWZ63NcTwahyAEAAOef3rt3rHT54+DWPM8Ynd673izlUleCc37j6PGPMWsu4dOSkLwWKs5sPTe7eiJeK6aA8KkADjA6PPvtHHxKvff873vztVDrjbMlQEo3nmdbm2q2Xm7Xm5taWzqtOMy4qSgpdTKpj5x8fJO4oM91OPNMwcYxGZcuP7hxNVW9zmFGrGThgYaC4wEAgZ0wiZtZt37vv+6EUstXqNkSBsoyWO5TO/ZbS2goAAwAAdWtX16PhQTbY7Y+S60899US8dXoyz8X+9v0gCBxHblUU+0U6q/JcUz7ZuWny6d5eue/IAXAAPZ8Es7unj604S4kunDWLrV5eJl6g7k9mzguXN09NyqwWhjslG2XmMKNAuLapSgQ/CsChc85xOdE2KW3pwoTjvEgbs8oH1gLpcwiYVr4SxBlRY2Hj5u2XN9c2tCn/88+/rRF35tu7C8e3dv/qS4zI/lf9dYJw3o+efv87Wwt1TwhdutxRGNSFQ0ZIrrRGp7aUZFHIqK5YT1TXh3lG4AAAYQ2kwkp47upHP3T+7/zc3fv7W2sr/SIrHDwYzitStfWzt9KSCf/WMBGcV6XxAt8PvLyoLHD/VZeSMxLRITrGlWDKmCYPI18WpkrJzEorhICiKAHQERIv6msvp2ZR59ILFafVM6chN1+4OW9wHFligA4ICF682dflx9ut5ePnThs/kGH9+OkzTBdllVdVqa3BqrSl0S4fHPP1/l7fMf4qBaoBfOv5R86e2xQo6vOb/dIMRXz5KHl5XN6YlDPt/FoIYJGRM1XkecAg9FU98FyhcwNE7m/UbUuBrxxiWRpCiKUIJNNUScbLypSWKm3Tspon2TAtJpUGyV+5fb9fzAfTaSR9nc4EqJaCk6eOAYADehVmM4BrDwong698/fnAqzWC+NbLV4VjDK11ZJ1zHERTUUzs4M5OWYDf6PRGA0SAUHq91saJk6nT+7f3xlk1ycpePU4tlkUVKDXPCiGlc84XstA6lirR5Ty3mXZNn/uSgzOVk5mGmjQc+ZxhnpUCnfQUd7wCUCizqnIKLKFhHjJEcIrzpy6c64VsVltsdOsSvByKexm+vruAcI/+azYiwAz55cvXj28uf+0zf5nnlRRMKESHoGqRI6wLKXTlF8PdI7COug3T6vqHg2Kamh99z5NCSOlcbi24oh0IxtBWGpTvwCo/SJOcMca50xYLqhQTXLE4xlleoYWJBXQlcAEGiZnI58QYWQAiQ46QFbbkjErtlBBUaDIkJJtllczM9BN/LDyv3lksQF+/fENF4Y37u9/81atB+753PXP16qXLt3e31pZ+6Md+ikRDkJXWAZTMVqWRuUNmRnrqoKtwZbF7886dWsufjUspEF2xd2knxerupz/6yHf84CR3gQfzTPuhB876Hu/G3jzT9UA6aznnFqHSzifQ5JALbbViNKuKWCmHzlnHOdrKcULf545zhnZcaCSoecIhCIRuMwBwV7/650yqS6/0n738ysuv3K0F6vb2LgCcOLZ25/4eAQHAn3zic9/zHd/93T90ttVpcCZr9SYzlBM4YDYIQhfUlaN7fd3sicWtxWQ+IgO7k4IQP/xnL2kRHLu4JRTKK59Lq8I6ozgLJQeivLKFhaO5dgwCib5yXBlHhogZACDiuqj7qq6Uz1hWlkqg9KVQvB6KsK4ktyWZFFzoK2Dke1xJCoX1hOUMYukyg//st//gE1967sFwVM0zSwAASnntRvwjP/JeADrejq698OwXPvKRS5/4yz/9j7+7u3tLoOPWaZ25ioqaJ/Px0eKyL4NAeGI+mc6mjhMYcP/HJ/7iTW8+F3clEV9r2O2dBymrnV1txjWvKovIY4TO91jEGYgqFN6VQ/PVF25EpJ2ret1uIAQjFwWhdvbW9uG3fsuj3YAPc/b8zt7J1d5a5HFRzSunta35kJUakLxQXdtN1pe72sosmwABADiAaVm1AebIGhLvzmZf//o3upG6PUw6vnjDO9578fVPc0/lSS7qq4vJcOxKkgJcUcwH1DxRaymWzGf97TxHNEQI4MB9z89/6OLWZqvZfM+3rV149DRZTAuda6Yks4RO1G7sJ//po38RxDVP2qgWdTtNAlYPwjQrSikUYhSirqpaxD711auz6SDPysqwG616Zfh4NskJlhZWwtjzBJO+lPlkfWOlyfhzWfWJzz73TUTNiBoM26F6/NsuvnLz3tve/sY//K0/+umf+Icf+8h/SebDWy89/9KVaxeeeEpM9/cJUShOKBSS2lqOJM1zHQAfOXD0TZSSceaLt24iyHe94cT2nl5pR0mh//q5G61WlyPV1LwZev/g3W8FsLPK5AYWQ+F7niOX6wosApmiKpMsLypabcW9RoMhLte9Zq0mJVeoZkUKhM1YtGK4ci+lhejkcnhzf7uo7P60/NumqKmj5biRzkR3OfzKF77uxUGZ4Xd//ztfvra3uLW8depE1ApE3Fmaj6Y6N+DKUMgKYFqYRSUPjyYR4oAIAAQA/be+oa8fwhrXw8FMAP7A649bS6lmrrKEKDlzztXSKgchgDgQcVYjIXwB4Pmi7hZ6qXGl1pXFGCkIvcBjnHNGxhOeQfIFkoWFboCAxgZ3PvQ/DtIC/nYRBACAewf95pXnX/P02xqx5VQeHd5+81Pv/9Mbu+aodeqhtZvRJra3ekQE6DwhI8YCT3IyejrfPUzTEgw5AOgimwC0iI6AAOB47/iP//mXh8Nkc70TClJMVMYV2knGjSVAV2gHyAttJONoTa4NYwyIKkecc0RkBL7kSjHOmWPICQSCJRd5ApGAKEN0lb2825//5j/6fz72+f/eySYYX1ta/o53v2dv/xqKYC9+4qvFCXpwAMs9iAMsU6GUEkL5cRTGsZ7OUc904Xa2572VYLqTA0CIUIHrMRjTqz4/uH90d3VloT8aMyWJtCXH0QUCnXVcAFkUDMnaSLI014WxgVJAGhhXZOphqJ2WAo11ngJwBpBzwdEaJbhzhjGc5C4z5tlvXF4+eW57VHyzC3tCFka/ejXOWgd37t3UZfGpV45DI+ZLiQsakHkURrB9H5ceWlGB79XiQDBfKuHg7ksvSPSMcnu7KRLEAD4CI+jD32QER3jb69+5+o9/2ebTcYEXTm08tL4WogNWZAWrSiskVpZXzoJxGskZAIC0zCWT2hHnXDGIQ4+70vc945ziVA/Dy3f3v/jcZQS7dPKRVreTlEX+Wz/6H/7444D4qhYBCILAvLoVQGAQ4+m32WmPt07TqVVXVZ2OPxk4S3zj8SURtmqKYb0RNxsdyRVkGT/78Hg0Q54NdlIN0G57rKh0RgTw6puS8MUXv3Gi1smC1smQ9Y8GBzv3i2Q6NrTUWTmxvCikQiE8LvxAeIyT1RIYI+vIBQylxzlnngQl/LQwt3cP7+7suCrp1IKHt47JuLHYqN8aDsWf/ur+YEoAAIQADJERAGfgLALU2ptzuyBOX7TPzWw7wIxOv/YxPynHMH7fe177nz7+oqi3ur5QTMnJNB0PbrqiENpxP1CsAbDXkRhF/nxaEge0SEAeAAOy84Q+8osX/tn/PdvfbiwsSFwus/GWLa7vDHe94MTKguKszOf9/vxoNp+VLlRgi2JlZSUIgtLY+eBgfeXYPBnf3t1BnW912iRaKqhJEYggKjxUg1uH29uf/MwXAEAhVkSOiCOQtQhIQMlwmy++prrj0Ouxvb5dxCBqkBvATvWf/+L62a2emPQHeZZKKcOIMxUya7N0IgU0e61exwtU4AtvrmZlQU2kVi886mccIHf581+7vHjlC8Hq+STNjdNhWJtM3ebaqlTy/mAERKvtKDc6EMw6qLTW1vbHYzGbVaU5sbKsdXY0HG50O5wBgFRBkFZO66LlyUCEd//gVz/65597FU6V+5sp3gBEQuSAxpADwVoPwdinILcoweMvvXjz+ErkhjNextkRF1zAuUcfadXb/WF/NBzErfYjDz8e+ovaHU729j0Xeh62Frt6+wiJlxWrBUwKBgg3rrx48Y9/I/y+D6yun84K3R8c1sNgnmUFIaEARg92+73FBU/6g/HQFflukY7GQ0v+QqcxnY389kq73SnLIopa2ppSV4HypBcvLdTo4PrOg+GrsPIZK51DQEJwRLkj6TeNGeHWd5n6MviwcfHMgxeGnlx7x3c8Ndy7c6vD0PP76Yg12+uHe4P72/cBYKHXO33m0XliSpcvrp08+ciZ8fAoLahwsar5BLaqTFWQ50WjmTl2avPP/vIrj6317h9sN+u1tcW10trZbIqmBJOODrenyfjocGf/6CCpqCLqLS6vLK+sryx367VpZobDo0pXAtkknRLwOK7XolDrVB/u/9kvfeCrV668Gi7rHABwJEEQMuFYgLwSZ/6uWjqp1nq82dvNKi6garqrr+wlVkE6Ny++8kPf/QxbXGotbyyuraydPfPIoxe+VcpAeDA82r10+flbdD54aCPPSi49gyhjn4ExROPhtNNp7uzuHAx3P/bTP3hxqXH7wZWvXXp2c/X4sc0TXhBlhTHWRcpnnIExgnRZltPBkS5tgGY8OMqLGXc6TeaBUp1Gb7nXPNh/cH/nzkYEf/1vfuwjf/G5b5Z4AkQAT/l16ZVknS3z8FtM7pfzZcNiy0yj9YjNUMj6RqeB6DCpYHT4e//iN0Wels6YHMv9/f357MpoNFpcWgzOvfvc5sa5zVVLP/RrH/gBl1mQQbuubt8/6AReVZnZdK7QVQBpObrx73/2dT/x65/V7Ortrx/sjb79jW89sbQ0mgx3x+PxZCyEFsK3phScA+OjJLEmL8oSpDq5vFrq8sWXvhiGUafbPV2Lvvy//+THP/lZAGCMOeeaADmQB6jLYobMgoTo/SA9ODS4vhnMIBWNx86Eg8WLgWp//hvbSwtEupQy15MREwi1Rms6HfmRPH3+wvLjf+eo+51veu0TJ7bWxob2Z+y7f/bfzXWysLC8ezg6e+a4lIKctdZqAxygFarhZP/a7/7MM1uLjIWnTp64tX3n8r07N+7dO7G+8dDxh5yxZZWneeo4n8wmXuCvrZ98w5Ovi8A9+5VP3757ZX1j83WPnH3rSvOrH/qJj33ySz5pRIgZf+vrXv/I0xdLABl6506d8ILm2Z/5FISG+gXWOr5X5N2FM2+5+Nqn1q9fLZ/fHZm8OLy/i0w7l0Kzie/6we+vt3uVVQ/4k296fGV1qV1omBdllWqsqpKBYtUr3/jUcx/7kyQr7XhvvdfI86SxtvnS9Vuc4N2Pbz3++DrwSIB64id//U9v3GMitk5bUCudxtFocOfeg8XVDc8V88zVauHu7oPFhW5RGWmLVq8HRG9cbf/HD/4TmaV/+cWvf9+3nXjhlf0Hh9PtaflqKr7+dc9kk/JwPtjZ8eDk92LYWTuzMNjftWcf/bVfeP0//qlPvuaxN75w+RLJmFyOe9fBpmQQajG+9Sd/ySxcJONQeL1QtGoi8Hg6njBnwFPVLKmoevEbn87aK7svfq38+l+sxfWV1eYrt3fmxgHA33/DI697YmOcMeTASDSbtRce+5HUCTDV1kNnijS/c/eWK8bj+awe1job5zrtZicInr91s+Hzb9toff63/5cGpM/fGl2+cuXnfvgtly899+mvDXZyDQCIf2N+QAAGj1rvaVg+C4fb8PSZyOumTILqINM4rbqPxcWc1pbil1++Bjdvseq+u/cKG7eeLKvqoD/53Oc/+dEPf/x3fvf/O9o5KIoi5V4xmU/Ho90He8XS0w8msPH0myKAzOh7u0MSAhAJoFYLGechKyMlgent7d3zz/76O9Zrusy+8sVPfeUzH67GO5WpOo0643Rw7Ytf+czHnn/5JarK9242Lv3mB7LDoz/7/NViPvjgP3mPyzPt9DSvAAiA6JuMqfWEhRUo8+idJ3l3WVWN9Cjxg5CpQxocfef7Xj/cYbM8v9+3TIT//OfftAqfh/KLwisGMD+6dfUmVhyj3tPH2lUk2XhE6XyWTeb9ofV6rYXGbHw/+6NfK1a/Ncv6/vj2q0RCIVirGbLlY8v7+0eh8lSbHuzuz37n556qh8Mnf/DLMy+d9CtjEs8jXSk/jPyghsXt3/ynHz2+eP32fjofnF+N3vddb5odDcti3ghrczj621M8Q07+edYNHK1ko5Z85PXVK5fg4ROVTTxf50p99dauHdz1o/bimmc6Cx/8w6+sfcv7hf6MeMtrtq7fESePLd3ZHj0Ys0t3R1vH08y6wc7dO0tPLLFUtVqN4TX257+z23oG/CX0j8zkTgygGVkLniAhBWizslg7OkoCITdXF+ZFsndwCH/579/cjO7dPnBes/bGb6+GyXN/+Puba50i9rjJr1+/cmyxeeENFx86tZVPxlWaekxYrv5bnQcAAIerQCHoDN71FjbeN/NCPvqQ3h+4Zj3PzGNvvHj5xgM4cbwaTqZl8dantnae//JOPzj3I/8S3/eLH55kiXDl4eGEd2uqIqq3iwcvY1GCr0yW3Nu7/dTf/bHPXTq0n3nZyRk/PHDFKwHdihisLbXf+85Hj/V6tXrAPE5Q7W9PhWTWkHMurYqqKI+GsywrS+vKwhlTALrV5QViFBK+8/3vMfPJ7OBoNpsmyfTAeL/8f32aMWVdBcAQEICo9y+gG2296+3376b2Cy+C8GFN17eWdcmrw+T069au3R11Txx7ZLP+uef33LMv4PLYhQGn4P8HVral3n70HTwAAAAASUVORK5CYII=", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is: Correctly Masked\n" ] }, { - "output_type": "display_data", "data": { "image/png": "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", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is: Correctly Masked\n" ] }, { - "output_type": "display_data", "data": { "image/png": "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", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is: Incorrectly Worn\n" ] }, { - "output_type": "display_data", "data": { "image/png": "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", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is: Incorrectly Worn\n" ] }, { - "output_type": "display_data", "data": { "image/png": "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", "text/plain": [ "" ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Returned class is: No Mask\n" ] } ], - "metadata": {} + "source": [ + "im = Image.open(mask_examples_dir + '/1.jpg')\n", + "im = resize(im)\n", + "accel_in = im.reshape(accel.ishape_normal())\n", + "im = Image.fromarray(im, 'RGB')\n", + "display(im)\n", + "accel_out = accel.execute(accel_in)\n", + "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", + "\n", + "im = Image.open(mask_examples_dir + '/2.jpg')\n", + "im = resize(im)\n", + "accel_in = im.reshape(accel.ishape_normal())\n", + "im = Image.fromarray(im, 'RGB')\n", + "display(im)\n", + "accel_out = accel.execute(accel_in)\n", + "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", + "\n", + "im = Image.open(mask_examples_dir + '/3.jpg')\n", + "im = resize(im)\n", + "accel_in = im.reshape(accel.ishape_normal())\n", + "im = Image.fromarray(im, 'RGB')\n", + "display(im)\n", + "accel_out = accel.execute(accel_in)\n", + "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", + "\n", + "im = Image.open(mask_examples_dir + '/4.jpg')\n", + "im = resize(im)\n", + "accel_in = im.reshape(accel.ishape_normal())\n", + "im = Image.fromarray(im, 'RGB')\n", + "display(im)\n", + "accel_out = accel.execute(accel_in)\n", + "print(\"Returned class is: \" + class_dict[int(accel_out)])\n", + "\n", + "im = Image.open(mask_examples_dir + '/5.jpg')\n", + "im = resize(im)\n", + "accel_in = im.reshape(accel.ishape_normal())\n", + "im = Image.fromarray(im, 'RGB')\n", + "display(im)\n", + "accel_out = accel.execute(accel_in)\n", + "print(\"Returned class is: \" + class_dict[int(accel_out)])" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Run Webcam" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 7, + "metadata": {}, + "outputs": [], "source": [ "from IPython.display import clear_output\n", "\n", @@ -260,7 +262,7 @@ " if flag:\n", " frame = webcam_rev(frame)\n", " img = Image.fromarray(frame, 'RGB')\n", - " frame = frame.reshape(accel.ishape_normal)\n", + " frame = frame.reshape(accel.ishape_normal())\n", " return frame, img\n", "\n", " else:\n", @@ -277,40 +279,39 @@ " img_resized = cv2.resize(img_cropped,(72,72))\n", " img_rev = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)\n", " return img_rev" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 8, - "source": [ - "cap = cv2.VideoCapture(0)\n", - "while not cap.isOpened():\n", - " cap = cv2.VideoCapture(0)\n", - " cv2.waitKey(1)\n", - " print (\"Wait for the device\")\n", - "\n", - "# set small capture resolution for faster processing\n", - "cap.set(cv2.CAP_PROP_FRAME_WIDTH, 160)\n", - "cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 120)" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "True" ] }, + "execution_count": 8, "metadata": {}, - "execution_count": 8 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "cap = cv2.VideoCapture(0)\n", + "while not cap.isOpened():\n", + " cap = cv2.VideoCapture(0)\n", + " cv2.waitKey(1)\n", + " print (\"Wait for the device\")\n", + "\n", + "# set small capture resolution for faster processing\n", + "cap.set(cv2.CAP_PROP_FRAME_WIDTH, 160)\n", + "cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 120)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Classify Webcam Input\n", "* Make sure you are in a well-lit environment\n", @@ -318,119 +319,118 @@ "* Position your face in the center of the frame, close to camera (see examples)\n", "\n", "This notebook is a basic proof-of-concept. Model was trained on simple blue-mask augmentation of Flickr-Faces-HQ (FFHQ). For better results, more mask-types can be supported (e.g. https://github.com/aqeelanwar/MaskTheFace)" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 9, - "source": [ - "clear_output()\n", - "frame, img = producer_live(cap)\n", - "consumer_live(accel, frame)\n", - "img" - ], + "metadata": { + "scrolled": true + }, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Class name: Correctly Masked\n" ] }, { - "output_type": "execute_result", "data": { "image/png": "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", "text/plain": [ "" ] }, + "execution_count": 9, "metadata": {}, - "execution_count": 9 + "output_type": "execute_result" } ], - "metadata": { - "scrolled": true - } - }, - { - "cell_type": "code", - "execution_count": 10, "source": [ "clear_output()\n", "frame, img = producer_live(cap)\n", "consumer_live(accel, frame)\n", "img" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Class name: Incorrectly Worn\n" ] }, { - "output_type": "execute_result", "data": { "image/png": "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", "text/plain": [ "" ] }, + "execution_count": 10, "metadata": {}, - "execution_count": 10 + "output_type": "execute_result" } ], - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 11, "source": [ "clear_output()\n", "frame, img = producer_live(cap)\n", "consumer_live(accel, frame)\n", "img" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Class name: No Mask\n" ] }, { - "output_type": "execute_result", "data": { "image/png": "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Erwfc8qqZGYGQiBqo2RwTMyugKkaITgiMyPzZGCghlC07FqviQDMPBMjJLGzx9t3fuv125/7uMXgVLUfW3735Df+zsU7H9z4zL9yUc+mCsYR7PDg3/j5/+Qr/+Bv3n/nW4fVgfYl+PLkcy89970/cXeajQ5ghEJ27wNM90+a09e7k6+049cr3pwc55PX17x2FlgCj+kYicRQiqqamRKzJNVAQ8qTakBMpRSRYppFppIpepeswKUjCGygBo6cmgLRbmcjqkweQREohphHKMLv3blXB5g9eZUOD3ogy44pbb/5v/zBe9988cf+QplfCx4GgrsZPvWln31xLu994w9OT6frN59ZV8vvTFDt43KC00eWi23vfbd69H8dlNeqfJK5fu9hf/z63cVmEwoKx9J0m2FY1EEEiuzEYsOdjGOUBNQIEHLOKmKmpppSqgDdpBPpznsAR4xqKmpmhDv1w4qZZzBllRxCxVOOpYiVb751/2BTrj812l7cxj3RxoXTsH337V/7766+8PnmU/9qCfHRCs4E3niXnm4+3lX9sblHE0+Ip++abqb+/B4df+Pa9K253kW8WJucvv1o/c67cXM+UVAfZpar6ex4HJazToqI6u4DO0Zm74ppQRQk21liZqA7DRdLzv8fz6kM7g19L04AAAAASUVORK5CYII=", "text/plain": [ "" ] }, + "execution_count": 11, "metadata": {}, - "execution_count": 11 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "clear_output()\n", + "frame, img = producer_live(cap)\n", + "consumer_live(accel, frame)\n", + "img" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Release Webcam" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 12, + "metadata": {}, + "outputs": [], "source": [ "cap.release()" - ], - "outputs": [], - "metadata": {} + ] } ], "metadata": { @@ -454,4 +454,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} \ No newline at end of file +} diff --git a/finn_examples/notebooks/4_keyword_spotting.ipynb b/finn_examples/notebooks/4_keyword_spotting.ipynb index c7a200e..4898b76 100644 --- a/finn_examples/notebooks/4_keyword_spotting.ipynb +++ b/finn_examples/notebooks/4_keyword_spotting.ipynb @@ -62,30 +62,6 @@ "print(\"Label shape: \" + str(golden_out_data.shape))" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you get an error for the cell above, it's possible something went wrong with the sample data download/extraction and you can manually download and unzip files by uncommenting the function call in the cell below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import pkg_resources as pk\n", - "import json\n", - "\n", - "#data_folder = pk.resource_filename(\"finn_examples\", \"data\")\n", - "#zip_file_link = pk.resource_filename(\"finn_examples\", \"data/all_validation_kws_data_preprocessed_py_speech.zip.link\")\n", - "#file = open(zip_file_link)\n", - "#zip_file = json.load(file)\n", - "#! wget {zip_file[\"url\"]} -P {data_folder}\n", - "#! unzip {data_folder+\"/all_validation_kws_data_preprocessed_py_speech.zip\"} -d {data_folder}" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -149,8 +125,8 @@ } ], "source": [ - "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal), str(accel.idt)))\n", - "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal), str(accel.odt)))" + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" ] }, { @@ -484,21 +460,6 @@ "!ls $audio_samples_folder" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# uncomment below if you get an error for the sample files\n", - "#data_folder = pk.resource_filename(\"finn_examples\", \"data\")\n", - "#zip_file_link = pk.resource_filename(\"finn_examples\", \"data/audio_samples.zip.link\")\n", - "#file = open(zip_file_link)\n", - "#zip_file = json.load(file)\n", - "#! wget {zip_file[\"url\"]} -P {data_folder}\n", - "#! unzip {data_folder+\"/audio_samples.zip\"} -d {data_folder}" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -618,7 +579,7 @@ } ], "source": [ - "res_label = tf_dataset_labels[res_acc[0,0].astype(np.int)]\n", + "res_label = tf_dataset_labels[res_acc[0,0].astype(int)]\n", "print(f\"The audio file was classified as: {res_label}\")" ] }, @@ -644,7 +605,7 @@ " quant_mfcc_feat = quantize_input(mfcc_feat_py)\n", " accel.batch_size = 1\n", " res_acc = accel.execute(quant_mfcc_feat)\n", - " res_label = tf_dataset_labels[res_acc[0,0].astype(np.int)]\n", + " res_label = tf_dataset_labels[res_acc[0,0].astype(int)]\n", " print(f\"The audio file for {sample_class} was classified as: {res_label}\")" ] } diff --git a/finn_examples/notebooks/5_radioml_with_cnns.ipynb b/finn_examples/notebooks/5_radioml_with_cnns.ipynb index ed3093b..b6b79b7 100755 --- a/finn_examples/notebooks/5_radioml_with_cnns.ipynb +++ b/finn_examples/notebooks/5_radioml_with_cnns.ipynb @@ -2,93 +2,118 @@ "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "# Initialize the accelerator" - ], - "metadata": {} + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Remember to install the following dependencies:" + ] }, { "cell_type": "code", "execution_count": 1, - "source": [ - "# remember to install the following dependencies\n", - "#! apt-get install libhdf5-dev -y\n", - "#! pip3 install versioned-hdf5" - ], + "metadata": {}, "outputs": [], - "metadata": {} + "source": [ + "! apt-get install libhdf5-dev -y\n", + "! pip3 install versioned-hdf5" + ] }, { "cell_type": "code", "execution_count": 2, - "source": [ - "from finn_examples import models\n", - "print(list(filter(lambda x: \"radioml\" in x, dir(models))))" - ], + "metadata": {}, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "['_radioml_io_shape_dict', 'vgg10_w4a4_radioml']\n" ] } ], - "metadata": {} + "source": [ + "from finn_examples import models\n", + "print(list(filter(lambda x: \"radioml\" in x, dir(models))))" + ] }, { "cell_type": "code", "execution_count": 3, + "metadata": {}, + "outputs": [], "source": [ "# Note: the RadioML example is only available on the ZCU104 at the moment\n", "accel = models.vgg10_w4a4_radioml()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 4, - "source": [ - "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal), str(accel.idt)))\n", - "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal), str(accel.odt)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Expected input shape and datatype: (1, 1024, 1, 2) DataType.INT8\n", "Expected output shape and datatype: (1, 1) DataType.UINT8\n" ] } ], - "metadata": {} + "source": [ + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Load RadioML 2018 dataset" - ], - "metadata": {} + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please note that you will have to manually download the RadioML 2018 dataset and set the `dataset_dir` variable to point to its path." + ] }, { "cell_type": "code", "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/home/xilinx/datasets/radioml_2018\n" + ] + } + ], "source": [ "import numpy as np\n", "import math\n", @@ -98,21 +123,13 @@ "\n", "dataset_dir = \"/home/xilinx/datasets/radioml_2018\"\n", "print(dataset_dir)" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "/home/xilinx/datasets/radioml_2018\n" - ] - } - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 6, + "metadata": {}, + "outputs": [], "source": [ "h5_file = h5py.File(dataset_dir + \"/GOLD_XYZ_OSC.0001_1024.hdf5\",'r')\n", "data_h5 = h5_file['X']\n", @@ -142,23 +159,16 @@ "'16APSK','32APSK','64APSK','128APSK','16QAM','32QAM','64QAM','128QAM','256QAM',\n", "'AM-SSB-WC','AM-SSB-SC','AM-DSB-WC','AM-DSB-SC','FM','GMSK','OQPSK']\n", "snr_classes = np.arange(-20., 32., 2) # -20dB to 30dB" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 7, - "source": [ - "print(data_h5.shape)\n", - "print(label_mod.shape)\n", - "print(label_snr.shape)\n", - "print(len(test_indices))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "(2555904, 1024, 2)\n", "(2555904,)\n", @@ -167,18 +177,33 @@ ] } ], - "metadata": {} + "source": [ + "print(data_h5.shape)\n", + "print(label_mod.shape)\n", + "print(label_snr.shape)\n", + "print(len(test_indices))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Inspect a single frame" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Modulation: 16QAM, SNR: 30.0 dB\n" + ] + } + ], "source": [ "%matplotlib inline\n", "from matplotlib import pyplot as plt\n", @@ -193,29 +218,21 @@ "plt.figure()\n", "plt.plot(data)\n", "print(\"Modulation: %s, SNR: %.1f dB\" % (mod_classes[mod], snr))" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Modulation: 16QAM, SNR: 30.0 dB\n" - ] - } - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Input quantization\n", "Quantize input data on-the-fly in software before feeding it to the accelerator. Use the uniform quantization range on which the model was trained." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 9, + "metadata": {}, + "outputs": [], "source": [ "def quantize(data):\n", " quant_min = -2.0\n", @@ -226,102 +243,94 @@ " data_quant = np.clip(data_quant, -128, 127)\n", " data_quant = data_quant.astype(np.int8)\n", " return data_quant" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Classify a single frame" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 10, - "source": [ - "accel_in = quantize(data).reshape(accel.ishape_normal)\n", - "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Input buffer shape is (1, 1024, 1, 2) and datatype is int8\n" ] } ], - "metadata": {} + "source": [ + "accel_in = quantize(data).reshape(accel.ishape_normal())\n", + "print(\"Input buffer shape is %s and datatype is %s\" % (str(accel_in.shape), str(accel_in.dtype)))" + ] }, { "cell_type": "code", "execution_count": 11, + "metadata": {}, + "outputs": [], "source": [ "accel_out = accel.execute(accel_in)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 12, - "source": [ - "print(\"Result: \" + str(accel_out))\n", - "print(\"Top-1 class predicted by the accelerator: \" + mod_classes[int(accel_out)])" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Result: [[12.]]\n", "Top-1 class predicted by the accelerator: 16QAM\n" ] } ], - "metadata": {} + "source": [ + "print(\"Result: \" + str(accel_out))\n", + "print(\"Top-1 class predicted by the accelerator: \" + mod_classes[int(accel_out)])" + ] }, { "cell_type": "code", "execution_count": 13, - "source": [ - "%%timeit\n", - "accel_out = accel.execute(accel_in)" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "1000 loops, best of 3: 822 µs per loop\n" ] } ], - "metadata": {} + "source": [ + "%%timeit\n", + "accel_out = accel.execute(accel_in)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "# Validate accuracy on entire test set" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 14, - "source": [ - "batch_size = 1024\n", - "accel.batch_size = batch_size\n", - "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed), str(accel.oshape_packed)) )\n", - "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_folded), str(accel.oshape_folded)) )\n", - "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_normal), str(accel.oshape_normal)) )" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Accelerator buffer shapes are (1024, 1024, 1, 1, 2) for input, (1024, 1, 1) for output\n", "Accelerator buffer shapes are (1024, 1024, 1, 1, 2) for input, (1024, 1, 1) for output\n", @@ -329,11 +338,38 @@ ] } ], - "metadata": {} + "source": [ + "batch_size = 1024\n", + "accel.batch_size = batch_size\n", + "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_packed()), str(accel.oshape_packed())))\n", + "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_folded()), str(accel.oshape_folded())))\n", + "print(\"Accelerator buffer shapes are %s for input, %s for output\" % (str(accel.ishape_normal()), str(accel.oshape_normal())))" + ] }, { "cell_type": "code", "execution_count": 15, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "batch 0 : total OK 1018 NOK 6\n", + "batch 1 : total OK 2041 NOK 7\n", + "batch 2 : total OK 3059 NOK 13\n", + "batch 3 : total OK 4082 NOK 14\n", + "batch 4 : total OK 4948 NOK 172\n", + "batch 5 : total OK 5682 NOK 462\n", + "batch 6 : total OK 6314 NOK 854\n", + "batch 7 : total OK 7039 NOK 1153\n", + "batch 8 : total OK 8024 NOK 1192\n", + "batch 9 : total OK 8648 NOK 1192\n" + ] + } + ], "source": [ "ok = 0\n", "nok = 0\n", @@ -346,7 +382,7 @@ " batch_indices = test_indices[i_frame:i_frame+batch_size]\n", " data, mod, snr = data_h5[batch_indices], label_mod[batch_indices], label_snr[batch_indices]\n", "\n", - " ibuf = quantize(data).reshape(accel.ishape_normal)\n", + " ibuf = quantize(data).reshape(accel.ishape_normal())\n", " obuf = accel.execute(ibuf)\n", "\n", " pred = obuf.reshape(batch_size).astype(int)\n", @@ -355,64 +391,39 @@ " nok += np.not_equal(pred, mod).sum().item()\n", " \n", " print(\"batch %d : total OK %d NOK %d\" % (i_batch, ok, nok))" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "batch 0 : total OK 1018 NOK 6\n", - "batch 1 : total OK 2041 NOK 7\n", - "batch 2 : total OK 3059 NOK 13\n", - "batch 3 : total OK 4082 NOK 14\n", - "batch 4 : total OK 4948 NOK 172\n", - "batch 5 : total OK 5682 NOK 462\n", - "batch 6 : total OK 6314 NOK 854\n", - "batch 7 : total OK 7039 NOK 1153\n", - "batch 8 : total OK 8024 NOK 1192\n", - "batch 9 : total OK 8648 NOK 1192\n" - ] - } - ], - "metadata": { - "scrolled": true - } + ] }, { "cell_type": "code", "execution_count": 16, - "source": [ - "acc = 100.0 * ok / (total)\n", - "print(\"Overall top-1 accuracy: {}%\".format(acc))" - ], + "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Overall top-1 accuracy: 87.88617886178862%\n" ] } ], - "metadata": {} + "source": [ + "acc = 100.0 * ok / (total)\n", + "print(\"Overall top-1 accuracy: {}%\".format(acc))" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## More benchmarking" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": 17, - "source": [ - "accel.batch_size = 1024\n", - "accel.throughput_test()" - ], + "metadata": {}, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "{'DRAM_in_bandwidth[Mb/s]': 473.18806940706867,\n", @@ -429,18 +440,22 @@ " 'unpack_output[ms]': 0.6284713745117188}" ] }, + "execution_count": 17, "metadata": {}, - "execution_count": 17 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "accel.batch_size = 1024\n", + "accel.throughput_test()" + ] }, { "cell_type": "code", "execution_count": null, - "source": [], + "metadata": {}, "outputs": [], - "metadata": {} + "source": [] } ], "metadata": { @@ -464,4 +479,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/finn_examples/notebooks/6_cybersecurity_with_mlp.ipynb b/finn_examples/notebooks/6_cybersecurity_with_mlp.ipynb new file mode 100644 index 0000000..7d946e5 --- /dev/null +++ b/finn_examples/notebooks/6_cybersecurity_with_mlp.ipynb @@ -0,0 +1,501 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "adf1903e", + "metadata": {}, + "source": [ + "# Initialize the accelerator" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5f012d0d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['_unsw_nb15_mlp_io_shape_dict', 'mlp_w2a2_unsw_nb15']\n" + ] + } + ], + "source": [ + "from finn_examples import models\n", + "print(list(filter(lambda x: \"unsw_nb15\" in x, dir(models))))" + ] + }, + { + "cell_type": "markdown", + "id": "1e8446d7", + "metadata": {}, + "source": [ + "Specify a batch size & create the FINN overlay. Note that the batch size must divide 82000." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ac7e32e0", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "\ntry {\nrequire(['notebook/js/codecell'], function(codecell) {\n codecell.CodeCell.options_default.highlight_modes[\n 'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n Jupyter.notebook.get_cells().map(function(cell){\n if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n });\n});\n} catch (e) {};\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "batch_size = 1\n", + "accel = models.mlp_w2a2_unsw_nb15()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "912943c1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Expected input shape and datatype: (1, 600) BIPOLAR\n", + "Expected output shape and datatype: (1, 1) BIPOLAR\n" + ] + } + ], + "source": [ + "print(\"Expected input shape and datatype: %s %s\" % (str(accel.ishape_normal()), str(accel.idt())))\n", + "print(\"Expected output shape and datatype: %s %s\" % (str(accel.oshape_normal()), str(accel.odt())))" + ] + }, + { + "cell_type": "markdown", + "id": "d47e899e", + "metadata": {}, + "source": [ + "# Load the binarized UNSW-NB15 test dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "69ba8798", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "File ‘unsw_nb15_binarized.npz’ already there; not retrieving.\r\n" + ] + } + ], + "source": [ + "! wget -nc -O unsw_nb15_binarized.npz https://zenodo.org/record/4519767/files/unsw_nb15_binarized.npz?download=1" + ] + }, + { + "cell_type": "markdown", + "id": "ebc2ae98", + "metadata": {}, + "source": [ + "Note that the generated design expects inputs of length 600. As explained in the [end-to-end notebook](https://github.com/Xilinx/finn/blob/main/notebooks/end2end_example/cybersecurity/1-train-mlp-with-brevitas.ipynb) in the FINN repository, padding the input data from length 593 to 600 enables SIMD parallelization for the first layer.\n", + "Thus, we'll have to pad our dataset before feeding it to the accelerator." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6631508a", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def make_unsw_nb15_test_batches(bsize):\n", + " unsw_nb15_data = np.load(\"unsw_nb15_binarized.npz\")[\"test\"][:82000]\n", + " test_imgs = unsw_nb15_data[:, :-1]\n", + " test_imgs = np.pad(test_imgs, [(0, 0), [0, 7]], mode=\"constant\")\n", + " test_labels = unsw_nb15_data[:, -1]\n", + " n_batches = int(test_imgs.shape[0] / bsize)\n", + " test_imgs = test_imgs.reshape(n_batches, bsize, -1)\n", + " test_labels = test_labels.reshape(n_batches, bsize)\n", + " return (test_imgs, test_labels)" + ] + }, + { + "cell_type": "markdown", + "id": "69dbb1d3", + "metadata": {}, + "source": [ + "# Classify a single attack" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a92e1b11", + "metadata": {}, + "outputs": [], + "source": [ + "(test_imgs, test_labels) = make_unsw_nb15_test_batches(bsize=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "48f308f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Expected label is 0 (normal data)\n" + ] + } + ], + "source": [ + "test_single = test_imgs[-1]\n", + "test_single_label = test_labels[-1].astype(np.float32)\n", + "\n", + "print(\"Expected label is: %d (%s data)\" % (test_single_label, (lambda x: \"normal\" if x==0 else \"abnormal\")(test_single_label)))" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "fc6e39fa", + "metadata": {}, + "outputs": [], + "source": [ + "# Note: the accelerator expects binary input data presented in bipolar form (i.e. {-1, 1})\n", + "accel_in = 2 * test_single - 1\n", + "accel_out = accel.execute(accel_in)\n", + "# To convert back to the original label (i.e. {0, 1}), we'll have to map the bipolar output to binary\n", + "accel_out_binary = (accel_out + 1) / 2" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "5c273dd1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Returned label is 0 (normal data)\n" + ] + } + ], + "source": [ + "print(\"Returned label is: %d (%s data)\" % (accel_out_binary, (lambda x: \"normal\" if x==0 else \"abnormal\")(accel_out_binary)))" + ] + }, + { + "cell_type": "markdown", + "id": "1149e06f", + "metadata": {}, + "source": [ + "# Validate accuracy on 82000 (out of 82332) records from UNSW-NB15 test set" + ] + }, + { + "cell_type": "markdown", + "id": "64b841f2", + "metadata": {}, + "source": [ + "To increase the throughput, let's increase the batch size. Note that the FINN accelerator operates on a batch size of 1, but to fill the compute pipeline, we'll copy a greater chunk of the test set to the device buffer." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "f51e3e49", + "metadata": {}, + "outputs": [], + "source": [ + "batch_size = 1000\n", + "accel.batch_size = batch_size\n", + "(test_imgs, test_labels) = make_unsw_nb15_test_batches(batch_size)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "f8c88f49", + "metadata": {}, + "outputs": [], + "source": [ + "ok = 0\n", + "nok = 0\n", + "n_batches = test_imgs.shape[0]\n", + "total = batch_size*n_batches" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "a26358f9", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "batch 1 / 82 : total OK 866 NOK 134\n", + "batch 2 / 82 : total OK 1706 NOK 294\n", + "batch 3 / 82 : total OK 2607 NOK 393\n", + "batch 4 / 82 : total OK 3490 NOK 510\n", + "batch 5 / 82 : total OK 4438 NOK 562\n", + "batch 6 / 82 : total OK 5380 NOK 620\n", + "batch 7 / 82 : total OK 6290 NOK 710\n", + "batch 8 / 82 : total OK 7261 NOK 739\n", + "batch 9 / 82 : total OK 8174 NOK 826\n", + "batch 10 / 82 : total OK 9109 NOK 891\n", + "batch 11 / 82 : total OK 10026 NOK 974\n", + "batch 12 / 82 : total OK 10963 NOK 1037\n", + "batch 13 / 82 : total OK 11955 NOK 1045\n", + "batch 14 / 82 : total OK 12950 NOK 1050\n", + "batch 15 / 82 : total OK 13948 NOK 1052\n", + "batch 16 / 82 : total OK 14948 NOK 1052\n", + "batch 17 / 82 : total OK 15947 NOK 1053\n", + "batch 18 / 82 : total OK 16947 NOK 1053\n", + "batch 19 / 82 : total OK 17947 NOK 1053\n", + "batch 20 / 82 : total OK 18946 NOK 1054\n", + "batch 21 / 82 : total OK 19946 NOK 1054\n", + "batch 22 / 82 : total OK 20945 NOK 1055\n", + "batch 23 / 82 : total OK 21942 NOK 1058\n", + "batch 24 / 82 : total OK 22939 NOK 1061\n", + "batch 25 / 82 : total OK 23938 NOK 1062\n", + "batch 26 / 82 : total OK 24938 NOK 1062\n", + "batch 27 / 82 : total OK 25938 NOK 1062\n", + "batch 28 / 82 : total OK 26938 NOK 1062\n", + "batch 29 / 82 : total OK 27938 NOK 1062\n", + "batch 30 / 82 : total OK 28938 NOK 1062\n", + "batch 31 / 82 : total OK 29938 NOK 1062\n", + "batch 32 / 82 : total OK 30938 NOK 1062\n", + "batch 33 / 82 : total OK 31938 NOK 1062\n", + "batch 34 / 82 : total OK 32938 NOK 1062\n", + "batch 35 / 82 : total OK 33938 NOK 1062\n", + "batch 36 / 82 : total OK 34938 NOK 1062\n", + "batch 37 / 82 : total OK 35938 NOK 1062\n", + "batch 38 / 82 : total OK 36938 NOK 1062\n", + "batch 39 / 82 : total OK 37937 NOK 1063\n", + "batch 40 / 82 : total OK 38937 NOK 1063\n", + "batch 41 / 82 : total OK 39880 NOK 1120\n", + "batch 42 / 82 : total OK 40845 NOK 1155\n", + "batch 43 / 82 : total OK 41807 NOK 1193\n", + "batch 44 / 82 : total OK 42640 NOK 1360\n", + "batch 45 / 82 : total OK 43252 NOK 1748\n", + "batch 46 / 82 : total OK 43917 NOK 2083\n", + "batch 47 / 82 : total OK 44605 NOK 2395\n", + "batch 48 / 82 : total OK 45358 NOK 2642\n", + "batch 49 / 82 : total OK 46111 NOK 2889\n", + "batch 50 / 82 : total OK 46901 NOK 3099\n", + "batch 51 / 82 : total OK 47700 NOK 3300\n", + "batch 52 / 82 : total OK 48504 NOK 3496\n", + "batch 53 / 82 : total OK 49355 NOK 3645\n", + "batch 54 / 82 : total OK 50179 NOK 3821\n", + "batch 55 / 82 : total OK 51106 NOK 3894\n", + "batch 56 / 82 : total OK 51988 NOK 4012\n", + "batch 57 / 82 : total OK 52928 NOK 4072\n", + "batch 58 / 82 : total OK 53801 NOK 4199\n", + "batch 59 / 82 : total OK 54701 NOK 4299\n", + "batch 60 / 82 : total OK 55548 NOK 4452\n", + "batch 61 / 82 : total OK 56393 NOK 4607\n", + "batch 62 / 82 : total OK 57198 NOK 4802\n", + "batch 63 / 82 : total OK 58004 NOK 4996\n", + "batch 64 / 82 : total OK 58766 NOK 5234\n", + "batch 65 / 82 : total OK 59543 NOK 5457\n", + "batch 66 / 82 : total OK 60307 NOK 5693\n", + "batch 67 / 82 : total OK 61290 NOK 5710\n", + "batch 68 / 82 : total OK 62205 NOK 5795\n", + "batch 69 / 82 : total OK 63128 NOK 5872\n", + "batch 70 / 82 : total OK 64082 NOK 5918\n", + "batch 71 / 82 : total OK 65053 NOK 5947\n", + "batch 72 / 82 : total OK 66017 NOK 5983\n", + "batch 73 / 82 : total OK 66978 NOK 6022\n", + "batch 74 / 82 : total OK 67839 NOK 6161\n", + "batch 75 / 82 : total OK 68703 NOK 6297\n", + "batch 76 / 82 : total OK 69624 NOK 6376\n", + "batch 77 / 82 : total OK 70600 NOK 6400\n", + "batch 78 / 82 : total OK 71577 NOK 6423\n", + "batch 79 / 82 : total OK 72551 NOK 6449\n", + "batch 80 / 82 : total OK 73459 NOK 6541\n", + "batch 81 / 82 : total OK 74418 NOK 6582\n", + "batch 82 / 82 : total OK 75357 NOK 6643\n" + ] + } + ], + "source": [ + "for i in range(n_batches):\n", + " inp = test_imgs[i].astype(np.float32)\n", + " exp = test_labels[i].astype(np.float32)\n", + " inp = 2 * inp - 1\n", + " exp = 2 * exp - 1\n", + " out = accel.execute(inp)\n", + " matches = np.count_nonzero(out.flatten() == exp.flatten())\n", + " nok += batch_size - matches\n", + " ok += matches\n", + " print(\"batch %d / %d : total OK %d NOK %d\" % (i + 1, n_batches, ok, nok))" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "98af33f1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final accuracy: 91.898780\n" + ] + } + ], + "source": [ + "acc = 100.0 * ok / (total)\n", + "print(\"Final accuracy: {:.2f}%\".format(acc))" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "c7d63354", + "metadata": {}, + "outputs": [], + "source": [ + "def run_validation():\n", + " for i in range(n_batches):\n", + " ibuf_normal = test_imgs[i].reshape(accel.ishape_normal())\n", + " accel.execute(ibuf_normal)" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "e6a7fa9d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "33.3 s ± 698 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "full_validation_time = %timeit -n 1 -o run_validation()" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "e2dde028", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2542.157784 images per second including data movement\n" + ] + } + ], + "source": [ + "print(\"%f images per second including data movement\" % (total / float(full_validation_time.best)))" + ] + }, + { + "cell_type": "markdown", + "id": "f95c0d3a", + "metadata": {}, + "source": [ + "# More benchmarking" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "f79491c3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'runtime[ms]': 1.0788440704345703,\n", + " 'throughput[images/s]': 926918.0110497237,\n", + " 'DRAM_in_bandwidth[MB/s]': 69.51885082872928,\n", + " 'DRAM_out_bandwidth[MB/s]': 0.9269180110497238,\n", + " 'fclk[mhz]': 100.0,\n", + " 'batch_size': 1000,\n", + " 'fold_input[ms]': 0.09775161743164062,\n", + " 'pack_input[ms]': 71.11644744873047,\n", + " 'copy_input_data_to_device[ms]': 2.642393112182617,\n", + " 'copy_output_data_from_device[ms]': 0.2548694610595703,\n", + " 'unpack_output[ms]': 355.4694652557373,\n", + " 'unfold_output[ms]': 0.05626678466796875}" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "accel.throughput_test()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "9ca5c916", + "metadata": {}, + "source": [ + "The measured `throughput` of the accelerator, excluding any software and data movement overhead, is influenced by the batch size. The more we fill the compute pipeline, the higher the throughput.\n", + "Note that the total runtime consists of the overhead of packing/unpacking the inputs/outputs to convert form numpy arrays to the bit-contiguous data representation our accelerator expectes (`pack_input`/`unpack_output`), the cost of moving data between the CPU and accelerator memories (`copy_input_data_to_device`/`copy_output_data_from_device`), as well as the accelerator's execution time." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/finn_examples/qonnx/core/datatype.py b/finn_examples/qonnx/core/datatype.py index 1fac858..7adc272 100644 --- a/finn_examples/qonnx/core/datatype.py +++ b/finn_examples/qonnx/core/datatype.py @@ -78,7 +78,6 @@ def max(self): @abstractmethod def allowed(self, value): """Check whether given value is allowed for this DataType. - * value (float32): value to be checked""" pass @@ -105,12 +104,12 @@ def get_hls_datatype_str(self): @abstractmethod def to_numpy_dt(self): - "Return an appropriate numpy datatype that can represent this FINN DataType." + "Return an appropriate numpy datatype that can represent this QONNX DataType." pass @abstractmethod def get_canonical_name(self): - "Return a canonical string representation of this FINN DataType." + "Return a canonical string representation of this QONNX DataType." class FloatType(BaseDataType): @@ -339,8 +338,8 @@ def __getitem__(self, name): class DataType(Enum, metaclass=DataTypeMeta): - """Enum class that contains FINN data types to set the quantization annotation. - ONNX does not support data types smaller than 8-bit integers, whereas in FINN we are + """Enum class that contains QONNX data types to set the quantization annotation. + ONNX does not support data types smaller than 8-bit integers, whereas in QONNX we are interested in smaller integers down to ternary and bipolar.""" @staticmethod @@ -362,4 +361,4 @@ def get_smallest_possible(value): dt = DataType[cand] if (dt.min() <= value) and (value <= dt.max()): return dt - raise Exception("Could not find a suitable int datatype for " + str(value)) + raise Exception("Could not find a suitable int datatype for " + str(value)) \ No newline at end of file diff --git a/finn_examples/qonnx/util/basic.py b/finn_examples/qonnx/util/basic.py index cc467b1..ee948dd 100644 --- a/finn_examples/qonnx/util/basic.py +++ b/finn_examples/qonnx/util/basic.py @@ -34,6 +34,33 @@ from qonnx.core.datatype import DataType +# TODO solve by moving onnx-dependent fxns to onnx.py +# finn-examples uses parts of qonnx without having +# onnx installed and doesn't use this functionality +# workaround to avoid import errors when onnx isn't +# installed: +try: + from onnx.helper import make_model, make_opsetid +except ModuleNotFoundError: + make_model = None + make_opsetid = None + + +def get_preferred_onnx_opset(): + "Return preferred ONNX opset version for QONNX" + return 11 + + +def qonnx_make_model(graph_proto, **kwargs): + "Wrapper around ONNX make_model with preferred qonnx opset version" + opset_imports = kwargs.pop("opset_imports", None) + if opset_imports is None: + opset_imports = [make_opsetid("", get_preferred_onnx_opset())] + kwargs["opset_imports"] = opset_imports + else: + kwargs["opset_imports"] = opset_imports + return make_model(graph_proto, **kwargs) + def is_finn_op(op_type): "Return whether given op_type string is a QONNX or FINN custom op" @@ -181,35 +208,22 @@ def pad_tensor_to_multiple_of(ndarray, pad_to_dims, val=0, distr_pad=False): return ret -def calculate_matvec_accumulator_range(matrix, vec_dt): +def calculate_matvec_accumulator_range(matrix: np.ndarray, vec_dt: DataType): """Calculate the minimum and maximum possible result (accumulator) values for a dot product x * A, given matrix A of dims (MW, MH), and vector (1, MW) with datatype vec_dt. Returns (acc_min, acc_max). """ - min_weight = matrix.min() - max_weight = matrix.max() - perceptive_field_elems = matrix.shape[0] - min_input = vec_dt.min() - max_input = vec_dt.max() - # calculate minimum and maximum values of accumulator - # assume inputs span the whole range of the input datatype - acc_min = perceptive_field_elems * min( - min_weight * max_input, - min_weight * min_input, - max_weight * max_input, - max_weight * min_input, - ) - acc_max = perceptive_field_elems * max( - min_weight * max_input, - min_weight * min_input, - max_weight * max_input, - max_weight * min_input, - ) - return (acc_min, acc_max) + max_weight = abs(matrix).sum(axis=0).max() + max_input = max(abs(vec_dt.min()), abs(vec_dt.max())) + max_value = max_input * max_weight + # If either the weight and input datatypes are signed, then the minimum + # value that their accumulated product can be is -max_value. Else, it's 0. + min_value = -max_value if (matrix.min() < 0) or vec_dt.signed() else 0 + return (min_value, max_value) def gen_finn_dt_tensor(finn_dt, tensor_shape): - """Generates random tensor in given shape and with given FINN DataType.""" + """Generates random tensor in given shape and with given QONNX DataType.""" if type(tensor_shape) == list: tensor_shape = tuple(tensor_shape) if finn_dt == DataType["BIPOLAR"]: @@ -255,10 +269,8 @@ def sanitize_quant_values(model, node_tensors, execution_context, check_values=F that are supposed to be integers (as indicated by their quantization annotation). Will raise an assertion if the amount of rounding is too large. Returns the sanitized execution context. - If check_values is specified, an extra DataType.allowed() check will be performed on any rounded tensors. - Background: QONNX uses floating point tensors as a carrier data type to represent integers. Floating point arithmetic can introduce rounding errors, e.g. @@ -305,9 +317,9 @@ def sanitize_quant_values(model, node_tensors, execution_context, check_values=F execution_context[tensor_name] = updated_values else: raise Exception( - """Rounding error is too high to match set FINN + """Rounding error is too high to match set QONNX datatype ({}) for input {}""".format( dtype, tensor_name ) ) - return execution_context + return \ No newline at end of file diff --git a/setup.py b/setup.py index d7aef96..4708827 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ import os import zipfile from distutils.command.build import build as dist_build -from pynq.utils import build_py as _build_py +from pynqutils.setup_utils import build_py as _build_py __author__ = "Yaman Umuroglu" __copyright__ = "Copyright 2020-2021, Xilinx"