GithubHelp home page GithubHelp logo

es-ude / elastic-ai.creator Goto Github PK

View Code? Open in Web Editor NEW
13.0 5.0 2.0 2.92 MB

elastic ai.creator

License: MIT License

Python 89.02% VHDL 10.89% JavaScript 0.09%
artifical-intelligence embedded-systems deep-learning fpga

elastic-ai.creator's Introduction

ElasticAI.creator

Design, train and compile neural networks optimized specifically for FPGAs. Obtaining a final model is typically a three stage process.

  • design and train it using the layers provided in the elasticai.creator.nn package.
  • translate the model to a target representation, e.g. VHDL
  • compile the intermediate representation with a third party tool, e.g. Xilinx Vivado (TM)

This version currently only supports parts of VHDL as target representations.

The project is part of the elastic ai ecosystem developed by the Embedded Systems Department of the University Duisburg-Essen. For more details checkout the slides at researchgate.

Table of contents

Users Guide

Install

You can install the ElasticAI.creator as a dependency using pip:

python3 -m pip install elasticai-creator

Minimal Example

The following example shows how to use the ElasticAI.creator to define and translate a machine learning model to VHDL. It will save the generated VHDL code to a directory called build_dir.

from elasticai.creator.nn import Sequential
from elasticai.creator.nn.fixed_point import Linear, HardSigmoid
from elasticai.creator.file_generation.on_disk_path import OnDiskPath


def main() -> None:
    # Define a model
    model = Sequential(
        Linear(in_features=10, out_features=2, bias=True, total_bits=16, frac_bits=8),
        HardSigmoid(total_bits=16, frac_bits=8),
    )

    # Train the model
    run_training(model)

    # Save the VHDL code of the trained model
    destination = OnDiskPath("build_dir")
    design = model.create_design("my_model")
    design.save_to(destination)


if __name__ == "__main__":
    main()

General Limitations

By now we only support Sequential models for our translations.

Structure of the Project

The structure of the project is as follows. The creator folder includes all main concepts of our project, especially the qtorch implementation which is our implementation of quantized PyTorch layer. It also includes the supported target representations, like the subfolder nn is for the translation to vhdl. Additionally, we have unit and integration tests in the tests folder.

Developers Guide

Install Dev Dependencies

pip install poetry
poetry install
poetry shell
pre-commit install
npm install --save-dev @commitlint/{config-conventional,cli}

# Optional:
sudo apt install ghdl

Project Structure

All packages and modules fall into one of five main categories and can thus be found in the corresponding package

  • nn: trainable modules that can be translated to vhdl to build a hardware accelerator
  • base_modules: (non-public) shared functionality and data structures, that we use to create our neural network software modules
  • vhdl: (non-public) shared code that we use to represent and generate vhdl code
  • file_generation: provide a very restricted api to generate files or file subtrees under a given root node and defines a basic template api and datastructure, compatible with file_generation

Glossary

  • fxp/Fxp: prefix for fixed point
  • flp/Flp: prefix for floating point
  • x: parameter input tensor for layer with single input tensor
  • y: output value/tensor for layer with single output
  • _bits: suffix to denote the number of bits, e.g. total_bits, frac_bits, in python context
  • _width: suffix to denote the number of bits used for a data bus in vhdl, e.g. total_width, frac_width
  • MathOperations/operations: definition of how to perform mathematical operations (quantization, addition, matrix multiplication, ...)

Conventional Commit Rules

We use conventional commits (see here). The following commit types are allowed. The message scope is optional.

Commit Types
feat
fix
docs
style
refactor
revert
chore
wip
perf

Tests

Our implementation is tested with unit and integration. You can run one explicit test with the following statement:

python3 -m pytest ./tests/path/to/specific/test.py

If you want to run all tests, give the path to the tests:

python3 -m pytest ./tests

If you want to add more tests please refer to the Test Guidelines in the following.

Test Style Guidelines

File IO

In general try to avoid interaction with the filesystem. In most cases instead of writing to or reading from a file you can use a StringIO object or a StringReader. If you absolutely have to create files, be sure to use pythons tempfile module and cleanup after the tests. In most cases you can use the InMemoryPath class to write files to the RAM instead of writing them to the hard disc (especially for testing the generated VHDL files of a certain layer).

Directory structure and file names

