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