diff --git a/.gitignore b/.gitignore index f37378e300efeb5362882eb8d6eb59f028563a0e..67ffbefbdc41ea1abebd64602649fb129f2faf07 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ __pycache__ *.pyc *.egg-info dist*/ +aidge_export_cpp/_version.py +wheelhouse/* # Mermaid *.mmd @@ -19,4 +21,4 @@ dist*/ xml*/ # ONNX -*.onnx \ No newline at end of file +*.onnx diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..ba0bf4b4acd33d20324f919422346eeb18abea4e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,23 @@ +############################################################################### +# Aidge Continuous Integration and Deployment # +# # +############################################################################### + +stages: + - static_analysis + - build + - test + - coverage + - release + - deploy + +include: + - project: 'eclipse/aidge/gitlab_shared_files' + ref: 'main' + file: + #Â choose which jobs to run by including the corresponding files. + - '.gitlab/ci/ubuntu_python.gitlab-ci.yml' + - '.gitlab/ci/release/pip.gitlab-ci.yml' + + # Since aidge_export_cpp is a pure python package building on windows and on ubuntu doesn't differ + # - '.gitlab/ci/windows_python.gitlab-ci.yml' diff --git a/.gitlab/ci/cibuildwheel_build_deps_before_build_wheel.sh b/.gitlab/ci/cibuildwheel_build_deps_before_build_wheel.sh new file mode 100755 index 0000000000000000000000000000000000000000..4f74488ae41714a4ce03ba7514bf93842768c5ae --- /dev/null +++ b/.gitlab/ci/cibuildwheel_build_deps_before_build_wheel.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -e +if [[ "$1" == "" ]]; then + echo "build aidge deps in cibuildwheel container before building wheel." + echo "search path defines where the dependencies will be searched." + echo "Hint : In wheel containers, files are mounted on /host by default." + echo "\nusage : ./cibuildwheel_build_deps_before_build_wheel.sh $search_path" +fi +set -x +if [[ $AIDGE_DEPENDENCIES == "" ]]; then # case for aidge_ core + mkdir -p build # creating build if its not already there to hold the build of cpp files + rm -rf build/* # build from scratch +else + for repo in $AIDGE_DEPENDENCIES ; do # case for other projects + search_path=$1 + REPO_PATH=$(find $search_path ! -writable -prune -o -type d \ + -name "$repo" \ + -not -path "*/install/*" \ + -not -path "*/.git/*" \ + -not -path "*/miniconda/*" \ + -not -path "*/conda/*" \ + -not -path "*/.local/*" \ + -not -path "*/lib/*" \ + -not -path "*/$repo/$repo/*" \ + -not -path "*/proc/*" \ + -print -quit) + if [[ -z "$REPO_PATH" ]]; then + echo "ERROR : dependency $repo not found in search_path \"$search_path\". ABORTING." + exit -1 + fi + + cd $REPO_PATH + mkdir -p build # creating build if its not already there to hold the build of cpp files + rm -rf build/* # build from scratch + pip install . -v + cd - + done +fi +set +x +set +e diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 8b855097c5f117c765232e01aff2aff8a9f30bff..0000000000000000000000000000000000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include MANIFEST.in -include LICENSE -include README.md -recursive-include aidge_export_cpp * -include setup.py -include version.txt diff --git a/aidge_export_cpp/__init__.py b/aidge_export_cpp/__init__.py index 26fbb739104cc032b3523c59b858a1049735eba0..c79853c6e53fefe3733937a01ef70cf483042b6b 100644 --- a/aidge_export_cpp/__init__.py +++ b/aidge_export_cpp/__init__.py @@ -9,7 +9,5 @@ import aidge_core from aidge_export_cpp.utils import ROOT -__version__ = open(ROOT / "version.txt", "r").read().strip() - from .export import * diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py index b8fe28a54fd5ccc2cd8e380722b431b51a3e1aa8..1d876bfab7769606540e3eebb5f9102d95b8c7f8 100644 --- a/aidge_export_cpp/export.py +++ b/aidge_export_cpp/export.py @@ -90,13 +90,18 @@ def export(export_folder_name, graphview, scheduler): list_outputs_name.append((export_type, node.name())) # Generate forward file + # TODO: for now the mem type is bound for all intermediate results, should change. + # Note that we may have all inputs constants, hence select output type + assert len(list_outputs_name) >= 1, f"TODO: requires some output to determine mem type" + mem_ctype = list_outputs_name[0][0] generate_file( str(dnn_folder / "src" / "forward.cpp"), str(ROOT / "templates" / "network" / "network_forward.jinja"), headers=list_configs, actions=list_actions, inputs= list_inputs_name, - outputs=list_outputs_name + outputs=list_outputs_name, + mem_ctype=mem_ctype, ) # Generate dnn API diff --git a/aidge_export_cpp/kernels/matmul.hpp b/aidge_export_cpp/kernels/matmul.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4500993e02cf42fb698bc9004462800bdd3f7dc4 --- /dev/null +++ b/aidge_export_cpp/kernels/matmul.hpp @@ -0,0 +1,33 @@ +#ifndef __AIDGE_EXPORT_CPP_KERNELS_MATMUL__ +#define __AIDGE_EXPORT_CPP_KERNELS_MATMUL__ + +#include "network/typedefs.hpp" +#include "kernels/activation.hpp" + +// Generic function for matmul and activation + +template<int M, + int K, + int N, + ActivationFunction_T ACTIVATION, + typename Input_T, typename Output_T, + typename Rescaling_T> +__attribute__((always_inline)) inline +void matmul_forward ( + const Input_T* __restrict inputs1, + const Input_T* __restrict inputs2, + Output_T* __restrict outputs, + const Rescaling_T& __restrict rescaling) +{ + for (int m = 0; m < M; ++m) { + for (int n = 0; n < N; ++n) { + Output_T sum = Output_T(0); + for (int k = 0; k < K; ++k) { + sum += inputs1[K*m + k] * inputs2[N*k + n]; + } + outputs[N*m + n] = activation_forward_value<Output_T>(sum, 0/*not applicable*/, ACTIVATION, rescaling); + } + } +} + +#endif // __AIDGE_EXPORT_CPP_KERNELS_MATMUL__ diff --git a/aidge_export_cpp/operators.py b/aidge_export_cpp/operators.py index 6dba9a8f37995b4bb387689e89c4841a5311b5c5..602aea8dcee699ced05863ecc627e94193e9e653 100644 --- a/aidge_export_cpp/operators.py +++ b/aidge_export_cpp/operators.py @@ -238,6 +238,8 @@ class AddCPP(ExportNode): copyfile(str(ROOT / "kernels" / "elemwise.hpp"), str(export_folder / "include" / "kernels")) + copyfile(str(ROOT / "kernels" / "activation.hpp"), + str(export_folder / "include" / "kernels")) generate_file( str(export_folder / "layers" / f"{self.name}.h"), @@ -251,13 +253,13 @@ class AddCPP(ExportNode): return list_configs def forward(self, list_actions:list): - - list_actions.append(set_up_output(self.name, "float")) + if not self.is_last: + list_actions.append(set_up_output(self.name, "float")) list_actions.append(generate_str( str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja"), name=self.name, - inputs1_name=self.parents[0].name() if self.parents[0] else self.name + "_input1", - inputs2_name=self.parents[1].name() if self.parents[1] else self.name + "_input2", + inputs1_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input1", + inputs2_name=self.inputs[1].name() if self.inputs[1] else self.name + "_input2", output_name=self.name )) return list_actions @@ -272,6 +274,9 @@ class SubCPP(ExportNode): list_configs.append("kernels/elemwise.hpp") copyfile(str(ROOT / "kernels" / "elemwise.hpp"), str(export_folder / "include" / "kernels")) + copyfile(str(ROOT / "kernels" / "activation.hpp"), + str(export_folder / "include" / "kernels")) + generate_file( str(export_folder / "layers" / f"{self.name}.h"), str(ROOT / "templates" / "configuration" / "elemwise_config.jinja"), @@ -284,8 +289,46 @@ class SubCPP(ExportNode): return list_configs def forward(self, list_actions:list): + if not self.is_last: + list_actions.append(set_up_output(self.name, "float")) + + list_actions.append(generate_str( + str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja"), + name=self.name, + inputs1_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input1", + inputs2_name=self.inputs[1].name() if self.inputs[1] else self.name + "_input2", + output_name=self.name + )) + return list_actions + +@operator_register("Mul") +class MulCPP(ExportNode): + def __init__(self, node): + super().__init__(node) + + def export(self, export_folder:str, list_configs:list): + list_configs.append(f"layers/{self.name}.h") + list_configs.append("kernels/elemwise.hpp") + copyfile(str(ROOT / "kernels" / "elemwise.hpp"), + str(export_folder / "include" / "kernels")) + copyfile(str(ROOT / "kernels" / "activation.hpp"), + str(export_folder / "include" / "kernels")) + + generate_file( + str(export_folder / "layers" / f"{self.name}.h"), + str(ROOT / "templates" / "configuration" / "elemwise_config.jinja"), + name=self.name, + nb_elts=np.prod(self.inputs_dims[0]), + activation="Linear", + elemwise_op="Mul", + rescaling="NoScaling") + + return list_configs + + def forward(self, list_actions:list): + if not self.is_last: + list_actions.append(set_up_output(self.name, "float")) - list_actions.append(set_up_output(self.name, "float")) list_actions.append(generate_str( str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja"), name=self.name, @@ -410,3 +453,68 @@ class FcCPP(ExportNode): )) return list_actions +@operator_register("MatMul") +class MatMulCPP(ExportNode): + def __init__(self, node): + super().__init__(node) + + dims0, dims1, outdims = [tuple(x) for x in [self.inputs_dims[0], self.inputs_dims[1], self.outputs_dims[0]]] + + # TODO: MatMul aidge operator supports N-D multi broadcast dimensions where N > 2 + assert len(dims0) <= 2 and len(dims1) <= 2, ( + f"MatMul export do not support yet dimensions above 2D: inputs shapes are: {dims0}, {dims1}") + + # Cast to at least 1D + # Note that from MatMul::forwardDims(), scalar inputs are supported + # which is actually more general than np.matmul + dims0 = dims0 if len(dims0) >= 1 else (1, 1) + dims1 = dims1 if len(dims1) >= 1 else (1, 1) + + # Cast to at least 2D + dims0 = dims0 if len(dims0) >= 2 else (1, dims0[0]) + dims1 = dims1 if len(dims1) >= 2 else (dims1[0], 1) + assert dims0[1] == dims1[0], ( + f"MatMul input dimensions do no match, expected (m, k), (k, n): inputs shapes are: {dims0}, {dims1}") + + outdims = outdims if len(outdims) > 0 else (1, 1) + assert outdims == (dims0[0], dims1[1]), ( + f"MatMul output dimensions do no match, expected (m, n) for inputs (m, k) (k, n): output shape is: {outdims}, inputs shapes are: {dims0}, {dims1}") + + self.matmul_inputs_dims = dims0, dims1 + self.matmul_output_dims = outdims + + def export(self, export_folder:Path, list_configs:list): + + copyfile(str(ROOT / "kernels" / "matmul.hpp"), + str(export_folder / "include" / "kernels")) + copyfile(str(ROOT / "kernels" / "activation.hpp"), + str(export_folder / "include" / "kernels")) + + # Add to config list the include of configurations + list_configs.append("kernels/matmul.hpp") + list_configs.append(f"layers/{self.name}.h") + + # Export configuration file + generate_file( + str(export_folder / "layers" / f"{self.name}.h"), + str(ROOT / "templates" / "configuration" / "matmul_config.jinja"), + name=self.name, + inputs_dims=self.matmul_inputs_dims, + output_dims=self.matmul_output_dims, + activation="Linear", + rescaling="NoScaling", + ) + + return list_configs + + def forward(self, list_actions:list): + if not self.is_last: + list_actions.append(set_up_output(self.name, "float")) + list_actions.append(generate_str( + str(ROOT / "templates" / "kernel_forward" / "matmul_forward.jinja"), + name=self.name, + inputs1_name=self.inputs[0].name() if self.inputs[0] else self.name + "_input1", + inputs2_name=self.inputs[1].name() if self.inputs[1] else self.name + "_input2", + outputs_name=self.name + )) + return list_actions diff --git a/aidge_export_cpp/templates/configuration/matmul_config.jinja b/aidge_export_cpp/templates/configuration/matmul_config.jinja new file mode 100644 index 0000000000000000000000000000000000000000..fece988ac13b0136a8506abb39998114923817d6 --- /dev/null +++ b/aidge_export_cpp/templates/configuration/matmul_config.jinja @@ -0,0 +1,15 @@ +{#- For name header -#} +#ifndef {{ name|upper }}_LAYER_H +#define {{ name|upper }}_LAYER_H + +{# For layer configuration -#} +#define {{ name|upper }}_M {{ inputs_dims[0][0] }} +#define {{ name|upper }}_K {{ inputs_dims[0][1] }} +#define {{ name|upper }}_N {{ inputs_dims[1][1] }} +#define {{ name|upper }}_ACTIVATION {{ activation }} +static const {{ rescaling }} {{ name|upper }}_RESCALING = {}; + +{#- Calculate sizes #} + + +#endif /* {{ name|upper }}_LAYER_H */ diff --git a/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja b/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja new file mode 100644 index 0000000000000000000000000000000000000000..ce80ffd2abc90ad611d3008c57aae36383691452 --- /dev/null +++ b/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja @@ -0,0 +1,5 @@ +matmul_forward<{{name|upper}}_M, + {{name|upper}}_K, + {{name|upper}}_N, + {{name|upper}}_ACTIVATION> + ({{inputs1_name}}, {{inputs2_name}}, {{outputs_name}}, {{name|upper}}_RESCALING); \ No newline at end of file diff --git a/aidge_export_cpp/templates/network/network_forward.jinja b/aidge_export_cpp/templates/network/network_forward.jinja index b9c313cad157f90b40a94d47c2782d1f3f954bad..e7bde0a1285bcc12931f73d97fb216362cdca6a8 100644 --- a/aidge_export_cpp/templates/network/network_forward.jinja +++ b/aidge_export_cpp/templates/network/network_forward.jinja @@ -11,7 +11,7 @@ {# mem has the datatype of the firt input #} {#- Change here to improve it -#} -static {{inputs[0][0]}} mem[MEMORY_SIZE]; +static {{mem_ctype}} mem[MEMORY_SIZE]; {# Forward function #} {#- Support multiple inputs with different datatypes and multiple outputs with different datatypes -#} diff --git a/examples/add_custom_operator/add_custom_operator.ipynb b/examples/add_custom_operator/add_custom_operator.ipynb index 5477cdaefa00334a64472810e71c04031c06ab0f..d3d532bf371d5e916328f3d9d8f3b5f0a1d6e0e4 100644 --- a/examples/add_custom_operator/add_custom_operator.ipynb +++ b/examples/add_custom_operator/add_custom_operator.ipynb @@ -22,6 +22,15 @@ "## Import Aidge\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install nbconvert" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/examples/export_LeNet/export_lenet_fp32.ipynb b/examples/export_LeNet/export_lenet_fp32.ipynb index b10ca6b9dbe4fecf1e56ec04ead0c6c774aaf66c..dfab1c762f8a16ab21843ab560b7754fa9cb25d4 100644 --- a/examples/export_LeNet/export_lenet_fp32.ipynb +++ b/examples/export_LeNet/export_lenet_fp32.ipynb @@ -13,7 +13,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install requests numpy ipywidgets ipycanvas" + "%pip install requests numpy ipywidgets ipycanvas nbconvert" ] }, { diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..cbfa6210ebcb885d5efea69279f92e5a49b26a06 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[project] +name = "aidge_export_cpp" +description="Aidge CPP generic export" +dependencies = [ + "numpy>=1.20", + "Jinja2>=3.1.3", +] + +requires-python = ">= 3.7" +readme = "README.md" +license = { file = "LICENSE" } +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)", + "Programming Language :: C++", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Software Development" +] +dynamic = ["version"] #Â defined in tool.setuptools_scm + +[build-system] +requires = [ + "setuptools>=64", + "setuptools_scm[toml]==7.1.0", + "toml" +] +build-backend = "setuptools.build_meta" + +##################################################### +# SETUPTOOLS +[tool.setuptools] +[tool.setuptools.packages.find] +where = ["."] # list of folders that contain the packages (["."] by default) +include = ["aidge_export_cpp.*"] # package names should match these glob patterns (["*"] by default) +exclude = ["aidge_export_cpp.unit_tests*"] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) +# SETUPTOOLS_SCM +[tool.setuptools_scm] +write_to = "aidge_export_cpp/_version.py" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 51cafbf17414b91d70c3a8eba48dd201ffbd64e1..0000000000000000000000000000000000000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Jinja2 -numpy \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index f680b4c68e2fe2f533f7b43fd94ca704facf6866..0000000000000000000000000000000000000000 --- a/setup.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 -""" Aidge CPP generic export -""" - -DOCLINES = (__doc__ or '').split("\n") - -import sys - -# Python supported version checks -if sys.version_info[:2] < (3, 7): - raise RuntimeError("Python version >= 3.7 required.") - - -CLASSIFIERS = """\ -Development Status :: 2 - Pre-Alpha -Intended Audience :: Developers -Intended Audience :: Education -Intended Audience :: Science/Research -License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0) -Programming Language :: C++ -Programming Language :: Python -Programming Language :: Python :: 3 -Programming Language :: Python :: 3.7 -Programming Language :: Python :: 3.8 -Programming Language :: Python :: 3.9 -Programming Language :: Python :: 3 :: Only -Topic :: Scientific/Engineering -Topic :: Scientific/Engineering :: Artificial Intelligence -Topic :: Software Development -""" - -import os -import shutil -import pathlib - -from setuptools import setup, Extension -from setuptools import find_packages -from setuptools.command.build_ext import build_ext - -def get_project_version() -> str: - aidge_root = pathlib.Path().absolute() - version = open(aidge_root / "version.txt", "r").read().strip() - return version - -class AdditionalExtension(Extension): - def __init__(self, name): - super().__init__(name, sources=[]) - -class AdditionalBuild(build_ext): - - def run(self): - cwd = pathlib.Path().absolute() - - build_temp = cwd / "build" - if not build_temp.exists(): - build_temp.mkdir(parents=True, exist_ok=True) - - build_lib = pathlib.Path(self.build_lib) - if not build_lib.exists(): - build_lib.mkdir(parents=True, exist_ok=True) - - aidge_package = build_lib / "aidge_export_cpp" - - - # Copy version.txt in aidge_package - os.chdir(os.path.dirname(__file__)) - shutil.copy("version.txt", str(aidge_package.absolute())) - - -if __name__ == '__main__': - - setup( - name="aidge_export_cpp", - version=get_project_version(), - license="Eclipse Public License 2.0 (EPL-2.0)", - python_requires='>=3.7', - description=DOCLINES[0], - long_description_content_type="text/markdown", - long_description="\n".join(DOCLINES[2:]), - classifiers=[c for c in CLASSIFIERS.split('\n') if c], - platforms=["Linux"], - packages=find_packages(where="."), - include_package_data=True, - ext_modules=[AdditionalExtension("aidge_export_cpp")], - cmdclass={ - 'build_ext': AdditionalBuild, - }, - ) diff --git a/version.txt b/version.txt index 8294c184368c0ec9f84fbcc80c6b36326940c770..d917d3e26adc9854b4569871e20111c38de2606f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.1.2 \ No newline at end of file +0.1.2