Files containing tests for a python module should be located in a test directory for the sake of separation of concerns. Each file in the test directory should contain tests for one and only one class/function defined in the module. Files containing tests should be named according to the rubric test_<class_name>.py. Next, if needed for more specific tests define a class. Then subclass it. It avoids introducing the category of bugs associated with copying and pasting code for reuse. This class should be named similarly to the file name. There's a category of bugs that appear if the initialization parameters defined at the top of the test file are directly used: some tests require the initialization parameters to be changed slightly. Its possible to define a parameter and have it change in memory as a result of a test. Subsequent tests will therefore throw errors. Each class contains methods that implement a test. These methods are named according to the rubric test_<name>_<condition>

Unit tests

In those tests each functionality of each function in the module is tested, it is the entry point when adding new functions. It assures that the function behaves correctly independently of others. Each test has to be fast, so use of heavier libraries is discouraged. The input used is the minimal one needed to obtain a reproducible output. Dependencies should be replaced with mocks as needed.

Integration Tests

Here the functions' behaviour with other modules is tested. In this repository each integration function is in the correspondent folder. Then the integration with a single class of the target, or the minimum amount of classes for a functionality, is tested in each separated file.

System tests

Those tests will use every component of the system, comprising multiple classes. Those tests include expected use cases and unexpected or stress tests.

Adding new functionalities and tests required

When adding new functions to an existing module, add unit tests in the correspondent file in the same order of the module, if a new module is created a new file should be created. When a bug is solved created the respective regression test to ensure that it will not return. Proceed similarly with integration tests. Creating a new file if a functionality completely different from the others is created e.g. support for a new layer. System tests are added if support for a new library is added.

Updating tests

If new functionalities are changed or removed the tests are expected to reflect that, generally the ordering is unit tests -> integration tests-> system tests. Also, unit tests that change the dependencies should be checked, since this system is fairly small the internal dependencies are not always mocked.

references: https://jrsmith3.github.io/python-testing-style-guidelines.html

elastic-ai.creator's People

Contributors

actions-user avatar ahmadhusein5853 avatar edwina1030 avatar glencoe avatar julianhoever avatar rodrigowissa avatar sillobear avatar superchange001 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

mfkiwl

elastic-ai.creator's Issues

pytorch api for input_domain.py

The input_domain module heavily relies on python standard lib and numpy, since we seem to mostly deal with tensors anyway we should try to stick to the pytorch api as much as possible.

get rid of QConv1d and co.

With pytorch 3.10 the concept of parametrization was introduced... Now pytorch provides a native way for us to inject and apply arbitrary modules to other modules parameters before they are used... E.g. A module behaving equivalently to QConv1d with Binarize can be built like this

import torch
from torch.nn import Conv1d
from torch.nn.utils.parametrize import register_parametrization
from elasticai.creator.layers import Binarize

layer = Conv1d(in_channels=2,
               out_channels=3,
               kernel_size=(1,),
               bias=False)

register_parametrization(layer, "weight", Binarize())

Therefore neither the implementations of our quantizable convolutions nor of our qlstm cells should [not] be needed anymore.
If we decide to still keep the implementations we should implement them with the help of parametrization.

Also see: https://pytorch.org/tutorials/intermediate/parametrizations.html

precomputable as decorator for classes

currently the precomputable function in elasticai.creator.precomputation takes an instance of a module... This allows us to modify instances of classes to attach to them the information we need to perform precomputations. However i guess it would be easier to read if we tagged classes as precomputable like so:

@precomputable(items=[-1, 1], input_generator=my_input_generator)
class MyCustomModule(torch.nn.Module):
  ...

use the ghdl syntax checker

ghdl has a syntax checker which is useful for us for files that do not have a testbench. Check which command is needed for this and add it in the readme. Chao already mentioned elaborate could be the command for this

Example Case 8 or 16bit LSTM

Provide a simple example case for an LSTM with fixed point weights of 8 or 16 bit. This should allow us to get the fixed point weights from the model for verifying the hardware implementation in progress. Maybe we can use easily accessible training data from torchaudio? In a later step we could generate the corresponding testbench from that LSTM example already, but that's for a future issue.

Serialize Precomputations to JSON

Currently Precomputation has a dump_to(f) method. To encode as JSON and dump the result to a file-like object f. Instead we should see if there is a way to make the Precomputation JSON-serializable instead, so the following becomes possible:

with open("precomputations.json", "w") as f:
  for precomputation in precomputations:
    json.dump(precomputation)

or even

with open("precomputations.json", "w") as f:
  json.dump(precomputations)

run doctests automatically

run the python doctests automatically before we merge back into main to make sure documentation is mostly up to date.

Generating BRAMs to store weights and bias for lstm cell

Storing weight and bias on FPGA with BRAM is quite usual. And I want a feature from our creator tool that can generate a rom_name.vhd file by sending a floating array to it. Sure I also will specify the bit with for a fixed point and the bit with for its fractional part.

I made a Jupyter-notebook and an HDL template. In the template, all arguments that can be modified are with a prefix PARAM_, e.g, PARAM_ROM_NAME, PARAM_ADDR_WIDTH, PARAM_DATA_WITH. The Jupyter-notebook also gives an example of how these arguments are defined.

LSTMCell parameters effectively hard coded

and

lstm_single_cell = _elastic_ai_creator_lstm()

cannot be correct. This seems like we are always generating the exact same LSTM Cell every time.

To fix this create two different LSTM Cells in tests and make the tests pass.
Additionally the module that generates the vhdl implementation should not depend directly on the QLSTM cell.

Check if simple templates can be used for code generation

There are still quite some vhdl components that are built from scratch by using a representation of the formal grammar of vhdl. That grammar (in elasticai.creator.vhdl.language) was originally started to generate the vhdl implementations for precomputed components like the Sigmoid and Tanh functions, since more than 80% of the contents for these files need to be generated.
However for many other components we can just use a template and fill in a few fields, e.g., just the field for the bitwidths and entity_name. See https://github.com/es-ude/elastic-ai.creator/pull/83/files#diff-0365f931810180507db0470eda0743afec6363dc6a4fc8f95f58b5793ac177f6 for an example.

An additional advantage of templates is that they are easier to understand for hardware designers.

The following code generators should be checked as well to see if using simple templates makes more sense for them. For each of them, check whether or not to use a template. In case we should rather use a template, create a new issue for changing that code generator.

merge tanh and sigmoid process

For now sigmoid and tanh generation has lots of duplication's which can be merged and outsourced in a general precomputed scalar function

Shape Inference Warnings for ONNX exports

Currently we're producing the following warning

The shape inference of elasticai.creator::Wrapper type is missing, so it may result in wrong shape inference for the exported graph. Please consider adding it in symbolic function.

when exporting to onnx. I do not know atm how to add the shape inference to the symbolic function, but i think we should really do it.

Use VHDL Formatter for indentation of generated files

see #47
note: this should be solved in very naive, straight-forward way, e.g. calling the tool from the main module, or even calling it via the operating system, e.g. subprocess.

The intended behaviour is to change the exisiting file not create a new one

change folder structure for vhd files

put vhd files in highest directory, add static files and copy them, when generating files, give user the possibility to define where generated files should be located

IOTableBuilder

Currently the created IOTables exactly match the data that was passed to the corresponding pytorch module. E.g.:

>> class Simple(torch.nn.Module):
>>   def forward(x):
>>     return x*torch.tensor([2, 1])
>>
>> x = torch.tensor([[1, 1], [0, 0]])
>> p = Precomputation(module, x)
>> p()
>> print(p.output.tolist())
[[2, 1], [0, 0]]

The Precomputation class does not need any information about how inputs and outputs are connected with each other inside the module. However for creating the final IOTable, especially on hardware it makes a massive difference whether we can express the above operation as two tables

input0 input1 output0
1 1 2
input0 input1 output1
1 1 1

or as two tables

input0 output0
1 2
input1 output1
1 1

I don't think we should add functionality to the precomputation class. My current plan is to add write a function or class that is responsible for formatting or splitting a bigger table into smaller ones according to how inputs and outputs are connected. In the old code base we already had code to format the tables according to an implicit contract. However we decide to solve this, the rules that we use to format the tables should be as explicit and obvious as possible.

Basic Fixed Point quantization functions

As an AI designer i want to be able to train my network with a specific fixed point number representation, so the corresponding representation can then be used on hardware.

Comment: I'd propose to implement quantization functions similar to the fake quant in pytorch with a straight-through estimator. But we should also talk about how related publications, e.g. FINN+Brevitas, are handling this. However from what i remember the rough idea is mostly the same.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.