From f113b674bc9272f8eb1e4b517e2ba5d7ac5336e6 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 11 Feb 2025 15:12:24 +0100
Subject: [PATCH 01/93] [Chore] Remove deprecated warning

---
 aidge_export_cpp/export.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py
index 42bf90f..2018f66 100644
--- a/aidge_export_cpp/export.py
+++ b/aidge_export_cpp/export.py
@@ -2,7 +2,7 @@ import aidge_core
 from aidge_export_cpp import ExportLibCpp
 
 def export(export_folder_name, graphview, scheduler, mem_wrapping=False):
-    print("Warning: This function is deprecated, check tutorial https://eclipse.dev/aidge/source/Tutorial/export_cpp.html to find the new way to generate a C++ export.")
+    
     aidge_core.export_utils.scheduler_export(
         scheduler,
         export_folder_name,
-- 
GitLab


From 77fd2522e225612b89d9b7ae8aecb0e10b780093 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 11 Feb 2025 15:12:57 +0100
Subject: [PATCH 02/93] [Refactor] Change static files path

---
 aidge_export_cpp/export_registry.py                        | 4 ++--
 aidge_export_cpp/static/{include/network => }/typedefs.hpp | 0
 aidge_export_cpp/static/{include/network => }/utils.hpp    | 0
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename aidge_export_cpp/static/{include/network => }/typedefs.hpp (100%)
 rename aidge_export_cpp/static/{include/network => }/utils.hpp (100%)

diff --git a/aidge_export_cpp/export_registry.py b/aidge_export_cpp/export_registry.py
index 876e4ff..d3fcd9a 100644
--- a/aidge_export_cpp/export_registry.py
+++ b/aidge_export_cpp/export_registry.py
@@ -5,6 +5,6 @@ class ExportLibCpp(ExportLib):
     _name="export_cpp"
     static_files={
         str(ROOT / "static" / "Makefile"): "",
-        str(ROOT / "static" / "include" / "network" / "typedefs.hpp"): "dnn/include/network",
-        str(ROOT / "static" / "include" / "network" / "utils.hpp"): "dnn/include/network",
+        str(ROOT / "static" / "typedefs.hpp"): "dnn/include/network",
+        str(ROOT / "static" / "utils.hpp"): "dnn/include/network",
     }
diff --git a/aidge_export_cpp/static/include/network/typedefs.hpp b/aidge_export_cpp/static/typedefs.hpp
similarity index 100%
rename from aidge_export_cpp/static/include/network/typedefs.hpp
rename to aidge_export_cpp/static/typedefs.hpp
diff --git a/aidge_export_cpp/static/include/network/utils.hpp b/aidge_export_cpp/static/utils.hpp
similarity index 100%
rename from aidge_export_cpp/static/include/network/utils.hpp
rename to aidge_export_cpp/static/utils.hpp
-- 
GitLab


From 7b1686244cb0ee4347795d2a4dc9bc363757c34f Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 11 Feb 2025 15:13:43 +0100
Subject: [PATCH 03/93] [Refactor] Split operators.py into several files

---
 aidge_export_cpp/operators/Add.py             | 19 ++++++
 aidge_export_cpp/operators/Conv.py            | 21 ++++++
 aidge_export_cpp/operators/Fc.py              | 17 +++++
 .../operators/GlobalAveragePool.py            | 28 ++++++++
 aidge_export_cpp/operators/MaxPool.py         | 22 +++++++
 aidge_export_cpp/operators/Mul.py             | 19 ++++++
 aidge_export_cpp/operators/Pad.py             | 10 +++
 aidge_export_cpp/operators/PaddedConv.py      | 30 +++++++++
 aidge_export_cpp/operators/PaddedMaxPool.py   | 28 ++++++++
 aidge_export_cpp/operators/Producer.py        | 64 +++++++++++++++++++
 aidge_export_cpp/operators/Relu.py            | 18 ++++++
 aidge_export_cpp/operators/Sub.py             | 19 ++++++
 aidge_export_cpp/operators/__init__.py        | 14 ++++
 13 files changed, 309 insertions(+)
 create mode 100644 aidge_export_cpp/operators/Add.py
 create mode 100644 aidge_export_cpp/operators/Conv.py
 create mode 100644 aidge_export_cpp/operators/Fc.py
 create mode 100644 aidge_export_cpp/operators/GlobalAveragePool.py
 create mode 100644 aidge_export_cpp/operators/MaxPool.py
 create mode 100644 aidge_export_cpp/operators/Mul.py
 create mode 100644 aidge_export_cpp/operators/Pad.py
 create mode 100644 aidge_export_cpp/operators/PaddedConv.py
 create mode 100644 aidge_export_cpp/operators/PaddedMaxPool.py
 create mode 100644 aidge_export_cpp/operators/Producer.py
 create mode 100644 aidge_export_cpp/operators/Relu.py
 create mode 100644 aidge_export_cpp/operators/Sub.py
 create mode 100644 aidge_export_cpp/operators/__init__.py

diff --git a/aidge_export_cpp/operators/Add.py b/aidge_export_cpp/operators/Add.py
new file mode 100644
index 0000000..78942ea
--- /dev/null
+++ b/aidge_export_cpp/operators/Add.py
@@ -0,0 +1,19 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Add", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class AddCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["elemwise_op"] = "Add"
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "elemwise_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
+        self.include_list = ["kernels/elemwise.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Conv.py b/aidge_export_cpp/operators/Conv.py
new file mode 100644
index 0000000..7e4a49c
--- /dev/null
+++ b/aidge_export_cpp/operators/Conv.py
@@ -0,0 +1,21 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Conv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class ConvCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        # No padding with Conv
+        # Use PaddedConv to add padding attribute
+        self.attributes["padding"] = [0, 0]
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "convolution_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
+        self.include_list = ["kernels/convolution.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Fc.py b/aidge_export_cpp/operators/Fc.py
new file mode 100644
index 0000000..59afa99
--- /dev/null
+++ b/aidge_export_cpp/operators/Fc.py
@@ -0,0 +1,17 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("FC", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class FcCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "fullyconnected_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "fullyconnected_forward.jinja")
+        self.include_list = ["kernels/fullyconnected.hpp"]
+        self.kernels_to_copy = []
diff --git a/aidge_export_cpp/operators/GlobalAveragePool.py b/aidge_export_cpp/operators/GlobalAveragePool.py
new file mode 100644
index 0000000..068d797
--- /dev/null
+++ b/aidge_export_cpp/operators/GlobalAveragePool.py
@@ -0,0 +1,28 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("GlobalAveragePooling", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class GlobalAveragePoolCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        self.attributes["stride_dims"] = [1, 1]
+        # No padding with MaxPooling
+        # Use PaddedMaxPooling to add padding attribute
+        self.attributes["padding"] = [0, 0]
+        self.attributes["kernel_dims"] = [
+            self.attributes["in_height"][0],
+            self.attributes["in_width"][0],
+        ]
+        self.attributes["pool_type"] = "Average"
+        self.attributes["activation"] = "Linear"
+
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "pooling_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
+        self.include_list = ["kernels/pooling.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/MaxPool.py b/aidge_export_cpp/operators/MaxPool.py
new file mode 100644
index 0000000..3a21345
--- /dev/null
+++ b/aidge_export_cpp/operators/MaxPool.py
@@ -0,0 +1,22 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("MaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class MaxPoolCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # No padding with MaxPooling
+        # Use PaddedMaxPooling to add padding attribute
+        self.attributes["padding"] = [0, 0]
+        self.attributes["pool_type"] = "Max"
+        self.attributes["activation"] = "Linear"
+
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "pooling_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
+        self.include_list = ["kernels/pooling.hpp"]
+        self.kernels_to_copy = []
diff --git a/aidge_export_cpp/operators/Mul.py b/aidge_export_cpp/operators/Mul.py
new file mode 100644
index 0000000..66b147c
--- /dev/null
+++ b/aidge_export_cpp/operators/Mul.py
@@ -0,0 +1,19 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Mul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class MulCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["elemwise_op"] = "Mul"
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "elemwise_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
+        self.include_list = ["kernels/elemwise.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Pad.py b/aidge_export_cpp/operators/Pad.py
new file mode 100644
index 0000000..7880b88
--- /dev/null
+++ b/aidge_export_cpp/operators/Pad.py
@@ -0,0 +1,10 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ExportLibCpp
+
+# TODO : find a way to remove this dummy exportnode
+@ExportLibCpp.register("Pad2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class Pad_ARMCortexM(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        raise NotImplementedError("Pad2D nodes is not implemented")
+    
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/PaddedConv.py b/aidge_export_cpp/operators/PaddedConv.py
new file mode 100644
index 0000000..9b314a1
--- /dev/null
+++ b/aidge_export_cpp/operators/PaddedConv.py
@@ -0,0 +1,30 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("PaddedConv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class PaddedConvCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        # TODO find a way to retrive attr for meta op
+        for n in self.operator.get_micro_graph().get_nodes():
+            if n.type() == "Pad2D":
+                self.attributes["padding"] = n.get_operator(
+                ).attr.begin_end_borders
+            if n.type() == "Conv2D":
+                self.attributes["kernel_dims"] = n.get_operator(
+                ).attr.kernel_dims
+                self.attributes["stride_dims"] = n.get_operator(
+                ).attr.stride_dims
+                self.attributes["dilation_dims"] = n.get_operator(
+                ).attr.dilation_dims
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "convolution_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
+        self.include_list = ["kernels/convolution.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/PaddedMaxPool.py b/aidge_export_cpp/operators/PaddedMaxPool.py
new file mode 100644
index 0000000..d6f68f2
--- /dev/null
+++ b/aidge_export_cpp/operators/PaddedMaxPool.py
@@ -0,0 +1,28 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("PaddedMaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class PaddedMaxPoolCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        for n in self.operator.get_micro_graph().get_nodes():
+            if n.type() == "Pad2D":
+                self.attributes["padding"] = n.get_operator(
+                ).attr.begin_end_borders
+            if n.type() == "MaxPooling2D":
+                self.attributes["kernel_dims"] = n.get_operator(
+                ).attr.kernel_dims
+                self.attributes["stride_dims"] = n.get_operator(
+                ).attr.stride_dims
+        self.attributes["pool_type"] = "Max"
+        self.attributes["activation"] = "Linear"
+
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "pooling_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
+        self.include_list = ["kernels/pooling.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Producer.py b/aidge_export_cpp/operators/Producer.py
new file mode 100644
index 0000000..a252f8e
--- /dev/null
+++ b/aidge_export_cpp/operators/Producer.py
@@ -0,0 +1,64 @@
+import os
+from pathlib import Path
+import numpy as np
+import aidge_core
+from aidge_core.export_utils import ExportNode, generate_file
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+def numpy_dtype2ctype(dtype):
+    if dtype == np.int8:
+        return "int8_t"
+    elif dtype == np.int16:
+        return "int16_t"
+    elif dtype == np.int32:
+        return "int32_t"
+    elif dtype == np.int64:
+        return "int64_t"
+    elif dtype == np.float32:
+        return "float"
+    elif dtype == np.float64:
+        return "double"
+    # Add more dtype mappings as needed
+    else:
+        raise ValueError(f"Unsupported {dtype} dtype")
+
+def export_params(name: str,
+                  array: np.ndarray,
+                  filepath: str):
+
+    # Get directory name of the file
+    dirname = os.path.dirname(filepath)
+
+    # If directory doesn't exist, create it
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+    generate_file(
+        filepath,
+        str(ROOT / "templates" / "data" / "parameters.jinja"),
+        name=name,
+        data_t=numpy_dtype2ctype(array.dtype),
+        values=array.tolist()
+    )
+
+@ExportLibCpp.register("Producer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class ProducerCPP(ExportNode):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.values = np.array(self.operator.get_output(0))
+
+        if len(self.values.shape) == 4:  # Note: export in HWC
+            self.values = np.transpose(self.values, (0, 2, 3, 1))
+
+    def export(self, export_folder: Path):
+        header_path = f"include/parameters/{self.attributes['name']}.h"
+        export_params(
+            self.attributes['out_name'][0],
+            self.values.reshape(-1),
+            str(export_folder / header_path))
+        return [header_path]
+
+    def forward(self):
+        # A Producer does nothing during forward
+        return []
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Relu.py b/aidge_export_cpp/operators/Relu.py
new file mode 100644
index 0000000..0fbccf0
--- /dev/null
+++ b/aidge_export_cpp/operators/Relu.py
@@ -0,0 +1,18 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("ReLU", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class ReLUCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["activation"] = "Rectifier"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "activation_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "activation_forward.jinja")
+        self.include_list = ["kernels/activation.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Sub.py b/aidge_export_cpp/operators/Sub.py
new file mode 100644
index 0000000..3444cef
--- /dev/null
+++ b/aidge_export_cpp/operators/Sub.py
@@ -0,0 +1,19 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Sub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class SubCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["elemwise_op"] = "Sub"
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "elemwise_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
+        self.include_list = ["kernels/elemwise.hpp"]
+        self.kernels_to_copy = []
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/__init__.py b/aidge_export_cpp/operators/__init__.py
new file mode 100644
index 0000000..37d674a
--- /dev/null
+++ b/aidge_export_cpp/operators/__init__.py
@@ -0,0 +1,14 @@
+"""
+Copyright (c) 2023 CEA-List
+
+This program and the accompanying materials are made available under the
+terms of the Eclipse Public License 2.0 which is available at
+http://www.eclipse.org/legal/epl-2.0.
+
+SPDX-License-Identifier: EPL-2.0
+"""
+from pathlib import Path
+
+DIR_PATH = Path(__file__).parent
+modules = [Path(module).stem for module in DIR_PATH.glob("*.py")]
+__all__ = [ f for f in modules if f != "__init__"]
-- 
GitLab


From 1abcabb69773d382592929a426f84c1d661a6ed6 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:13:19 +0100
Subject: [PATCH 04/93] [Git] Add venv

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 9dc376a..876473d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ __pycache__
 dist*/
 aidge_export_cpp/_version.py
 wheelhouse/*
+env_aidge/
 
 # Temp test folders
 aidge_export_cpp/unit_tests/*_temp_test
-- 
GitLab


From 16ee52953b760a8de22c55baf12773ad321cc838 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:15:10 +0100
Subject: [PATCH 05/93] [Refactor] Rework the operators system to match with
 the actual cpp kernels implementations

---
 aidge_export_cpp/operators/Add.py             | 19 --------
 aidge_export_cpp/operators/Conv.py            | 21 ---------
 aidge_export_cpp/operators/CppActivation.py   | 32 +++++++++++++
 aidge_export_cpp/operators/CppConv.py         | 41 ++++++++++++++++
 aidge_export_cpp/operators/CppElemWise.py     | 34 ++++++++++++++
 aidge_export_cpp/operators/CppFc.py           | 33 +++++++++++++
 aidge_export_cpp/operators/CppPool.py         | 47 +++++++++++++++++++
 aidge_export_cpp/operators/Fc.py              | 17 -------
 .../operators/GlobalAveragePool.py            | 28 -----------
 aidge_export_cpp/operators/MaxPool.py         | 22 ---------
 aidge_export_cpp/operators/Mul.py             | 19 --------
 aidge_export_cpp/operators/Pad.py             | 10 ----
 aidge_export_cpp/operators/PaddedConv.py      | 30 ------------
 aidge_export_cpp/operators/PaddedMaxPool.py   | 28 -----------
 aidge_export_cpp/operators/Relu.py            | 18 -------
 aidge_export_cpp/operators/Sub.py             | 19 --------
 16 files changed, 187 insertions(+), 231 deletions(-)
 delete mode 100644 aidge_export_cpp/operators/Add.py
 delete mode 100644 aidge_export_cpp/operators/Conv.py
 create mode 100644 aidge_export_cpp/operators/CppActivation.py
 create mode 100644 aidge_export_cpp/operators/CppConv.py
 create mode 100644 aidge_export_cpp/operators/CppElemWise.py
 create mode 100644 aidge_export_cpp/operators/CppFc.py
 create mode 100644 aidge_export_cpp/operators/CppPool.py
 delete mode 100644 aidge_export_cpp/operators/Fc.py
 delete mode 100644 aidge_export_cpp/operators/GlobalAveragePool.py
 delete mode 100644 aidge_export_cpp/operators/MaxPool.py
 delete mode 100644 aidge_export_cpp/operators/Mul.py
 delete mode 100644 aidge_export_cpp/operators/Pad.py
 delete mode 100644 aidge_export_cpp/operators/PaddedConv.py
 delete mode 100644 aidge_export_cpp/operators/PaddedMaxPool.py
 delete mode 100644 aidge_export_cpp/operators/Relu.py
 delete mode 100644 aidge_export_cpp/operators/Sub.py

diff --git a/aidge_export_cpp/operators/Add.py b/aidge_export_cpp/operators/Add.py
deleted file mode 100644
index 78942ea..0000000
--- a/aidge_export_cpp/operators/Add.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("Add", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class AddCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["elemwise_op"] = "Add"
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "elemwise_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
-        self.include_list = ["kernels/elemwise.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Conv.py b/aidge_export_cpp/operators/Conv.py
deleted file mode 100644
index 7e4a49c..0000000
--- a/aidge_export_cpp/operators/Conv.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("Conv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class ConvCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        # No padding with Conv
-        # Use PaddedConv to add padding attribute
-        self.attributes["padding"] = [0, 0]
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "convolution_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
-        self.include_list = ["kernels/convolution.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
new file mode 100644
index 0000000..1cb82d9
--- /dev/null
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -0,0 +1,32 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("CppActivation", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppActivation(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "activation_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "activation_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.kernels_to_copy = [ROOT / "kernels" / "activation.hpp",
+                                ROOT / "kernels" / "rescaling.hpp"]
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
new file mode 100644
index 0000000..47dd312
--- /dev/null
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -0,0 +1,41 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("CppConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppConv(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["padding"] = [0, 0, 0, 0]
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "Pad2D":
+                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
+            elif n.type() == "Conv2D":
+                self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
+                self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
+                self.attributes["dilation_dims"] = n.get_operator().attr.dilation_dims
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.kernels_to_copy = [ROOT / "kernels" / "convolution.hpp",
+                                ROOT / "kernels" / "macs.hpp",
+                                ROOT / "kernels" / "rescaling.hpp",
+                                ROOT / "kernels" / "activation.hpp"]
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
new file mode 100644
index 0000000..e2d0ba8
--- /dev/null
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -0,0 +1,34 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("CppElemWise", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppElemWise(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["elemwise_op"] = node.type()    # [Add, Mul, Sub]
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "elemwise_config.jinja")
+
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
+
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+
+        # Path to the kernel(s) files to copy
+        self.kernels_to_copy = [ROOT / "kernels" / "elemwise.hpp",
+                                ROOT / "kernels" / "rescaling.hpp",
+                                ROOT / "kernels" / "activation.hpp"]
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
new file mode 100644
index 0000000..c86f8be
--- /dev/null
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -0,0 +1,33 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("CppFc", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppFc(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "fullyconnected_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "fullyconnected_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.kernels_to_copy = [ROOT / "kernels" / "fullyconnected.hpp",
+                                ROOT / "kernels" / "macs.hpp",
+                                ROOT / "kernels" / "rescaling.hpp",
+                                ROOT / "kernels" / "activation.hpp"]
diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
new file mode 100644
index 0000000..842680e
--- /dev/null
+++ b/aidge_export_cpp/operators/CppPool.py
@@ -0,0 +1,47 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("CppPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppPool(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["stride_dims"] = [1, 1]
+        self.attributes["padding"] = [0, 0, 0, 0]
+        self.attributes["pool_type"] = "Max"
+        self.attributes["activation"] = "Linear"
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "Pad2D":
+                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
+            elif n.type() == "GlobalAveragePooling":
+                self.attributes["pool_type"] = "Average"
+                self.attributes["kernel_dims"] = self.attributes["in_width"][0]
+            elif n.type() == "MaxPooling2D":
+                self.attributes["pool_type"] = "Max"
+                self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
+                self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
+            elif n.type() == "AvgPooling2D":
+                self.attributes["pool_type"] = "Average"
+                self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
+                self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "pooling_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.kernels_to_copy = [ROOT / "kernels" / "pooling.hpp",
+                                ROOT / "kernels" / "activation.hpp"]
+        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Fc.py b/aidge_export_cpp/operators/Fc.py
deleted file mode 100644
index 59afa99..0000000
--- a/aidge_export_cpp/operators/Fc.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("FC", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class FcCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "fullyconnected_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "fullyconnected_forward.jinja")
-        self.include_list = ["kernels/fullyconnected.hpp"]
-        self.kernels_to_copy = []
diff --git a/aidge_export_cpp/operators/GlobalAveragePool.py b/aidge_export_cpp/operators/GlobalAveragePool.py
deleted file mode 100644
index 068d797..0000000
--- a/aidge_export_cpp/operators/GlobalAveragePool.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("GlobalAveragePooling", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class GlobalAveragePoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        self.attributes["stride_dims"] = [1, 1]
-        # No padding with MaxPooling
-        # Use PaddedMaxPooling to add padding attribute
-        self.attributes["padding"] = [0, 0]
-        self.attributes["kernel_dims"] = [
-            self.attributes["in_height"][0],
-            self.attributes["in_width"][0],
-        ]
-        self.attributes["pool_type"] = "Average"
-        self.attributes["activation"] = "Linear"
-
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "pooling_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
-        self.include_list = ["kernels/pooling.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/MaxPool.py b/aidge_export_cpp/operators/MaxPool.py
deleted file mode 100644
index 3a21345..0000000
--- a/aidge_export_cpp/operators/MaxPool.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("MaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class MaxPoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        # No padding with MaxPooling
-        # Use PaddedMaxPooling to add padding attribute
-        self.attributes["padding"] = [0, 0]
-        self.attributes["pool_type"] = "Max"
-        self.attributes["activation"] = "Linear"
-
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "pooling_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
-        self.include_list = ["kernels/pooling.hpp"]
-        self.kernels_to_copy = []
diff --git a/aidge_export_cpp/operators/Mul.py b/aidge_export_cpp/operators/Mul.py
deleted file mode 100644
index 66b147c..0000000
--- a/aidge_export_cpp/operators/Mul.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("Mul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class MulCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["elemwise_op"] = "Mul"
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "elemwise_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
-        self.include_list = ["kernels/elemwise.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Pad.py b/aidge_export_cpp/operators/Pad.py
deleted file mode 100644
index 7880b88..0000000
--- a/aidge_export_cpp/operators/Pad.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ExportLibCpp
-
-# TODO : find a way to remove this dummy exportnode
-@ExportLibCpp.register("Pad2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class Pad_ARMCortexM(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        raise NotImplementedError("Pad2D nodes is not implemented")
-    
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/PaddedConv.py b/aidge_export_cpp/operators/PaddedConv.py
deleted file mode 100644
index 9b314a1..0000000
--- a/aidge_export_cpp/operators/PaddedConv.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register_metaop("PaddedConv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class PaddedConvCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        # TODO find a way to retrive attr for meta op
-        for n in self.operator.get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator(
-                ).attr.begin_end_borders
-            if n.type() == "Conv2D":
-                self.attributes["kernel_dims"] = n.get_operator(
-                ).attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator(
-                ).attr.stride_dims
-                self.attributes["dilation_dims"] = n.get_operator(
-                ).attr.dilation_dims
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "convolution_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
-        self.include_list = ["kernels/convolution.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/PaddedMaxPool.py b/aidge_export_cpp/operators/PaddedMaxPool.py
deleted file mode 100644
index d6f68f2..0000000
--- a/aidge_export_cpp/operators/PaddedMaxPool.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register_metaop("PaddedMaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class PaddedMaxPoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        for n in self.operator.get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator(
-                ).attr.begin_end_borders
-            if n.type() == "MaxPooling2D":
-                self.attributes["kernel_dims"] = n.get_operator(
-                ).attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator(
-                ).attr.stride_dims
-        self.attributes["pool_type"] = "Max"
-        self.attributes["activation"] = "Linear"
-
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "pooling_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
-        self.include_list = ["kernels/pooling.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Relu.py b/aidge_export_cpp/operators/Relu.py
deleted file mode 100644
index 0fbccf0..0000000
--- a/aidge_export_cpp/operators/Relu.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("ReLU", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class ReLUCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["activation"] = "Rectifier"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "activation_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "activation_forward.jinja")
-        self.include_list = ["kernels/activation.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Sub.py b/aidge_export_cpp/operators/Sub.py
deleted file mode 100644
index 3444cef..0000000
--- a/aidge_export_cpp/operators/Sub.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register("Sub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class SubCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["elemwise_op"] = "Sub"
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "elemwise_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
-        self.include_list = ["kernels/elemwise.hpp"]
-        self.kernels_to_copy = []
-        
\ No newline at end of file
-- 
GitLab


From bb89d69f2f27c62c6fc15e699b41e16374a42be2 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:16:26 +0100
Subject: [PATCH 06/93] [Refactor] Add utils functions to clean export file

---
 aidge_export_cpp/export_utils.py | 125 +++++++++++++++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 aidge_export_cpp/export_utils.py

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
new file mode 100644
index 0000000..7be6636
--- /dev/null
+++ b/aidge_export_cpp/export_utils.py
@@ -0,0 +1,125 @@
+import os
+from collections import OrderedDict
+
+import aidge_core
+
+def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
+    """ 
+    Fuse nodes into metaops adapted for the Jacinto Export
+
+    :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
+                       ordered input/output data within the computational graph.
+    """
+
+    cpp_recipes = OrderedDict({
+        # "Quantizer":    "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
+        "CppFc":          "FC->ReLU?",
+        "CppConv":        "Conv2D#->ReLU?; Conv2D#<-Pad2D?",
+        "CppPool":        "(MaxPooling2D|AvgPooling2D|GlobalAveragePooling)#->ReLU?; (MaxPooling2D|AvgPooling2D|GlobalAveragePooling)#<-Pad2D?",
+        "CppElemWise":    "(Add|Mul|Sub)->ReLU?",
+        "CppActivation":  "ReLU"
+        # "CppScaling":     "Quantizer->ReLU?",
+    })
+
+    # Fuse Quantizers
+    # aidge_core.fuse_to_metaops(
+    #     graph_view, cpp_recipes["Quantizer"], "Quantizer")
+
+    for node, recipe in cpp_recipes.items():
+        aidge_core.fuse_to_metaops(graph_view, recipe, node)
+
+
+
+def set_nodes_names(scheduler):
+    """
+    Set the Jacinto nodes names as well as their producers. 
+    The producers naming is handled from their child node.
+    
+    [TODO] Fc and Conv layers will always have weights as parent 1 and 
+    possibly biases as parent 2. It may be better to previously label the 
+    producers. 
+
+    :param scheduler: Scheduler instance managing the computation graph.
+                      Uses `graph_view` and `get_sequential_static_scheduling` methods
+                      to retrieve the computation graph layout and ordered nodes.
+    :type scheduler: aidge_core.Scheduler
+    """
+
+    node_ids = {}   # Dict holding the node type along with a counter
+    node_it = 0     # Node Iterator
+    for node in scheduler.get_sequential_static_scheduling():
+        node_type = node.type()
+
+        if node_type != "Producer":     # Producers are 
+            if node.type() not in node_ids:
+                node_ids[node_type] = 0
+
+            # Set node name
+            node.set_name("_" + str(node_it) + "_" +
+                            str(node_type) + "_" + str(node_ids[node_type]))
+            node_ids[node_type] += 1
+            node_it += 1
+
+            # Set producers names
+            if node_type in ["CppFc", "CppConv"]:
+                # nb_parents = len(node.get_parents())
+                node.get_parent(1).set_name(node.name() + "_weights")
+                if node.get_parent(2) is not None:
+                    node.get_parent(2).set_name(node.name() + "_biases")  
+
+            for parent_node in node.get_parents():
+                if parent_node is not None:
+                    # [TODO] Does not work yet
+                    # if parent_node.attributes().has_attr("quantization.ptq.CompensationCoeff"):
+                    #     parent_node.set_name(node.name() + "_coeff")
+                    if parent_node.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        parent_node.set_name(node.name() + "_shift")
+                    # [Fix] Add scaling/add coeff nodes manually
+                    # elif node.type() in ["JScaling", "JAdd"] and parent_node.type() == "Producer":
+                    #     parent_node.set_name(node.name() + "_coeff")
+                    # [End Fix]
+
+
+
+def set_nodes_datatypes(graph_view: aidge_core.GraphView):
+    """ Set the nodes' datatypes
+
+    The set_datatype function can't be used on Conv2D and FC nodes directly
+    as the biases datatype is different from the other inputs. 
+
+    :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
+                       ordered input/output data within the computational graph.
+    """
+    for node in graph_view.get_nodes():
+        if node.type() != "Producer":
+            if node.type() in ["Conv2D", "FC"]:
+                node.get_operator().get_input(0).set_datatype(aidge_core.dtype.int8)    # Input
+                node.get_operator().get_input(1).set_datatype(aidge_core.dtype.int8)    # Weights
+                if node.get_parent(2) is not None:
+                    node.get_operator().get_input(2).set_datatype(aidge_core.dtype.int32)   # Biases
+                node.get_operator().get_output(0).set_datatype(aidge_core.dtype.int8)       # Output
+            else:
+                node.get_operator().set_datatype(aidge_core.dtype.int8)
+
+    # Set input node's datatype
+    for n in graph_view.get_input_nodes():
+        n.get_operator().get_input(0).set_datatype(aidge_core.dtype.int8)
+
+
+
+def read_log_file(file_path: str):
+    """ Read log file
+    Used to read the aidge generated log files containing the intermediate
+    tensors of the exported model. 
+
+    :param file_path: Path to the file to read. 
+    :type file_path: str
+    """
+    # Check if the file exists
+    if not os.path.isfile(file_path):
+        print(f"File not found: {file_path}")
+        return None
+
+    with open(file_path, 'r') as file:
+        content = file.read()
+    return content
-- 
GitLab


From 5b309cb560e32e92c8945e0dff3c25e2a35dae0a Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:20:23 +0100
Subject: [PATCH 07/93] [Refactor] Update export function adding the main file
 generation

---
 aidge_export_cpp/export.py | 72 ++++++++++++++++++++++++++++++++------
 1 file changed, 61 insertions(+), 11 deletions(-)

diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py
index 2018f66..bb89fe3 100644
--- a/aidge_export_cpp/export.py
+++ b/aidge_export_cpp/export.py
@@ -1,15 +1,65 @@
+import os
+import shutil
+from typing import List, Union
+
 import aidge_core
+from aidge_core.mem_info import generate_optimized_memory_info
+from aidge_core.export_utils import scheduler_export, generate_main_cpp
+
 from aidge_export_cpp import ExportLibCpp
+# from aidge_export_cpp.export_utils import read_log_file
+
+
+def export(export_folder_name: str,
+           graphview: aidge_core.GraphView,
+           scheduler: Union[List[aidge_core.Node],
+                            aidge_core.Scheduler],
+           input_tensor: aidge_core.Tensor = None,
+           labels: aidge_core.Tensor = None,
+           dev_mode: bool = False):
+    
+    """ Export an aidge_core.Scheduler to C++ code
+    
+    :param export_folder_name: Export folder name
+    :type export_folder_name: str
+    :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
+                       ordered input/output data within the computational graph.
+    :type graph_view: aidge_core.GraphView
+    :param scheduler: Scheduler instance managing the computation graph.
+                      Uses `graph_view` and `get_sequential_static_scheduling` methods
+    :param inputs_tensor: **For future** argument to provide tensor to use in the main function, not implemented yet!
+                          By default, the input of the given graph will be exported.
+    :type input_tensor: aidge_core.Tensor
+                    to retrieve the computation graph layout and ordered nodes.
+    :type scheduler: aidge_core.Scheduler
+    :param labels: Argument to provide labels tensor to generate and use in the main function. 
+    :type labels: aidge_core.Tensor
+    :param dev_mode: Wether or not the developer mode is enabled. If enabled, the export files
+                     will be symlinks from the aidge_export_cpp module. Therefore, modifying
+                     a file within the export will change the module as well. 
+    :type dev_mode: boolean
+    """
+
+    # Remove existing export
+    if os.path.isdir(export_folder_name):
+        print("Removing existing export directory...")
+        shutil.rmtree(export_folder_name)
+
+    # Generate Model Files
+    """
+    Perform the following tasks :
+    - Generate the parameters and layers config files
+    - Generate the forward.cpp file
+    - Copy all needed kernels
+    """
 
-def export(export_folder_name, graphview, scheduler, mem_wrapping=False):
+    scheduler_export(scheduler,
+                     export_folder_name,
+                     ExportLibCpp,
+                     memory_manager=generate_optimized_memory_info,
+                     memory_manager_args={
+                         "stats_folder": f"{export_folder_name}/stats"},
+                     dev_mode=dev_mode)
     
-    aidge_core.export_utils.scheduler_export(
-        scheduler,
-        export_folder_name,
-        ExportLibCpp,
-        memory_manager=aidge_core.mem_info.generate_optimized_memory_info,
-        memory_manager_args={
-            "stats_folder": f"{export_folder_name}/stats",
-            "wrapping": mem_wrapping
-        }
-    )
+    # Generate main file
+    generate_main_cpp(export_folder_name, graphview, labels=labels)
-- 
GitLab


From dc8d894a5dc57118b64205ff07efb0307a0f476b Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:20:49 +0100
Subject: [PATCH 08/93] [Chore] Remove unecessary tabulation

---
 .../templates/kernel_forward/_mem_offset.jinja             | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/_mem_offset.jinja b/aidge_export_cpp/templates/kernel_forward/_mem_offset.jinja
index b85aae8..f3bea03 100644
--- a/aidge_export_cpp/templates/kernel_forward/_mem_offset.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/_mem_offset.jinja
@@ -1,6 +1,3 @@
-{% filter indent(width=4, first=False) %}
-
-{% for outidx in range(nb_out) -%}
+{%- for outidx in range(nb_out) %}
 {{out_cdtype[outidx]}}* {{out_name[outidx]}} = ({{out_cdtype[outidx]}}*) mem + {{out_name[outidx]|upper}}_OFFSET;
-{% endfor %}
-{% endfilter %}
+{%- endfor %}
-- 
GitLab


From 4173f0a54aa33813ad5ffb41e55928a07f208573 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:21:13 +0100
Subject: [PATCH 09/93] [Chore] Include export_utils file

---
 aidge_export_cpp/__init__.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/aidge_export_cpp/__init__.py b/aidge_export_cpp/__init__.py
index d0a7ca5..04a6eda 100644
--- a/aidge_export_cpp/__init__.py
+++ b/aidge_export_cpp/__init__.py
@@ -8,3 +8,5 @@ from .operators import *
 from collections import defaultdict
 from .export import *
 from . import benchmark
+from .export_utils import *
+
-- 
GitLab


From 55201c7b25155cec0cc37d76bd344ac22912222b Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:25:37 +0100
Subject: [PATCH 10/93] [Chore] Temporarily comment the save_outputs generated
 code

Maybe it should be conditionned by an export option to avoid useless code generation
---
 .../templates/kernel_forward/activation_forward.jinja         | 2 +-
 .../templates/kernel_forward/batchnorm_forward.jinja          | 4 ++--
 .../templates/kernel_forward/convolution_forward.jinja        | 2 +-
 .../templates/kernel_forward/elemwise_forward.jinja           | 2 +-
 .../templates/kernel_forward/fullyconnected_forward.jinja     | 2 +-
 .../templates/kernel_forward/leakyrelu_forward.jinja          | 2 +-
 .../templates/kernel_forward/matmul_forward.jinja             | 2 +-
 .../templates/kernel_forward/pooling_forward.jinja            | 2 +-
 8 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja b/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja
index 9a39495..628dbec 100644
--- a/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja
@@ -3,5 +3,5 @@
 activation_forward<{{name|upper}}_NB_DATA,
                    {{name|upper}}_ACTIVATION>
                    ({{in_name[0]}}, {{out_name[0]}}, {{name|upper}}_RESCALING);
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja b/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
index 03fd8e8..8f8a665 100644
--- a/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
@@ -5,6 +5,6 @@ batchnorm_forward<{{ out_name[0]|upper }}_OUT_BATCH,
                   {{ out_name[0]|upper }}_OUT_HEIGHT,
                   {{ out_name[0]|upper }}_OUT_WIDTH,
                   {{name|upper}}_ACTIVATION>
-                  ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{in_name[3]}}, {{in_name[4]}}, {{name|upper}}_EPSILON, {{name|upper}}_RESCALING);
-{% include "./_save_outputs.jinja" %}
+                  ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{in_name[3]}}, {{in_name[4]}}, {{name|upper}}_EPSILON);
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja b/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja
index 7d0af8c..0fdd4d4 100644
--- a/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja
@@ -16,5 +16,5 @@ convolution{{ "_depthwise" if depthwise is defined else "" }}_forward<{{ in_name
                     {{name|upper}}_KERNEL_WIDTH,
                     {{name|upper}}_ACTIVATION>
                     ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{name|upper}}_RESCALING);
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja b/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
index f60d163..64ee040 100644
--- a/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
@@ -4,5 +4,5 @@ elemwise_forward<{{name|upper}}_NB_ELTS,
                  {{name|upper}}_ELEM_OP,
                  {{name|upper}}_ACTIVATION>
                  ({{out_name[0]}}, {{name|upper}}_RESCALING, {{in_name[0]}}, {{in_name[1]}});
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja b/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja
index cac97de..42c1174 100644
--- a/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja
@@ -8,5 +8,5 @@ fullyconnected_forward<{{ in_name[0]|upper }}_NB_CHANNELS,
                        {{ out_name[0]|upper }}_OUT_WIDTH,
                        {{name|upper}}_ACTIVATION>
                        ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{name|upper}}_RESCALING);
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja b/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja
index 591fafe..8a5b8e9 100644
--- a/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja
@@ -2,5 +2,5 @@
 {% include "./_mem_offset.jinja" %}
 leakyrelu_forward<{{name|upper}}_NB_DATA>
                    ({{input_name}}, {{output_name}}, {{name|upper}}_ALPHA);
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja b/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja
index 64b3df3..f804f7f 100644
--- a/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja
@@ -5,5 +5,5 @@ matmul_forward<{{name|upper}}_M,
                {{name|upper}}_N,
                {{name|upper}}_ACTIVATION>
                ({{in_name[0]}}, {{in_name[1]}}, {{out_name[0]}}, {{name|upper}}_RESCALING);
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja b/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja
index c730923..a56d103 100644
--- a/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja
@@ -15,5 +15,5 @@ pooling_forward<{{ in_name[0]|upper }}_NB_CHANNELS,
                 {{name|upper}}_POOLING_TYPE,
                 {{name|upper}}_ACTIVATION>
                 ({{in_name[0]}}, {{out_name[0]}});
-{% include "./_save_outputs.jinja" %}
+{# {% include "./_save_outputs.jinja" %} #}
 {% endfilter %}
-- 
GitLab


From 8a67ed96f476a4adc55f92bc89878f2b776d40b7 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:26:19 +0100
Subject: [PATCH 11/93] [Refactor] Reformat transpose jinja

---
 .../kernel_forward/transpose_ND_forward.jinja        | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja b/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja
index 25af5bd..37ecb14 100644
--- a/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja
@@ -1 +1,11 @@
-transpose_ND_forward<{{in_cdtype[0]}},{{name|upper}}_NB_DIMS>({{in_name[0]}},{{name|upper}}_DIMS,{{name|upper}}_PERMUTE,{{ out_name[0]|upper }}_SIZE,{{out_name[0]}});
\ No newline at end of file
+{% filter indent(width=4, first=False) %}
+{% include "./_mem_offset.jinja" %}
+transpose_ND_forward<{{in_cdtype[0]}},
+                     {{name|upper}}_NB_DIMS>
+                    ({{in_name[0]}},
+                     {{name|upper}}_DIMS,
+                     {{name|upper}}_PERMUTE,
+                     {{out_name[0]|upper}}_SIZE,
+                     {{out_name[0]}});
+{# {% include "./_save_outputs.jinja" %} #}
+{% endfilter %}
-- 
GitLab


From 838493da2a3f454585af5db388b6e5ef636206a5 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 14:27:23 +0100
Subject: [PATCH 12/93] [Feat] Add new lenet export and generation files

---
 examples/export_LeNet/.gitignore      |   5 +
 examples/export_LeNet/create_lenet.py | 106 ++++++
 examples/export_LeNet/lenet.py        | 460 ++++++++++++++++++++++++++
 3 files changed, 571 insertions(+)
 create mode 100644 examples/export_LeNet/.gitignore
 create mode 100644 examples/export_LeNet/create_lenet.py
 create mode 100644 examples/export_LeNet/lenet.py

diff --git a/examples/export_LeNet/.gitignore b/examples/export_LeNet/.gitignore
new file mode 100644
index 0000000..20cb419
--- /dev/null
+++ b/examples/export_LeNet/.gitignore
@@ -0,0 +1,5 @@
+# Exclude export artefacts
+export_lenet_int8/
+log_outputs/*
+assets/*
+data/*
diff --git a/examples/export_LeNet/create_lenet.py b/examples/export_LeNet/create_lenet.py
new file mode 100644
index 0000000..d135296
--- /dev/null
+++ b/examples/export_LeNet/create_lenet.py
@@ -0,0 +1,106 @@
+"""
+create_lenet.py
+
+This file creates a simple lenet network using the MNIST dataset. 
+It is meant to be used by the lenet.py file. 
+"""
+
+import random
+
+import torch
+from torchvision import datasets, transforms
+import torch.nn as nn
+import torch.nn.functional as F
+
+# Download the MNIST Dataset
+
+def get_mnist_dataset():
+    transform = transforms.ToTensor()
+    train_set = datasets.MNIST(root='./data', train=True,  transform=transform, download=True)
+    test_set  = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
+    return train_set, test_set
+
+# Create the lenet model
+
+class Classifier(torch.nn.Module):     
+    def __init__(self):
+        super().__init__()
+        self.network = nn.Sequential(
+            nn.Conv2d(1, 32, 5),  # 28 -> 24
+            nn.ReLU(),
+            nn.MaxPool2d(2, 2),   # 24 -> 12
+            nn.Conv2d(32, 32, 5), # 12 ->  8
+            nn.ReLU(),
+            nn.MaxPool2d(2, 2),   #  8 ->  4           
+            nn.Flatten(),
+            nn.Linear(32*4*4, 100),
+            nn.ReLU(),
+            nn.Linear(100, 100),
+            nn.ReLU(),
+            nn.Linear(100, 10)
+        )
+
+    def forward(self, x):
+        return self.network(x)
+
+# Compute accuracy function
+        
+def compute_accuracy(model, data_set, nb_samples):
+    nb_valid = 0
+    for it in range(nb_samples):
+        # get a sample
+        sample_idx = torch.randint(len(data_set), size=(1,)).item()
+        img, label = data_set[sample_idx]
+        # compute the output
+        x = torch.reshape(img, (1,1,28,28))
+        y_h = model.forward(x)
+        pred_label = torch.argmax(y_h).item()
+        if label == pred_label :
+            nb_valid = nb_valid + 1
+    return nb_valid / nb_samples
+
+# Train the model
+
+def train_model(NB_ITERATION, CHECK_PERIOD, train_set, test_set, classifier):
+    accuracy_history = []
+    for it in range(NB_ITERATION):
+        sample_idx = random.randint(0, len(train_set)-1)
+        img, label = train_set[sample_idx]
+        x = torch.flatten(img)
+        x = torch.reshape(x, (1,1,28,28))
+        y = torch.zeros(1,10)
+        y[0][label] = 1
+        y_h = classifier.forward(x)
+        #print(y_h.shape, 'test')
+        l = F.mse_loss(y, y_h)
+        l.backward()
+        for p in classifier.parameters():
+            with torch.no_grad():
+                p -= 0.01 * p.grad
+            p.grad.zero_()
+
+        if it % CHECK_PERIOD == 0:
+            accuracy = compute_accuracy(classifier, test_set, CHECK_PERIOD)
+            accuracy_history.append(accuracy)
+            print(f'it {it}: accuracy = {accuracy:.8f} ')
+
+
+def create_lenet():
+    
+    # Get Dataset
+    train_set, test_set = get_mnist_dataset()
+
+    # Create model
+    classifier = Classifier()
+
+    # Train model
+    NB_ITERATION = 50000
+    CHECK_PERIOD = 3000
+    print("NB_ITERATIONS = ", NB_ITERATION)
+    print("CHECK_PERIOD  = ", CHECK_PERIOD)
+    print("\nTraining LeNet...")
+    train_model(NB_ITERATION, CHECK_PERIOD, train_set, test_set, classifier)
+
+    # Export as ONNX
+    x = torch.Tensor(1,1,28,28)
+    torch.onnx.export(classifier.network, x, 'lenet.onnx', verbose=False, input_names=[ "input" ], output_names=[ "output" ])
\ No newline at end of file
diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
new file mode 100644
index 0000000..3d946e1
--- /dev/null
+++ b/examples/export_LeNet/lenet.py
@@ -0,0 +1,460 @@
+"""
+lenet.py
+
+Run this file to export a LeNet using the Aidge CPP Export module. 
+"""
+
+import random
+import numpy as np
+import os
+
+# Aidge Modules
+import aidge_core
+import aidge_onnx
+import aidge_backend_cpu
+import aidge_quantization
+import aidge_export_cpp
+
+from aidge_export_cpp.export_utils import (
+    cpp_fuse_to_metaops,
+    set_nodes_names,
+    set_nodes_datatypes)
+
+from aidge_core.export_utils import remove_optional_inputs
+
+# Torch (Dataset)
+import torch
+import torch.nn.functional as F
+from torch import nn
+from torchvision import transforms, datasets
+
+# Arguments
+import argparse
+
+supported_types = ["float32", "int8"]
+
+parser = argparse.ArgumentParser(description="Export the LeNet model with the aidge_export_cpp module.")
+parser.add_argument("--dev", action="store_true", help="Export in dev mode")
+parser.add_argument("--no_cuda", action="store_true", help="Disable USE_CUDA usage to perform inferences and training.")
+parser.add_argument("--dtype", type=str, choices=supported_types, default="float32", help="Specify the targeted datatype : [int8, float32]")
+parser.add_argument(
+    '-v', '--verbose',
+    action='count',
+    default=0,
+    help = (
+        "Set the verbosity level of the console output."
+        "Use -v to increase verbosity, with the following levels in ascending ordern"
+        "default WARN - Only warnings and higher (WARN, ERROR, FATAL) are displayed.n"
+        "-v NOTICE - Notices and higher (NOTICE, WARN, ERROR, FATAL) are displayed.n"
+        "-vv INFO - Informational messages and higher (INFO, NOTICE, WARN, ERROR, FATAL) are displayed.n"
+        "-vvv DEBUG - All messages, including debug information, are displayed.n"
+        "Available levels in descending order of severityn"
+        "DEBUG < INFO < NOTICE < WARN < ERROR < FATAL."
+    )
+)
+args = parser.parse_args()
+
+USE_CUDA = not args.no_cuda
+
+# Setting Aidge verbose level
+if args.verbose == 0:
+    aidge_core.Log.set_console_level(aidge_core.Level.Error)
+elif args.verbose == 1:
+    aidge_core.Log.set_console_level(aidge_core.Level.Notice)
+elif args.verbose == 2:
+    aidge_core.Log.set_console_level(aidge_core.Level.Info)
+elif args.verbose >= 3:
+    aidge_core.Log.set_console_level(aidge_core.Level.Debug) 
+
+if USE_CUDA:
+    import aidge_backend_cuda
+
+# ------------------------------------------------------------
+# EXPORT CONFIG
+# ------------------------------------------------------------
+
+"""
+Export configuration details :
+- RNG_SEED :        Fix a random seed for torch to always get the same images from the dataset,
+                        therefore always getting the same output. 
+- NB_TEST :         Number of example inferences to perform (used to get an accuracy approximation).
+- NB_CALIB :        Number of samples used for the calibration step of quantization. 
+- MODEL_NAME :      Should be the same name as the onnx file you want to load and export. 
+- DO_EXAMPLES :     Perform example inferences (and allow to get accuracy approximation)
+- NB_BITS :         Quantization output precision. Should be 8 to work with this export. 
+- TARGET_TYPE :     The aidge datatype for tensors to be casted after the quantization step [float64, float32, int32].
+- OPTIM_SIGN :      Quantization optional optimization based on data sign. 
+- SINGLE_SHIFT :    Quantization option specifying if inserted scaling nodes should be
+                        single shift or floating point.
+- ROUNDING :        Apply rounding on the data after the single shift step. 
+- NO_QUANTIZATION : Skip the quantization step.
+- CLIPPING :        Clipping method during quantization. 
+- FOLD_GRAPH :      The quantization step adds cast nodes to cast the graph into the given TARGET_TYPE.
+                        Enabling the FOLD_GRAPH will automatically fold these nodes into the following
+                        ones at the end of quantization step. 
+- USE_CUDA :        Determine if the quantization step uses the GPU. It is generally recommended
+                        to enable this option if you have access to GPUs as the quantization step
+                        may take a while to complete. 
+- DEV_MODE :        The dev mode allows to identify errors more easily exporting the model with 
+                        symbolic links enabling to modify the source files directly in the
+                        generated export (make sure you installed the export plugin running
+                        `pip install -e .`). 
+                        Enabled running this python file, adding the --dev argument. 
+"""
+
+print(" Available backends : ", aidge_core.Tensor.get_available_backends())
+
+quantize_model = False
+NB_BITS = 32
+TARGET_TYPE = aidge_core.dtype.float32 
+
+if args.dtype == "float32":
+    quantize_model = False
+elif args.dtype == "int8":
+    quantize_model = True
+    NB_BITS = 8
+    TARGET_TYPE = aidge_core.dtype.int32    # int8 not yet available
+else:
+    print(f"[ERROR] Datatype '{args.dtype}' not supported.")
+    print(f"[ERROR] Supported datatypes : {supported_types}.")
+    exit(1)
+
+RNG_SEED      = 1234 
+NB_TEST       = 10 # Example inferences
+NB_CALIB      = 20 # Calibration set
+MODEL_NAME    = 'lenet'
+EXPORT_FOLDER   = f"export_{MODEL_NAME}_int8"
+DO_EXAMPLES   = True
+
+# Quantization params
+OPTIM_SIGN      = False
+SINGLE_SHIFT    = True
+ROUNDING        = True
+NO_QUANTIZATION = False  
+CLIPPING        = aidge_quantization.Clipping.MSE  # 'MAX'
+FOLD_GRAPH      = True
+
+# Export modes
+DEV_MODE      = args.dev
+
+print('\n RNG_SEED         = ', RNG_SEED)
+print(' MODEL_NAME       = ', MODEL_NAME)
+print(' NB_TEST          = ', NB_TEST)
+print(' NB_CALIB         = ', NB_CALIB)
+print(' NB_BITS          = ', NB_BITS)
+print(' OPTIM_SIGN       = ', OPTIM_SIGN)
+print(' NO_QUANTIZATION  = ', NO_QUANTIZATION)
+print(' CLIPPING         = ', CLIPPING)
+print(' SINGLE_SHIFT     = ', SINGLE_SHIFT)
+print(' USE_CUDA         = ', USE_CUDA)
+print(' DEV_MODE         = ', DEV_MODE)
+print(' ROUNDING         = ', ROUNDING)
+
+torch.manual_seed(RNG_SEED)
+random.seed(RNG_SEED)
+
+backend = "cuda" if USE_CUDA else "cpu"
+
+# ------------------------------------------------------------
+# CREATE THE LENET MODEL
+# ------------------------------------------------------------
+"""
+The LeNet model is created and trained using the create_lenet file. 
+If a lenet.onnx file is already present in the current folder, this step will be skiped. 
+The generated network is not yet quantized. 
+"""
+
+from create_lenet import create_lenet
+
+if not os.path.isfile("./lenet.onnx"):
+    print("\nTraining LeNet...")
+    create_lenet()
+
+# --------------------------------------------------------------
+# CREATE THE SAMPLES
+# --------------------------------------------------------------
+
+transform = transforms.ToTensor()
+test_set  = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
+
+tensors = []
+labels  = []
+index = 0
+for input, label in test_set:
+    array = np.array(input)
+    array = np.reshape(array, (1, 1, 28, 28))
+    tensor = aidge_core.Tensor(array)
+    tensor.set_backend(backend)
+    tensor.set_datatype(aidge_core.dtype.float32)
+    tensors.append(tensor)
+    labels.append(label)
+    index += 1
+    if (index == max(NB_TEST, NB_CALIB)):
+        break
+
+# --------------------------------------------------------------
+# LOAD THE MODEL
+# --------------------------------------------------------------
+
+"""
+Load the .onnx model and perform some usual graph modifications :
+    - Remove the flatten nodes;
+    - Fuse the batchnorm nodes into the biases producers. 
+"""
+
+model = aidge_onnx.load_onnx(MODEL_NAME + ".onnx", verbose=False)
+aidge_core.remove_flatten(model)
+aidge_core.fuse_batchnorm(model)
+model.save("imported_model")
+
+# --------------------------------------------------------------
+# SET UP THE AIDGE SCHEDULER
+# --------------------------------------------------------------
+
+"""
+The scheduler is an ordered version of the model, allowing to schedule
+nodes to be able to run inferences, for instance. 
+"""
+
+# Set up the backend
+model.set_datatype(aidge_core.dtype.float32)
+model.set_backend(backend)
+
+# Create the Scheduler 
+scheduler = aidge_core.SequentialScheduler(model)
+
+# --------------------------------------------------------------
+# RUN SOME EXAMPLES INFERENCES
+# --------------------------------------------------------------
+
+def propagate(model, scheduler, tensor):
+    """ 
+    Propagate the given tensor into the model and return the
+    output tensor. 
+    """
+    # Run the inference 
+    scheduler.forward(True, [tensor])
+    # Gather the results
+    output_node = model.get_output_nodes().pop()
+    output_tensor = output_node.get_operator().get_output(0).clone()
+    output_tensor.set_backend("cpu")
+    return np.array(output_tensor)
+
+accuracy = 0
+if (DO_EXAMPLES):
+    print('\n EXAMPLE INFERENCES :')
+    nb_valid = 0
+    base_values = []
+    for i in range(NB_TEST):
+        output_array = propagate(model, scheduler, tensors[i])
+        print(labels[i], ' VS ', np.argmax(output_array), ' -> ', np.max(output_array))
+        base_values.append(np.max(output_array))
+        if (labels[i] == np.argmax(output_array)):
+            nb_valid += 1
+    accuracy = nb_valid / NB_TEST
+    print('\n MODEL ACCURACY = ', accuracy * 100, '%')
+
+# --------------------------------------------------------------
+# PERFORM THE QUANTIZATION
+# -------------------------------------------------------------- 
+
+if quantize_model:
+    aidge_quantization.quantize_network(
+        network = model, 
+        nb_bits = NB_BITS, 
+        input_dataset = tensors[0:NB_CALIB], 
+        clipping_mode = CLIPPING,
+        target_type = TARGET_TYPE,
+        no_quantization = NO_QUANTIZATION,
+        optimize_signs = OPTIM_SIGN, 
+        single_shift = SINGLE_SHIFT, 
+        use_cuda = USE_CUDA,
+        fold_graph = FOLD_GRAPH,
+        bitshift_rounding = ROUNDING)
+
+# --------------------------------------------------------------
+# RESCALE THE INPUT SAMPLES
+# --------------------------------------------------------------
+
+"""
+Once the quantization is done, the graph now only accepts integer inputs. 
+So we need to rescale the dataset for the data to be within [0, 255].
+Also, tensors should be casted to be the same type as TARGET_TYPE. 
+"""
+
+if quantize_model:
+    rescaling = 2**(NB_BITS-1)-1
+    for i in range(NB_TEST):
+        array = np.array(tensors[i]) * rescaling 
+        array = np.round(array).astype(int)
+        tensors[i] = aidge_core.Tensor(array)
+        tensors[i].set_datatype(TARGET_TYPE)
+
+# --------------------------------------------------------------
+# GENERATE NEW SCHEDULER
+# --------------------------------------------------------------
+
+"""
+Each time the graph has been change, it has to be reset. 
+Here some Quantizer and Cast nodes have been added. 
+"""
+
+""" [Issue]
+We need first to manually add an input tensor with the correct datatype, 
+as it is not automatically done in PTQ. 
+"""
+if quantize_model:
+    input_node = model.get_ordered_inputs()[0]
+    input_node[0].get_operator().set_input(0, tensors[0])
+    scheduler.reset_scheduling()
+
+# --------------------------------------------------------------
+# PERFORM THE EXAMPLE INFERENCES AGAIN
+# --------------------------------------------------------------
+
+if (DO_EXAMPLES and quantize_model):
+    print('\n QUANTIZED EXAMPLE INFERENCES :')
+    nb_valid = 0
+    post_values = []
+    for i in range(NB_TEST):
+        output_array = propagate(model, scheduler, tensors[i])
+        print(labels[i], ' VS ', np.argmax(output_array), ' -> ', np.max(output_array))
+        post_values.append(np.max(output_array))
+        if (labels[i] == np.argmax(output_array)):
+            nb_valid += 1
+
+    quant_accuracy = nb_valid / NB_TEST
+    print('\n MODEL ACCURACY = ', accuracy * 100, '%')
+    print('\n QUANTIZED ACCURACY = ', quant_accuracy * 100, '%')
+
+    output_array = propagate(model, scheduler, tensors[0])
+
+if USE_CUDA:
+    model.set_backend("cpu")
+    for tensor in tensors:
+        tensor.set_backend("cpu")
+
+# --------------------------------------------------------------
+# FUSE NODES INTO METAOPS
+# --------------------------------------------------------------
+
+"""
+Here is made the link between the Aidge model and the Jacinto
+kernels implementation. In aidge, all the nodes calculations
+are performed separately (Pad -> Conv -> Quantizer -> ReLU -> ...).
+
+However within the Jacinto export, some core operators are merged
+in meta operators. For instance, the padding, scaling and ReLU are
+performed within the Conv kernel. 
+
+In this step, we use graph regex techniques to find the desired patterns
+within the graph in order to match the export implementation of the kernels. 
+"""
+
+# Expand meta ops
+"""
+We first need to expand the graph to break all the metaops that may already
+exist. For instance, PaddedConv will become Pad -> Conv. 
+"""
+aidge_core.expand_metaops(model)
+
+# Exclude unwanted producers 
+"""
+Before fusing the nodes, we set a tag on the Producers in order to exclude
+from the export the ones holding coefficients, as they are directly handled
+within the layers parameters. 
+"""
+# exclude_unwanted_producers(model)
+
+# Fuse nodes
+cpp_fuse_to_metaops(model)
+
+# Remove optional inputs 
+"""
+Some optional inputs may be added by the quantization step (for instance with the clipping nodes).
+Here we make sure that they will not be considered as actual graph inputs by the export, by
+excluding them from the ordered_inputs list of the model. 
+"""
+remove_optional_inputs(model)
+
+# Reset scheduler to apply graph modifications
+"""
+The scheduler always needs to be reset after graph manipulation.
+"""
+scheduler.reset_scheduling()
+
+# Name newly created MetaOps
+"""
+As names are optional in Aidge, the fuse_to_metaops function will not automatically
+give a name to the newly created metaOps. However, in an export context, we need 
+our operators to be named, as this will be used to name the corresponding files.
+"""
+
+scheduler.generate_scheduling() # Scheduler needs to be generated as it has just been reset
+set_nodes_names(scheduler)
+
+# --------------------------------------------------------------
+# LOG OUTPUTS FOR THE FIRST IMAGE OF THE TEST DATASET
+# --------------------------------------------------------------
+
+"""
+Here a final inference is made on the input we want to export and run. 
+This will ensure that all the feature maps tensors (between the layers)
+hold the data corresponding to this specific input. 
+Then, the "log_outputs()" function (called later) will store these tensors
+into log files that may be exported as well for comparison purpose. 
+"""
+
+output_array = propagate(model, scheduler, tensors[0])
+
+print("### Exported Sample ###")
+print("Aidge prediction :", np.argmax(output_array), "(" + str(np.max(output_array)) + ")")
+print("Label :", labels[0])
+
+# --------------------------------------------------------------
+# HANDLING DATATYPE
+# --------------------------------------------------------------
+
+"""
+Now, despite the quantization stage, all the tensors of the model are
+still "virtually" in Int32. Before exporting the model, we have to set
+tensors' datatypes to Int8, except for biases which should remain in Int32.
+"""
+
+if quantize_model:
+    set_nodes_datatypes(model)
+
+# Store tensors values into log files
+"""
+Once the tensors has been casted, the log_outputs() function can be
+called to store their values into log files. 
+"""
+model.log_outputs("log_outputs")
+
+# --------------------------------------------------------------
+# TEST MODE
+# --------------------------------------------------------------
+
+"""
+The test mode is mainly used for validation and benchmark. The model will be 
+exported in a way that each layer's result will be compared with the CPU implementation. 
+The timings for each layer will be displayed. 
+In case of error, you will be able to enter debug mode, showing in-layer data or 
+changing the inputs of the layer, to isolate the source of the issue. 
+"""
+
+for node in model.get_nodes():
+    node.attributes().dev_mode = DEV_MODE
+
+# --------------------------------------------------------------
+# EXPORT THE MODEL
+# --------------------------------------------------------------
+
+model.save("exported_model")
+
+aidge_export_cpp.export(EXPORT_FOLDER, 
+                        model, 
+                        scheduler, 
+                        # tensors[0],
+                        labels = aidge_core.Tensor(labels[0]), 
+                        dev_mode = DEV_MODE)
-- 
GitLab


From 21ebb5de4d47e389a397f495192ea53aea008ef3 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 20 Mar 2025 16:41:14 +0100
Subject: [PATCH 13/93] [Feat] Add int8 support

- Add rescaling functions : SingleShift and FixedPoint (Mul->Shift)
- Add a rescaling node to the metaops recipes corresponding to the Quantizer node added by the PTQ
- PoC(export_utils.py) new recipe system
---
 aidge_export_cpp/export.py                    |  2 +-
 aidge_export_cpp/export_utils.py              | 71 ++++++++++++++++---
 aidge_export_cpp/kernels/rescaling.hpp        | 66 +++++++++++++++++
 aidge_export_cpp/operators/CppActivation.py   | 21 +++++-
 aidge_export_cpp/operators/CppConv.py         | 14 +++-
 aidge_export_cpp/operators/CppElemWise.py     | 21 ++++++
 aidge_export_cpp/operators/CppFc.py           | 15 ++++
 aidge_export_cpp/operators/Producer.py        | 15 ++--
 .../templates/configuration/_rescaling.jinja  |  7 ++
 .../configuration/activation_config.jinja     |  2 +-
 .../configuration/convolution_config.jinja    |  2 +-
 .../configuration/fullyconnected_config.jinja |  2 +-
 .../configuration/matmul_config.jinja         |  2 +-
 .../configuration/transpose_ND_config.jinja   |  1 -
 .../kernel_forward/activation_forward.jinja   |  3 +-
 .../kernel_forward/batchnorm_forward.jinja    |  3 +-
 .../kernel_forward/convolution_forward.jinja  |  3 +-
 .../kernel_forward/elemwise_forward.jinja     |  3 +-
 .../fullyconnected_forward.jinja              |  3 +-
 .../kernel_forward/leakyrelu_forward.jinja    |  3 +-
 .../kernel_forward/matmul_forward.jinja       |  3 +-
 .../kernel_forward/pooling_forward.jinja      |  3 +-
 .../kernel_forward/reshape_forward.jinja      |  1 +
 .../kernel_forward/transpose_ND_forward.jinja |  3 +-
 examples/export_LeNet/lenet.py                |  8 ++-
 25 files changed, 241 insertions(+), 36 deletions(-)
 create mode 100644 aidge_export_cpp/templates/configuration/_rescaling.jinja

diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py
index bb89fe3..48cb9e3 100644
--- a/aidge_export_cpp/export.py
+++ b/aidge_export_cpp/export.py
@@ -14,7 +14,7 @@ def export(export_folder_name: str,
            graphview: aidge_core.GraphView,
            scheduler: Union[List[aidge_core.Node],
                             aidge_core.Scheduler],
-           input_tensor: aidge_core.Tensor = None,
+           input_tensor: aidge_core.Tensor = None,  # Coming Soon
            labels: aidge_core.Tensor = None,
            dev_mode: bool = False):
     
diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 7be6636..e1b89b6 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -5,22 +5,51 @@ import aidge_core
 
 def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
     """ 
-    Fuse nodes into metaops adapted for the Jacinto Export
+    Fuse nodes into metaops adapted for the CPP Export
 
     :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
                        ordered input/output data within the computational graph.
     """
 
     cpp_recipes = OrderedDict({
-        # "Quantizer":    "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
-        "CppFc":          "FC->ReLU?",
-        "CppConv":        "Conv2D#->ReLU?; Conv2D#<-Pad2D?",
+        "Rescaling":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
+        "CppFc":          "FC->Rescaling?->ReLU?",
+        "CppConv":        "Conv2D#->Rescaling?->ReLU?; Conv2D#<-Pad2D?",
         "CppPool":        "(MaxPooling2D|AvgPooling2D|GlobalAveragePooling)#->ReLU?; (MaxPooling2D|AvgPooling2D|GlobalAveragePooling)#<-Pad2D?",
-        "CppElemWise":    "(Add|Mul|Sub)->ReLU?",
+        "CppElemWise":    "(Add|Mul|Sub)->Rescaling?->ReLU?",
         "CppActivation":  "ReLU"
-        # "CppScaling":     "Quantizer->ReLU?",
     })
 
+    # cpp_recipes = OrderedDict({
+    #     # FC
+    #     "FcReLU":         "FC->ReLU",
+
+    #     # Conv
+    #     "PadConv":        "Conv2D<-Pad2D",
+    #     "ConvReLU":       "Conv2D->ReLU",
+    #     "PadConvReLU":    "PadConv->ReLU",
+
+    #     # Max Pooling
+    #     "PadMaxPool":     "MaxPooling2D<-Pad2D",
+    #     "MaxPoolReLU":    "MaxPooling2D->ReLU",
+    #     "PadMaxPoolReLU": "PadMaxPool->ReLU",
+
+    #     # Average Pooling
+    #     "PadAvgPool":     "AvgPooling2D<-Pad2D",
+    #     "AvgPoolReLU":    "AvgPooling2D->ReLU",
+    #     "PadAvgPoolReLU": "PadAvgPool->ReLU",
+
+    #     # Global Average Pooling
+    #     "PadGlobalAvgPool":     "GlobalAveragePooling2D<-Pad2D",
+    #     "GlobalAvgPoolReLU":    "GlobalAveragePooling2D->ReLU",
+    #     "PadGlobalAvgPoolReLU": "PadGlobalAveragePool->ReLU",
+
+    #     # ElemWise
+    #     "AddReLU":    "Add->ReLU",
+    #     "SubReLU":    "Sub->ReLU",
+    #     "MulReLU":    "Mul->ReLU"
+    # })
+
     # Fuse Quantizers
     # aidge_core.fuse_to_metaops(
     #     graph_view, cpp_recipes["Quantizer"], "Quantizer")
@@ -32,7 +61,7 @@ def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
 
 def set_nodes_names(scheduler):
     """
-    Set the Jacinto nodes names as well as their producers. 
+    Set the CPP nodes names as well as their producers. 
     The producers naming is handled from their child node.
     
     [TODO] Fc and Conv layers will always have weights as parent 1 and 
@@ -75,8 +104,8 @@ def set_nodes_names(scheduler):
                     if parent_node.attributes().has_attr("quantization.ptq.ShiftAmount"):
                         parent_node.set_name(node.name() + "_shift")
                     # [Fix] Add scaling/add coeff nodes manually
-                    # elif node.type() in ["JScaling", "JAdd"] and parent_node.type() == "Producer":
-                    #     parent_node.set_name(node.name() + "_coeff")
+                    elif node.type() in ["CppElemWise", ""] and parent_node.type() == "Producer":
+                        parent_node.set_name(node.name() + "_coeff")
                     # [End Fix]
 
 
@@ -92,7 +121,7 @@ def set_nodes_datatypes(graph_view: aidge_core.GraphView):
     """
     for node in graph_view.get_nodes():
         if node.type() != "Producer":
-            if node.type() in ["Conv2D", "FC"]:
+            if node.type() in ["CppConv", "CppFc"]:
                 node.get_operator().get_input(0).set_datatype(aidge_core.dtype.int8)    # Input
                 node.get_operator().get_input(1).set_datatype(aidge_core.dtype.int8)    # Weights
                 if node.get_parent(2) is not None:
@@ -123,3 +152,25 @@ def read_log_file(file_path: str):
     with open(file_path, 'r') as file:
         content = file.read()
     return content
+
+
+
+def exclude_unwanted_producers(model):
+    """ Exclude some producers not needed for the export
+
+    Currently excludes the producers attached to the Mul and BitShift nodes, as they are
+    tensors holding a single data. This data is retrieved during the export
+    generation process and passed as argument directly within the Mul layer
+    configuration. 
+    """
+
+    nodes_to_ignore = ["Mul", "BitShift"]
+
+    for node in model.get_nodes():
+        node.attributes().ignore = False   
+        if node.type() == "Producer":
+            children_nodes = [n.type() for n in node.get_children()]
+            for node_type in nodes_to_ignore:
+                if node_type in children_nodes:
+                    node.attributes().ignore = True
+                    break
diff --git a/aidge_export_cpp/kernels/rescaling.hpp b/aidge_export_cpp/kernels/rescaling.hpp
index 856010d..9921047 100644
--- a/aidge_export_cpp/kernels/rescaling.hpp
+++ b/aidge_export_cpp/kernels/rescaling.hpp
@@ -1,6 +1,72 @@
 #ifndef __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
 #define __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
 
+// ---------------------------------------------------
+// ----------------- Saturate Utils ------------------
+// ---------------------------------------------------
+
+static int64_t toInt64(uint32_t lo, uint32_t hi) {
+    return (int64_t) (((uint64_t) hi) << 32ull) | ((uint64_t) lo);
+}
+
+static int64_t smlal(int32_t lhs, int32_t rhs, 
+                     uint32_t accumLo, uint32_t accumHi) 
+{
+    return ((int64_t) lhs) * ((int64_t) rhs) + toInt64(accumLo, accumHi);
+}
+
+// ---------------------------------------------------
+// --------------- Scaling by Shifting ---------------
+// ---------------------------------------------------
+
+template<int SHIFT>
+struct SingleShiftScaling {
+
+    template<typename Sum_T>
+    Sum_T operator()(Sum_T weightedSum, size_t /*output*/) const 
+    {
+        return (SHIFT != 0) ? ((weightedSum >> (SHIFT - 1)) + 1) >> 1   // Rounding
+                            : weightedSum;   
+    }
+
+    // // Shift attribute
+    // static const int mShift = SHIFT;
+    // static const Scaling_T mScalingType = SingleShift;
+
+    // // FP Attribute
+    // static const int32_t mScaling = 0;
+    // static const int64_t mFractionalBits = 0;
+
+};
+
+// ---------------------------------------------------
+// --------------- Fixed Point Scaling ---------------
+// ---------------------------------------------------
+
+template<int64_t SHIFT, int32_t COEF>
+struct FixedPointScaling {
+
+    template<typename Sum_T>
+    Sum_T operator()(Sum_T weightedSum, size_t /*output*/) const 
+    {
+        return smlal(weightedSum, COEF, HALF_LO, HALF_HI) >> SHIFT; 
+    }
+
+    // Attributes
+    static const uint32_t HALF_LO = (SHIFT > 0)
+        ? (1ull << (SHIFT - 1)) & 0xFFFFFFFF : 0;
+    static const uint32_t HALF_HI = (SHIFT > 0)
+        ? (1ull << (SHIFT - 1)) >> 32u : 0;
+    
+    // static const int32_t mScaling = SCALING;
+    // static const int64_t mFractionalBits = FRACTIONAL_BITS;
+    // static const Scaling_T mScalingType = FixedPoint;
+    // static const int mShift = 0;
+};
+
+// ---------------------------------------------------
+// ------------------- No Scaling --------------------
+// ---------------------------------------------------
 
 struct NoScaling {
 
diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
index 1cb82d9..40bd10b 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -3,7 +3,7 @@ from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
-@ExportLibCpp.register_metaop("CppActivation", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+@ExportLibCpp.register_metaop("CppActivation", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppActivation(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
@@ -11,12 +11,31 @@ class CppActivation(ExportNodeCpp):
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+        self.attributes["coef_value"] = 1
 
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
 
+        ## Get the scaling values
+        for prod in node.get_parents():
+            if prod is not None:
+                if prod.type() == "Producer":
+                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+                    # elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
+                    #     self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
+                    else:
+                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+
+        ## Set the scaling type
+        if self.attributes["coef_value"] != 1:
+            self.attributes["rescaling"] = "FixedPointScaling"
+        elif self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "activation_config.jinja")
         
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
index 47dd312..543f9d6 100644
--- a/aidge_export_cpp/operators/CppConv.py
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -3,7 +3,7 @@ from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
-@ExportLibCpp.register_metaop("CppConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+@ExportLibCpp.register_metaop("CppConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppConv(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
@@ -12,6 +12,7 @@ class CppConv(ExportNodeCpp):
         self.attributes["padding"] = [0, 0, 0, 0]
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
 
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
@@ -24,6 +25,17 @@ class CppConv(ExportNodeCpp):
                 self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
                 self.attributes["dilation_dims"] = n.get_operator().attr.dilation_dims
 
+        ## Get the scaling values
+        for prod in node.get_parents():
+            if prod is not None:
+                if prod.type() == "Producer":
+                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+
+        ## Set the scaling type
+        if self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
         
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index e2d0ba8..7f32677 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -12,12 +12,33 @@ class CppElemWise(ExportNodeCpp):
         self.attributes["elemwise_op"] = node.type()    # [Add, Mul, Sub]
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
+        ## Scaling
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+        self.attributes["coef_value"] = 1
 
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
 
+        ## Get the scaling values
+        for prod in node.get_parents():
+            if prod is not None:
+                if prod.type() == "Producer":
+                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+                    # elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
+                    #     self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
+                    else:
+                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+
+        ## Set the scaling type
+        if self.attributes["coef_value"] != 1:
+            self.attributes["rescaling"] = "FixedPointScaling"
+        elif self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "elemwise_config.jinja")
 
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index c86f8be..df8c55a 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -12,11 +12,26 @@ class CppFc(ExportNodeCpp):
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
 
+        ## Scaling
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
 
+        ## Get the scaling values
+        for prod in node.get_parents():
+            if prod is not None:
+                if prod.type() == "Producer":
+                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+
+        ## Set the scaling type
+        if self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "fullyconnected_config.jinja")
         
diff --git a/aidge_export_cpp/operators/Producer.py b/aidge_export_cpp/operators/Producer.py
index a252f8e..1ae54d3 100644
--- a/aidge_export_cpp/operators/Producer.py
+++ b/aidge_export_cpp/operators/Producer.py
@@ -47,17 +47,20 @@ class ProducerCPP(ExportNode):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
         self.values = np.array(self.operator.get_output(0))
+        self.ignore = node.attributes().ignore
 
         if len(self.values.shape) == 4:  # Note: export in HWC
             self.values = np.transpose(self.values, (0, 2, 3, 1))
 
     def export(self, export_folder: Path):
-        header_path = f"include/parameters/{self.attributes['name']}.h"
-        export_params(
-            self.attributes['out_name'][0],
-            self.values.reshape(-1),
-            str(export_folder / header_path))
-        return [header_path]
+        if not self.ignore :
+            header_path = f"include/parameters/{self.attributes['name']}.h"
+            export_params(
+                self.attributes['out_name'][0],
+                self.values.reshape(-1),
+                str(export_folder / header_path))
+            return [header_path]
+        return []
 
     def forward(self):
         # A Producer does nothing during forward
diff --git a/aidge_export_cpp/templates/configuration/_rescaling.jinja b/aidge_export_cpp/templates/configuration/_rescaling.jinja
new file mode 100644
index 0000000..8f3ad3d
--- /dev/null
+++ b/aidge_export_cpp/templates/configuration/_rescaling.jinja
@@ -0,0 +1,7 @@
+{%- if rescaling == "NoScaling" %}
+static const NoScaling {{ name|upper }}_RESCALING = {};
+{%- elif rescaling == "SingleShiftScaling" %}
+static const SingleShiftScaling<{{ shift_value }}> {{ name|upper }}_RESCALING = {};
+{%- elif rescaling == "FixedPointScaling" %}
+static const FixedPointScaling<{{ shift_value }}, {{ coef_value }}> {{ name|upper }}_RESCALING = {};
+{%- endif %}
\ No newline at end of file
diff --git a/aidge_export_cpp/templates/configuration/activation_config.jinja b/aidge_export_cpp/templates/configuration/activation_config.jinja
index 1ab5b21..df55575 100644
--- a/aidge_export_cpp/templates/configuration/activation_config.jinja
+++ b/aidge_export_cpp/templates/configuration/activation_config.jinja
@@ -9,6 +9,6 @@
 #define {{ name|upper }}_ACTIVATION {{ activation }}
 {% include "./_def_io.jinja" %}
 {% include "./_meminfo.jinja" %}
-static const {{ rescaling }} {{ name|upper }}_RESCALING = {};
+{% include "./_rescaling.jinja" %}
 
 #endif /* {{ name|upper }}_LAYER_H */
diff --git a/aidge_export_cpp/templates/configuration/convolution_config.jinja b/aidge_export_cpp/templates/configuration/convolution_config.jinja
index beb7de3..d29f9e3 100644
--- a/aidge_export_cpp/templates/configuration/convolution_config.jinja
+++ b/aidge_export_cpp/templates/configuration/convolution_config.jinja
@@ -14,7 +14,7 @@
 #define {{ name|upper }}_KERNEL_HEIGHT {{ kernel_dims[0] }}
 #define {{ name|upper }}_KERNEL_WIDTH {{ kernel_dims[1] }}
 #define {{ name|upper }}_ACTIVATION {{ activation }}
-static const {{ rescaling }} {{ name|upper }}_RESCALING = {};
+{% include "./_rescaling.jinja" %}
 
 {#- Calculate sizes #}
 {%- set weights_size = out_chan[0] * in_chan[0] * kernel_dims[1] * kernel_dims[0] %}
diff --git a/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja b/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja
index 3c80338..c14a6d3 100644
--- a/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja
+++ b/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja
@@ -6,7 +6,7 @@
 {% include "./_def_io.jinja" %}
 {% include "./_meminfo.jinja" %}
 #define {{ name|upper }}_ACTIVATION {{ activation }}
-static const {{ rescaling }} {{ name|upper }}_RESCALING = {};
+{% include "./_rescaling.jinja" %}
 
 {#- Calculate sizes #}
 {%- set weights_size = out_chan[0] * in_chan[0] * in_height[0] * in_width[0] %}
diff --git a/aidge_export_cpp/templates/configuration/matmul_config.jinja b/aidge_export_cpp/templates/configuration/matmul_config.jinja
index 38316f2..0d941c0 100644
--- a/aidge_export_cpp/templates/configuration/matmul_config.jinja
+++ b/aidge_export_cpp/templates/configuration/matmul_config.jinja
@@ -10,7 +10,7 @@
 #define {{ name|upper }}_K {{ in_dims[0][1] }}
 #define {{ name|upper }}_N {{ in_dims[1][1] }}
 #define {{ name|upper }}_ACTIVATION {{ activation }}
-static const {{ rescaling }} {{ name|upper }}_RESCALING = {};
+{% include "./_rescaling.jinja" %}
 
 {#- Calculate sizes #}
 
diff --git a/aidge_export_cpp/templates/configuration/transpose_ND_config.jinja b/aidge_export_cpp/templates/configuration/transpose_ND_config.jinja
index e5ef4ff..8879fe0 100644
--- a/aidge_export_cpp/templates/configuration/transpose_ND_config.jinja
+++ b/aidge_export_cpp/templates/configuration/transpose_ND_config.jinja
@@ -11,5 +11,4 @@
 static constexpr unsigned int {{ name|upper }}_PERMUTE[] = { {{ output_dims_order | join(', ') }} };
 static constexpr unsigned int {{ name|upper }}_DIMS[] = { {{ in_dims[0] | join(', ') }}};
 
-
 #endif /* {{ name|upper }}_LAYER_H */
\ No newline at end of file
diff --git a/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja b/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja
index 628dbec..1dc4eb5 100644
--- a/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/activation_forward.jinja
@@ -3,5 +3,6 @@
 activation_forward<{{name|upper}}_NB_DATA,
                    {{name|upper}}_ACTIVATION>
                    ({{in_name[0]}}, {{out_name[0]}}, {{name|upper}}_RESCALING);
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja b/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
index 8f8a665..39e7774 100644
--- a/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
@@ -6,5 +6,6 @@ batchnorm_forward<{{ out_name[0]|upper }}_OUT_BATCH,
                   {{ out_name[0]|upper }}_OUT_WIDTH,
                   {{name|upper}}_ACTIVATION>
                   ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{in_name[3]}}, {{in_name[4]}}, {{name|upper}}_EPSILON);
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja b/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja
index 0fdd4d4..bdde325 100644
--- a/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/convolution_forward.jinja
@@ -16,5 +16,6 @@ convolution{{ "_depthwise" if depthwise is defined else "" }}_forward<{{ in_name
                     {{name|upper}}_KERNEL_WIDTH,
                     {{name|upper}}_ACTIVATION>
                     ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{name|upper}}_RESCALING);
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja b/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
index 64ee040..0a3259b 100644
--- a/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
@@ -4,5 +4,6 @@ elemwise_forward<{{name|upper}}_NB_ELTS,
                  {{name|upper}}_ELEM_OP,
                  {{name|upper}}_ACTIVATION>
                  ({{out_name[0]}}, {{name|upper}}_RESCALING, {{in_name[0]}}, {{in_name[1]}});
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja b/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja
index 42c1174..9a35d79 100644
--- a/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/fullyconnected_forward.jinja
@@ -8,5 +8,6 @@ fullyconnected_forward<{{ in_name[0]|upper }}_NB_CHANNELS,
                        {{ out_name[0]|upper }}_OUT_WIDTH,
                        {{name|upper}}_ACTIVATION>
                        ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{name|upper}}_RESCALING);
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja b/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja
index 8a5b8e9..89cf259 100644
--- a/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/leakyrelu_forward.jinja
@@ -2,5 +2,6 @@
 {% include "./_mem_offset.jinja" %}
 leakyrelu_forward<{{name|upper}}_NB_DATA>
                    ({{input_name}}, {{output_name}}, {{name|upper}}_ALPHA);
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja b/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja
index f804f7f..090fbac 100644
--- a/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/matmul_forward.jinja
@@ -5,5 +5,6 @@ matmul_forward<{{name|upper}}_M,
                {{name|upper}}_N,
                {{name|upper}}_ACTIVATION>
                ({{in_name[0]}}, {{in_name[1]}}, {{out_name[0]}}, {{name|upper}}_RESCALING);
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja b/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja
index a56d103..fb1f2b7 100644
--- a/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/pooling_forward.jinja
@@ -15,5 +15,6 @@ pooling_forward<{{ in_name[0]|upper }}_NB_CHANNELS,
                 {{name|upper}}_POOLING_TYPE,
                 {{name|upper}}_ACTIVATION>
                 ({{in_name[0]}}, {{out_name[0]}});
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/reshape_forward.jinja b/aidge_export_cpp/templates/kernel_forward/reshape_forward.jinja
index f9752bc..6af8ece 100644
--- a/aidge_export_cpp/templates/kernel_forward/reshape_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/reshape_forward.jinja
@@ -3,4 +3,5 @@
 reshape_forward<{{name|upper}}_NB_ELTS>
                  ({{in_name[0]}}, {{in_name[1]}}, {{out_name[0]}});
 {% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja b/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja
index 37ecb14..5fc06d6 100644
--- a/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/transpose_ND_forward.jinja
@@ -7,5 +7,6 @@ transpose_ND_forward<{{in_cdtype[0]}},
                      {{name|upper}}_PERMUTE,
                      {{out_name[0]|upper}}_SIZE,
                      {{out_name[0]}});
-{# {% include "./_save_outputs.jinja" %} #}
+{% include "./_save_outputs.jinja" %}
+{% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index 3d946e1..176d070 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -4,9 +4,10 @@ lenet.py
 Run this file to export a LeNet using the Aidge CPP Export module. 
 """
 
+import os
+import shutil
 import random
 import numpy as np
-import os
 
 # Aidge Modules
 import aidge_core
@@ -18,7 +19,8 @@ import aidge_export_cpp
 from aidge_export_cpp.export_utils import (
     cpp_fuse_to_metaops,
     set_nodes_names,
-    set_nodes_datatypes)
+    set_nodes_datatypes,
+    exclude_unwanted_producers)
 
 from aidge_core.export_utils import remove_optional_inputs
 
@@ -364,7 +366,7 @@ Before fusing the nodes, we set a tag on the Producers in order to exclude
 from the export the ones holding coefficients, as they are directly handled
 within the layers parameters. 
 """
-# exclude_unwanted_producers(model)
+exclude_unwanted_producers(model)
 
 # Fuse nodes
 cpp_fuse_to_metaops(model)
-- 
GitLab


From 83f86a794e2a9504bdaf83c491d3af4638da3dc8 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 20 Mar 2025 16:44:57 +0100
Subject: [PATCH 14/93] [Feat] Add the aidge_cmp option and refactor
 save_outputs function

- aidge_cmp allows to compare the export feature maps with these generated by the aidge_cpu backend at inference time
- Fix the save_outputs function (will not work with wrapping as it does no longer take into account the wrapping offsets)
---
 aidge_export_cpp/export.py                    | 36 +++++++-
 aidge_export_cpp/operators/CppActivation.py   |  8 ++
 aidge_export_cpp/operators/CppConv.py         |  9 +-
 aidge_export_cpp/operators/CppElemWise.py     | 10 ++-
 aidge_export_cpp/operators/CppFc.py           |  8 +-
 aidge_export_cpp/operators/CppPool.py         |  9 +-
 aidge_export_cpp/static/utils.hpp             | 83 ++++++++++++-------
 .../templates/data/aidge_tensor.jinja         |  7 ++
 .../templates/kernel_forward/_aidge_cmp.jinja |  8 ++
 examples/export_LeNet/lenet.py                | 27 +++++-
 10 files changed, 163 insertions(+), 42 deletions(-)
 create mode 100644 aidge_export_cpp/templates/data/aidge_tensor.jinja
 create mode 100644 aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja

diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py
index 48cb9e3..58777f8 100644
--- a/aidge_export_cpp/export.py
+++ b/aidge_export_cpp/export.py
@@ -1,13 +1,15 @@
 import os
 import shutil
+import numpy as np
+from pathlib import Path
 from typing import List, Union
 
 import aidge_core
 from aidge_core.mem_info import generate_optimized_memory_info
-from aidge_core.export_utils import scheduler_export, generate_main_cpp
+from aidge_core.export_utils import scheduler_export, generate_main_cpp, aidge2c, generate_file
 
-from aidge_export_cpp import ExportLibCpp
-# from aidge_export_cpp.export_utils import read_log_file
+from aidge_export_cpp import ExportLibCpp, ROOT
+from aidge_export_cpp.export_utils import read_log_file
 
 
 def export(export_folder_name: str,
@@ -16,7 +18,8 @@ def export(export_folder_name: str,
                             aidge_core.Scheduler],
            input_tensor: aidge_core.Tensor = None,  # Coming Soon
            labels: aidge_core.Tensor = None,
-           dev_mode: bool = False):
+           dev_mode: bool = False,
+           aidge_cmp: bool = False):
     
     """ Export an aidge_core.Scheduler to C++ code
     
@@ -40,6 +43,8 @@ def export(export_folder_name: str,
     :type dev_mode: boolean
     """
 
+    export_folder_name = Path(export_folder_name)
+
     # Remove existing export
     if os.path.isdir(export_folder_name):
         print("Removing existing export directory...")
@@ -63,3 +68,26 @@ def export(export_folder_name: str,
     
     # Generate main file
     generate_main_cpp(export_folder_name, graphview, labels=labels)
+
+    # Generate log files (aidge_cmp option)
+    """
+    If the aidge_cmp option has been enabled, the generated log_outputs will
+    be copied into the generated export in order to be used as reference. 
+    """
+    if aidge_cmp:
+        os.makedirs(export_folder_name / "data" / "aidge_outputs")
+        os.makedirs(export_folder_name / "data" / "export_outputs")
+        for node in graphview.get_nodes():
+            if node.type() != "Producer":
+                file_path = 'log_outputs/' + node.name() + '/output_0.log'
+                data_t = aidge2c(node.get_operator().get_output(0).dtype())
+                name = node.name() + '_output_0_aidge'
+                dims = node.get_operator().get_output(0).dims()
+                values = read_log_file(file_path)
+
+                generate_file(export_folder_name / "data" / "aidge_outputs" / (node.name() + ".hpp"),
+                              ROOT / "templates" / "data" / "aidge_tensor.jinja",
+                              data_t=data_t,
+                              name=name,
+                              dims=dims,
+                              values=values)
diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
index 40bd10b..c1959dd 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -10,6 +10,9 @@ class CppActivation(ExportNodeCpp):
 
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
+        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+
+        ## Scaling
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["shift_value"] = 0
         self.attributes["coef_value"] = 1
@@ -48,4 +51,9 @@ class CppActivation(ExportNodeCpp):
         # Path to the kernel(s) files to copy
         self.kernels_to_copy = [ROOT / "kernels" / "activation.hpp",
                                 ROOT / "kernels" / "rescaling.hpp"]
+        
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
         
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
index 543f9d6..e97f6b3 100644
--- a/aidge_export_cpp/operators/CppConv.py
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -11,6 +11,9 @@ class CppConv(ExportNodeCpp):
         # Initialize kernel attributes
         self.attributes["padding"] = [0, 0, 0, 0]
         self.attributes["activation"] = "Linear"
+        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+
+        ## Scaling
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["shift_value"] = 0
 
@@ -50,4 +53,8 @@ class CppConv(ExportNodeCpp):
                                 ROOT / "kernels" / "macs.hpp",
                                 ROOT / "kernels" / "rescaling.hpp",
                                 ROOT / "kernels" / "activation.hpp"]
-        
\ No newline at end of file
+        
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index 7f32677..083a560 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -3,7 +3,7 @@ from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
-@ExportLibCpp.register_metaop("CppElemWise", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+@ExportLibCpp.register_metaop("CppElemWise", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppElemWise(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
@@ -12,6 +12,8 @@ class CppElemWise(ExportNodeCpp):
         self.attributes["elemwise_op"] = node.type()    # [Add, Mul, Sub]
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
+        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["shift_value"] = 0
@@ -52,4 +54,8 @@ class CppElemWise(ExportNodeCpp):
         self.kernels_to_copy = [ROOT / "kernels" / "elemwise.hpp",
                                 ROOT / "kernels" / "rescaling.hpp",
                                 ROOT / "kernels" / "activation.hpp"]
-        
\ No newline at end of file
+        
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index df8c55a..6d9f75f 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -3,7 +3,7 @@ from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
-@ExportLibCpp.register_metaop("CppFc", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+@ExportLibCpp.register_metaop("CppFc", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppFc(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
@@ -11,6 +11,7 @@ class CppFc(ExportNodeCpp):
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
+        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
 
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
@@ -46,3 +47,8 @@ class CppFc(ExportNodeCpp):
                                 ROOT / "kernels" / "macs.hpp",
                                 ROOT / "kernels" / "rescaling.hpp",
                                 ROOT / "kernels" / "activation.hpp"]
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
index 842680e..177fc59 100644
--- a/aidge_export_cpp/operators/CppPool.py
+++ b/aidge_export_cpp/operators/CppPool.py
@@ -3,7 +3,7 @@ from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
-@ExportLibCpp.register_metaop("CppPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+@ExportLibCpp.register_metaop("CppPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppPool(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
@@ -13,6 +13,7 @@ class CppPool(ExportNodeCpp):
         self.attributes["padding"] = [0, 0, 0, 0]
         self.attributes["pool_type"] = "Max"
         self.attributes["activation"] = "Linear"
+        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
 
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
@@ -44,4 +45,8 @@ class CppPool(ExportNodeCpp):
         # Path to the kernel(s) files to copy
         self.kernels_to_copy = [ROOT / "kernels" / "pooling.hpp",
                                 ROOT / "kernels" / "activation.hpp"]
-        
\ No newline at end of file
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/static/utils.hpp b/aidge_export_cpp/static/utils.hpp
index e2bfbe2..8316e5e 100644
--- a/aidge_export_cpp/static/utils.hpp
+++ b/aidge_export_cpp/static/utils.hpp
@@ -1,13 +1,17 @@
 #ifndef __AIDGE_EXPORT_CPP_NETWORK_UTILS__
 #define __AIDGE_EXPORT_CPP_NETWORK_UTILS__
 
-#ifdef SAVE_OUTPUTS
+#if SAVE_OUTPUTS
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <cstdio>      // fprintf
 #include <type_traits> // std::is_floating_point
 #endif
 
+#if AIDGE_CMP
+#include <string>
+#endif
+
 /**
  * @brief   Integer clamping
  * @param[in]  v   Value to be clamped
@@ -49,7 +53,7 @@ int min (int lhs, int rhs)
 }
 
 
-#ifdef SAVE_OUTPUTS
+#if SAVE_OUTPUTS
 enum class Format {
     Default,
     NCHW,
@@ -65,11 +69,11 @@ template<typename Output_T>
 inline void saveOutputs(
     int NB_OUTPUTS,
     int OUTPUTS_HEIGHT, int OUTPUTS_WIDTH,
-    int OUTPUT_MEM_CONT_OFFSET,
-    int OUTPUT_MEM_CONT_SIZE,
-    int OUTPUT_MEM_WRAP_OFFSET,
-    int OUTPUT_MEM_WRAP_SIZE,
-    int OUTPUT_MEM_STRIDE,
+    // int OUTPUT_MEM_CONT_OFFSET,
+    // int OUTPUT_MEM_CONT_SIZE,
+    // int OUTPUT_MEM_WRAP_OFFSET,
+    // int OUTPUT_MEM_WRAP_SIZE,
+    // int OUTPUT_MEM_STRIDE,
     const Output_T* __restrict outputs,
     FILE* pFile,
     Format format)
@@ -77,27 +81,29 @@ inline void saveOutputs(
     // default is NHCW !
     if (format == Format::NHWC) {
         fprintf(pFile, "(");
+        auto oOffset = 0;
         for(int oy = 0; oy < OUTPUTS_HEIGHT; oy++) {
             fprintf(pFile, "(");
 
             for(int ox = 0; ox < OUTPUTS_WIDTH; ox++) {
                 fprintf(pFile, "(");
 
-                const int oPos = (ox + OUTPUTS_WIDTH * oy);
-                int oOffset = OUTPUT_MEM_STRIDE * oPos;
+                // const int oPos = (ox + OUTPUTS_WIDTH * oy);
+                // int oOffset = OUTPUT_MEM_STRIDE * oPos;
 
-                if (OUTPUT_MEM_WRAP_SIZE > 0
-                    && oOffset >= OUTPUT_MEM_CONT_SIZE)
-                {
-                    oOffset += OUTPUT_MEM_WRAP_OFFSET - OUTPUT_MEM_CONT_OFFSET
-                                - OUTPUT_MEM_CONT_SIZE;
-                }
+                // if (OUTPUT_MEM_WRAP_SIZE > 0
+                //     && oOffset >= OUTPUT_MEM_CONT_SIZE)
+                // {
+                //     oOffset += OUTPUT_MEM_WRAP_OFFSET - OUTPUT_MEM_CONT_OFFSET
+                //                 - OUTPUT_MEM_CONT_SIZE;
+                // }
 
                 for (int output = 0; output < NB_OUTPUTS; output++) {
                     if (std::is_floating_point<Output_T>::value)
-                        fprintf(pFile, "%f", static_cast<float>(outputs[oOffset + output]));
+                        fprintf(pFile, "%f", static_cast<float>(outputs[oOffset]));
                     else
-                        fprintf(pFile, "%d", static_cast<int>(outputs[oOffset + output]));
+                        fprintf(pFile, "%d", static_cast<int>(outputs[oOffset]));
+                    oOffset += 1;
 
                     fprintf(pFile, ", ");
                 }
@@ -111,25 +117,14 @@ inline void saveOutputs(
         fprintf(pFile, ")\n");
     }
     else if (format == Format::NCHW || format == Format::Default) {
+        auto ofst = 0;
         for(int output = 0; output < NB_OUTPUTS; output++) {
             fprintf(pFile, "%d:\n", output);
             for(int oy = 0; oy < OUTPUTS_HEIGHT; oy++) {
                 for(int ox = 0; ox < OUTPUTS_WIDTH; ox++) {
-                    const int oPos = (ox + OUTPUTS_WIDTH * oy);
-                    int oOffset = OUTPUT_MEM_STRIDE * oPos;
-                    if (OUTPUT_MEM_WRAP_SIZE > 0
-                        && oOffset >= OUTPUT_MEM_CONT_SIZE)
-                    {
-                        oOffset += OUTPUT_MEM_WRAP_OFFSET
-                            - OUTPUT_MEM_CONT_OFFSET - OUTPUT_MEM_CONT_SIZE;
-                    }
-
-                    if (std::is_floating_point<Output_T>::value)
-                        fprintf(pFile, "%f", static_cast<float>(outputs[oOffset + output]));
-                    else
-                        fprintf(pFile, "%d",  static_cast<int>(outputs[oOffset + output]));
-
+                    fprintf(pFile, "%d",  static_cast<int>(outputs[ofst]));
                     fprintf(pFile, " ");
+                    ofst += 1;
                 }
 
                 fprintf(pFile, "\n");
@@ -146,4 +141,30 @@ inline void saveOutputs(
 }
 #endif // SAVE_OUTPUTS
 
+#if AIDGE_CMP
+
+template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, typename AidgeOutput_T, typename DevOutput_T>
+void aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
+
+    printf("[AIDGE COMPARE] - %s\n", layer_name.c_str());
+
+    for (auto out = 0; out < NB_OUTPUTS; ++out) {
+        for (auto h = 0; h < OUT_HEIGHT; ++h) {
+            for (auto w = 0; w < OUT_WIDTH; ++w) {
+                const int aidge_ofst = out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
+                const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
+                if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
+                    printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
+                           out, h, w, aidge_output[aidge_ofst], dev_output[dev_ofst]);
+                    printf("Abort program.\n");
+                    exit(1);
+                }
+            }
+        }
+    }
+    printf("[SUCCESS]\n\n");
+}
+
+#endif  // AIDGE_CMP
+
 #endif // __AIDGE_EXPORT_CPP_NETWORK_UTILS__
diff --git a/aidge_export_cpp/templates/data/aidge_tensor.jinja b/aidge_export_cpp/templates/data/aidge_tensor.jinja
new file mode 100644
index 0000000..f4f873f
--- /dev/null
+++ b/aidge_export_cpp/templates/data/aidge_tensor.jinja
@@ -0,0 +1,7 @@
+#include <stdint.h>
+
+static const {{ data_t }} {{ name }}
+{%- for dim in dims -%}
+    [{{ dim }}]
+{%- endfor %} __attribute__((section(".nn_ddr"))) = 
+{{ values }};
diff --git a/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja b/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja
new file mode 100644
index 0000000..4ca8af8
--- /dev/null
+++ b/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja
@@ -0,0 +1,8 @@
+{%- if aidge_cmp %}
+#if AIDGE_CMP
+    aidge_cmp<{{ out_name[0] | upper }}_NB_OUTPUTS, 
+              {{ out_name[0] | upper }}_OUT_HEIGHT, 
+              {{ out_name[0] | upper }}_OUT_WIDTH>
+             ("{{ name }}", (int8_t*) {{ out_name[0] }}_aidge, {{ out_name[0] }});
+#endif
+{%- endif %}
\ No newline at end of file
diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index 176d070..af529f9 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -39,6 +39,7 @@ parser = argparse.ArgumentParser(description="Export the LeNet model with the ai
 parser.add_argument("--dev", action="store_true", help="Export in dev mode")
 parser.add_argument("--no_cuda", action="store_true", help="Disable USE_CUDA usage to perform inferences and training.")
 parser.add_argument("--dtype", type=str, choices=supported_types, default="float32", help="Specify the targeted datatype : [int8, float32]")
+parser.add_argument("--aidge_cmp", action="store_true", help="Use aidge tensor results as reference.")
 parser.add_argument(
     '-v', '--verbose',
     action='count',
@@ -102,6 +103,9 @@ Export configuration details :
                         generated export (make sure you installed the export plugin running
                         `pip install -e .`). 
                         Enabled running this python file, adding the --dev argument. 
+- AIDGE_CMP :       Saves and export the outputs generated by the aidge inferences in order
+                        to compare it with the export outputs. 
+                        Enabled running this python file, adding the --aidge_cmp argument. 
 """
 
 print(" Available backends : ", aidge_core.Tensor.get_available_backends())
@@ -138,6 +142,7 @@ FOLD_GRAPH      = True
 
 # Export modes
 DEV_MODE      = args.dev
+AIDGE_CMP     = args.aidge_cmp
 
 print('\n RNG_SEED         = ', RNG_SEED)
 print(' MODEL_NAME       = ', MODEL_NAME)
@@ -431,6 +436,9 @@ if quantize_model:
 Once the tensors has been casted, the log_outputs() function can be
 called to store their values into log files. 
 """
+
+if os.path.isdir("log_outputs"):
+    shutil.rmtree("log_outputs")
 model.log_outputs("log_outputs")
 
 # --------------------------------------------------------------
@@ -448,6 +456,22 @@ changing the inputs of the layer, to isolate the source of the issue.
 for node in model.get_nodes():
     node.attributes().dev_mode = DEV_MODE
 
+# --------------------------------------------------------------
+# AIDGE CMP
+# --------------------------------------------------------------
+
+"""
+If the --aidge_cmp option is enabled, the feature maps generated by aidge with the 
+backend cpu will be exported in the generated export. It will be used as reference
+to verify that the results with the optimized kernels are correct for the exported
+model. 
+This option has to be passed to each node in order to be used within the Export Nodes.
+(JConv, JPad, ...) that you can find in the "operators" folder. 
+"""
+
+for node in model.get_nodes():
+    node.attributes().aidge_cmp = AIDGE_CMP
+
 # --------------------------------------------------------------
 # EXPORT THE MODEL
 # --------------------------------------------------------------
@@ -459,4 +483,5 @@ aidge_export_cpp.export(EXPORT_FOLDER,
                         scheduler, 
                         # tensors[0],
                         labels = aidge_core.Tensor(labels[0]), 
-                        dev_mode = DEV_MODE)
+                        dev_mode = DEV_MODE,
+                        aidge_cmp = AIDGE_CMP)
-- 
GitLab


From c0000b7763eff08db71899b5c31d97de0f3d92d6 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 20 Mar 2025 16:45:59 +0100
Subject: [PATCH 15/93] [Fix] Remove memory info from arg list for save_outputs
 function

---
 .../templates/kernel_forward/_save_outputs.jinja      | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja b/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
index ddefc0c..7ceab6d 100644
--- a/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
@@ -1,20 +1,19 @@
-/*
-#ifdef SAVE_OUTPUTS
+
+#if SAVE_OUTPUTS
 {% for outidx in range(nb_out) -%}
-    FILE* {{out_name[outidx]|upper}}_STREAM = fopen("outputs/{{out_name[outidx]}}.txt", "w");
+    FILE* {{out_name[outidx]|upper}}_STREAM = fopen("data/export_outputs/{{out_name[outidx]}}.txt", "w");
     saveOutputs<{{out_cdtype[outidx]}}>(
         {{out_name[outidx]|upper}}_NB_OUTPUTS,
         {{out_name[outidx]|upper}}_OUT_HEIGHT,
         {{out_name[outidx]|upper}}_OUT_WIDTH,
-        {{out_name[outidx]|upper}}_CONT_OFFSET,
+        {# {{out_name[outidx]|upper}}_CONT_OFFSET,
         {{out_name[outidx]|upper}}_CONT_SIZE,
         {{out_name[outidx]|upper}}_WRAP_OFFSET,
         {{out_name[outidx]|upper}}_WRAP_SIZE,
-        {{out_name[outidx]|upper}}_STRIDE,
+        {{out_name[outidx]|upper}}_STRIDE, #}
         {{out_name[outidx]}},
         {{out_name[outidx]|upper}}_STREAM,
         Format::{{out_format[outidx]}});
     fclose({{out_name[outidx]|upper}}_STREAM);
 {% endfor %}
 #endif
-*/
-- 
GitLab


From 5054c9bea4e5d885cab4d37515a40789014a39bf Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 20 Mar 2025 16:47:12 +0100
Subject: [PATCH 16/93] [Fix] Temporarily generate only layer params for the
 first input as all inputs were taken into account (even producers) leading to
 unused macros of compilation errors

---
 aidge_export_cpp/templates/configuration/_def_io.jinja | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/aidge_export_cpp/templates/configuration/_def_io.jinja b/aidge_export_cpp/templates/configuration/_def_io.jinja
index f444547..c420f7a 100644
--- a/aidge_export_cpp/templates/configuration/_def_io.jinja
+++ b/aidge_export_cpp/templates/configuration/_def_io.jinja
@@ -1,11 +1,15 @@
 {# NOTE: Suppose input is first #}
-// INPUT CONF
+{# // INPUT CONF
 {% for inidx in range(nb_in) -%}
 #define {{ in_name[inidx]|upper }}_NB_CHANNELS {{ in_chan[inidx] }}
 #define {{ in_name[inidx]|upper }}_IN_HEIGHT {{ in_height[inidx] }}
 #define {{ in_name[inidx]|upper }}_IN_WIDTH {{ in_width[inidx] }}
-#define {{ in_name[inidx]|upper }}_IN_BATCH {{ in_batch[inidx] }}
-{% endfor %}
+{% endfor %} #}
+
+// INPUT CONF
+#define {{ in_name[0]|upper }}_NB_CHANNELS {{ in_chan[0] }}
+#define {{ in_name[0]|upper }}_IN_HEIGHT {{ in_height[0] }}
+#define {{ in_name[0]|upper }}_IN_WIDTH {{ in_width[0] }}
 
 // OUTPUT CONF
 {% for outidx in range(nb_out) -%}
-- 
GitLab


From 66395330da7e902a8fc1f8e2223bd5e4d524c1f8 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 21 Mar 2025 09:46:08 +0100
Subject: [PATCH 17/93] [Feat] Change the kernels_to_copy system

The old system had some limitations :
- Not possible to chose a destination path different from the source path
- The copied kernel would be automatically included in the fwd file, sometime leading to unused includes
---
 aidge_export_cpp/operators/CppActivation.py               | 4 ++--
 aidge_export_cpp/operators/CppConv.py                     | 8 ++++----
 aidge_export_cpp/operators/CppElemWise.py                 | 6 +++---
 aidge_export_cpp/operators/CppFc.py                       | 8 ++++----
 aidge_export_cpp/operators/CppPool.py                     | 4 ++--
 .../templates/kernel_forward/_save_outputs.jinja          | 2 +-
 6 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
index c1959dd..01ac0a3 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -49,8 +49,8 @@ class CppActivation(ExportNodeCpp):
         self.include_list = []
         
         # Path to the kernel(s) files to copy
-        self.kernels_to_copy = [ROOT / "kernels" / "activation.hpp",
-                                ROOT / "kernels" / "rescaling.hpp"]
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
         
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
index e97f6b3..466f9a1 100644
--- a/aidge_export_cpp/operators/CppConv.py
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -49,10 +49,10 @@ class CppConv(ExportNodeCpp):
         self.include_list = []
         
         # Path to the kernel(s) files to copy
-        self.kernels_to_copy = [ROOT / "kernels" / "convolution.hpp",
-                                ROOT / "kernels" / "macs.hpp",
-                                ROOT / "kernels" / "rescaling.hpp",
-                                ROOT / "kernels" / "activation.hpp"]
+        self.add_kernel_to_copy(ROOT / "kernels" / "convolution.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
         
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index 083a560..416ef61 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -51,9 +51,9 @@ class CppElemWise(ExportNodeCpp):
         self.include_list = []
 
         # Path to the kernel(s) files to copy
-        self.kernels_to_copy = [ROOT / "kernels" / "elemwise.hpp",
-                                ROOT / "kernels" / "rescaling.hpp",
-                                ROOT / "kernels" / "activation.hpp"]
+        self.add_kernel_to_copy(ROOT / "kernels" / "elemwise.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
         
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index 6d9f75f..4bb8d4e 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -43,10 +43,10 @@ class CppFc(ExportNodeCpp):
         self.include_list = []
         
         # Path to the kernel(s) files to copy
-        self.kernels_to_copy = [ROOT / "kernels" / "fullyconnected.hpp",
-                                ROOT / "kernels" / "macs.hpp",
-                                ROOT / "kernels" / "rescaling.hpp",
-                                ROOT / "kernels" / "activation.hpp"]
+        self.add_kernel_to_copy(ROOT / "kernels" / "fullyconnected.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
 
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
index 177fc59..c2fb048 100644
--- a/aidge_export_cpp/operators/CppPool.py
+++ b/aidge_export_cpp/operators/CppPool.py
@@ -43,8 +43,8 @@ class CppPool(ExportNodeCpp):
         self.include_list = []
         
         # Path to the kernel(s) files to copy
-        self.kernels_to_copy = [ROOT / "kernels" / "pooling.hpp",
-                                ROOT / "kernels" / "activation.hpp"]
+        self.add_kernel_to_copy(ROOT / "kernels" / "pooling.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
 
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja b/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
index 7ceab6d..f9962dd 100644
--- a/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
@@ -6,7 +6,7 @@
         {{out_name[outidx]|upper}}_NB_OUTPUTS,
         {{out_name[outidx]|upper}}_OUT_HEIGHT,
         {{out_name[outidx]|upper}}_OUT_WIDTH,
-        {# {{out_name[outidx]|upper}}_CONT_OFFSET,
+        {#- {{out_name[outidx]|upper}}_CONT_OFFSET,
         {{out_name[outidx]|upper}}_CONT_SIZE,
         {{out_name[outidx]|upper}}_WRAP_OFFSET,
         {{out_name[outidx]|upper}}_WRAP_SIZE,
-- 
GitLab


From 454fa8d1e82f216a46b0d38c74fc2529f8e2c34d Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 4 Apr 2025 10:59:46 +0200
Subject: [PATCH 18/93] [Fix] MetaOp Recipe Pooling

---
 aidge_export_cpp/export_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index e1b89b6..7b7477d 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -15,7 +15,7 @@ def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
         "Rescaling":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
         "CppFc":          "FC->Rescaling?->ReLU?",
         "CppConv":        "Conv2D#->Rescaling?->ReLU?; Conv2D#<-Pad2D?",
-        "CppPool":        "(MaxPooling2D|AvgPooling2D|GlobalAveragePooling)#->ReLU?; (MaxPooling2D|AvgPooling2D|GlobalAveragePooling)#<-Pad2D?",
+        "CppPool":        "(MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)->ReLU?; (MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)<-Pad2D?",
         "CppElemWise":    "(Add|Mul|Sub)->Rescaling?->ReLU?",
         "CppActivation":  "ReLU"
     })
-- 
GitLab


From e6abb272b3a36722c2f13738a46fbd231b95ecc5 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 15 Apr 2025 14:54:44 +0200
Subject: [PATCH 19/93] [Refactor] Remove old operators.py file

---
 aidge_export_cpp/operators.py | 524 ----------------------------------
 1 file changed, 524 deletions(-)
 delete mode 100644 aidge_export_cpp/operators.py

diff --git a/aidge_export_cpp/operators.py b/aidge_export_cpp/operators.py
deleted file mode 100644
index e27dee2..0000000
--- a/aidge_export_cpp/operators.py
+++ /dev/null
@@ -1,524 +0,0 @@
-import os
-import numpy as np
-from pathlib import Path
-import aidge_core
-from aidge_core.export_utils import ExportNode, ExportNodeCpp, generate_file
-from aidge_export_cpp.utils import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-##############################################
-############## Export functions ##############
-##############################################
-def numpy_dtype2ctype(dtype):
-    if dtype == np.int8:
-        return "int8_t"
-    elif dtype == np.int16:
-        return "int16_t"
-    elif dtype == np.int32:
-        return "int32_t"
-    elif dtype == np.int64:
-        return "int64_t"
-    elif dtype == np.float32:
-        return "float"
-    elif dtype == np.float64:
-        return "double"
-    # Add more dtype mappings as needed
-    else:
-        raise ValueError(f"Unsupported {dtype} dtype")
-
-def export_params(name: str,
-                  array: np.ndarray,
-                  filepath: str):
-
-    # Get directory name of the file
-    dirname = os.path.dirname(filepath)
-
-    # If directory doesn't exist, create it
-    if not os.path.exists(dirname):
-        os.makedirs(dirname)
-
-    generate_file(
-        filepath,
-        str(ROOT / "templates" / "data" / "parameters.jinja"),
-        name=name,
-        data_t=numpy_dtype2ctype(array.dtype),
-        values=array.tolist()
-    )
-
-
-##############################################
-############## Operators helper ##############
-##############################################
-
-@ExportLibCpp.register("Producer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class ProducerCPP(ExportNode):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.values = np.array(self.operator.get_output(0))
-
-        if len(self.values.shape) == 4:  # Note: export in HWC
-            self.values =  np.transpose(self.values, (0, 2, 3, 1))
-
-    def export(self, export_folder: Path):
-        header_path = f"include/parameters/{self.attributes['name']}.h"
-        export_params(
-            self.attributes['out_name'][0],
-            self.values.reshape(-1),
-            str(export_folder / header_path))
-        return [header_path]
-
-    def forward(self):
-        # A Producer does nothing during forward
-        return []
-
-# TODO : find a way to remove this dummy exportnode
-@ExportLibCpp.register("Pad2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class PadCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["padding"] = node.get_operator().attr.begin_end_borders
-        self.attributes["border_type"] = node.get_operator().attr.border_type
-        self.attributes["border_value"] = node.get_operator().attr.border_value
-
-        assert self.attributes["border_type"] == aidge_core.pad_border_type.Constant, (
-            f"export Pad2d: border_type == {node.get_operator().attr.border_type} not implemented"
-        )
-
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "pad_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "pad_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "pad.hpp")
-        ]
-
-@ExportLibCpp.register("ReLU", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class ReLUCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["activation"] = "Rectifier"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "activation_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "activation_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "activation.hpp"),
-            str(ROOT / "kernels" / "rescaling.hpp")
-        ]
-
-@ExportLibCpp.register("Reshape", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class ReshapeCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "reshape_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "reshape_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "reshape.hpp"),
-        ]
-
-@ExportLibCpp.register("MatMul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class MatMulCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "matmul_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "matmul_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "matmul.hpp"),
-        ]
-
-def _setup_conv2D(conv):
-    """Common setup code for convolutions: Conv2D and PaddedConv2D."""
-
-    # If biases are not provided we set it as nullptr instead of None
-    if (len(conv.attributes["in_name"]) > 2 and conv.attributes["in_name"][2] is None):
-        conv.attributes["in_name"][2] = "nullptr"
-
-    conv.attributes["activation"] = "Linear"
-    conv.attributes["rescaling"] = "NoScaling"
-    conv.config_template = str(
-        ROOT / "templates" / "configuration" / "convolution_config.jinja")
-    conv.forward_template = str(
-        ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
-    conv.include_list = []
-    conv.kernels_to_copy = [
-        str(ROOT / "kernels" / "convolution.hpp"),
-        str(ROOT / "kernels" / "macs.hpp"),
-        str(ROOT / "kernels" / "activation.hpp"),
-        str(ROOT / "kernels" / "rescaling.hpp")
-    ]
-
-@ExportLibCpp.register("Conv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class ConvCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        # No padding with Conv
-        # Use PaddedConv to add padding attribute
-        self.attributes["padding"] = [0, 0]
-
-        _setup_conv2D(self)
-
-@ExportLibCpp.register_metaop("PaddedConv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class PaddedConvCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        # TODO find a way to retrive attr for meta op
-        for n in self.operator.get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator(
-                ).attr.begin_end_borders
-            if n.type() == "Conv2D":
-                self.attributes["kernel_dims"] = n.get_operator(
-                ).attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator(
-                ).attr.stride_dims
-                self.attributes["dilation_dims"] = n.get_operator(
-                ).attr.dilation_dims
-
-        _setup_conv2D(self)
-
-@ExportLibCpp.register("ConvDepthWise2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class ConvDepthWiseCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["depthwise"] = True
-
-        # No padding with Conv
-        # Use PaddedConv to add padding attribute
-        self.attributes["padding"] = [0, 0]
-
-        _setup_conv2D(self)
-
-@ExportLibCpp.register_metaop("PaddedConvDepthWise2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class PaddedConvDepthWiseCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["depthwise"] = True
-
-        # TODO find a way to retrive attr for meta op
-        for n in self.operator.get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator(
-                ).attr.begin_end_borders
-            if n.type() == "ConvDepthWise2D":
-                self.attributes["kernel_dims"] = n.get_operator(
-                ).attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator(
-                ).attr.stride_dims
-                self.attributes["dilation_dims"] = n.get_operator(
-                ).attr.dilation_dims
-
-        _setup_conv2D(self)
-
-def _setup_elemwise_op(elemwise, op):
-    """Common code (template and kernel setup) shared across all the different elementWise operator (Add, Sub,...)."""
-
-    elemwise.attributes["elemwise_op"] = op
-    elemwise.attributes["activation"] = "Linear"
-    elemwise.attributes["rescaling"] = "NoScaling"
-    elemwise.config_template = str(
-        ROOT / "templates" / "configuration" / "elemwise_config.jinja")
-    elemwise.forward_template = str(
-        ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
-    elemwise.include_list = []
-    elemwise.kernels_to_copy = [
-        str(ROOT / "kernels" / "elemwise.hpp"),
-        str(ROOT / "kernels" / "activation.hpp"),
-        str(ROOT / "kernels" / "rescaling.hpp")
-    ]
-
-@ExportLibCpp.register("Add", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class AddCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        _setup_elemwise_op(self, "Add")
-
-@ExportLibCpp.register("Sub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class SubCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        _setup_elemwise_op(self, "Sub")
-
-@ExportLibCpp.register("Mul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class MulCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        _setup_elemwise_op(self, "Mul")
-
-def _setup_pooling(pooling):
-    """Common code (template and kernel setup) shared across all the different pooling operator."""
-
-    pooling.config_template = str(
-        ROOT / "templates" / "configuration" / "pooling_config.jinja")
-    pooling.forward_template = str(
-        ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
-    pooling.include_list = []
-    pooling.kernels_to_copy = [
-        str(ROOT / "kernels" / "pooling.hpp"),
-        str(ROOT / "kernels" / "activation.hpp"),
-        str(ROOT / "kernels" / "rescaling.hpp")
-    ]
-
-@ExportLibCpp.register("MaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class MaxPoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        # No padding with MaxPooling
-        # Use PaddedMaxPooling to add padding attribute
-        self.attributes["padding"] = [0, 0]
-        self.attributes["pool_type"] = "Max"
-        self.attributes["activation"] = "Linear"
-
-        _setup_pooling(self)
-
-@ExportLibCpp.register("AvgPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class AvgPoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        # No padding with MaxPooling
-        # Use PaddedMaxPooling to add padding attribute
-        self.attributes["padding"] = [0, 0]
-        self.attributes["pool_type"] = "Average"
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-
-        _setup_pooling(self)
-
-@ExportLibCpp.register_metaop("PaddedMaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class PaddedMaxPoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        for n in self.operator.get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator(
-                ).attr.begin_end_borders
-            if n.type() == "MaxPooling2D":
-                self.attributes["kernel_dims"] = n.get_operator(
-                ).attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator(
-                ).attr.stride_dims
-        self.attributes["pool_type"] = "Max"
-        self.attributes["activation"] = "Linear"
-
-        _setup_pooling(self)
-
-@ExportLibCpp.register("GlobalAveragePooling", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class GlobalAveragePoolCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        self.attributes["stride_dims"] = [1, 1]
-        # No padding with MaxPooling
-        # Use PaddedMaxPooling to add padding attribute
-        self.attributes["padding"] = [0, 0]
-        self.attributes["kernel_dims"] = [
-            self.attributes["in_height"][0],
-            self.attributes["in_width"][0],
-        ]
-        self.attributes["pool_type"] = "Average"
-        self.attributes["activation"] = "Linear"
-
-        _setup_pooling(self)
-
-@ExportLibCpp.register("FC", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class FcCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "fullyconnected_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "fullyconnected_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "fullyconnected.hpp"),
-            str(ROOT / "kernels" / "macs.hpp"),
-            str(ROOT / "kernels" / "activation.hpp"),
-            str(ROOT / "kernels" / "rescaling.hpp")
-        ]
-
-@ExportLibCpp.register("Transpose", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class TransposeCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "transpose_ND_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "transpose_ND_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "transpose.hpp")
-        ]
-
-@ExportLibCpp.register("Softmax", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class SoftmaxCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        assert self.node.get_nb_inputs() == 1, (
-            f"export softmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
-        )
-
-        tensor = self.operator.get_input(0)
-        nbDims = len(tensor.dims())
-        axis = node.get_operator().attr.axis if node.get_operator().attr.axis >= 0 else node.get_operator().attr.axis + nbDims
-
-        assert axis < nbDims, (
-            f"export softmax: attribute axis == {node.get_operator().attr.axis} should be less than {nbDims}"
-        )
-
-        postAxisElems = 1
-        for i in range(axis + 1, nbDims):
-            postAxisElems *= tensor.dims()[i]
-
-        preAxisElems = 1
-        for i in range(axis):
-            preAxisElems *= tensor.dims()[i]
-
-        self.attributes["axis_size"] = tensor.dims()[axis]
-        self.attributes["axis_size_post"] = postAxisElems
-        self.attributes["axis_size_pre"] = preAxisElems
-
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "softmax_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "softmax_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "softmax.hpp"),
-            str(ROOT / "kernels" / "macs.hpp"),
-        ]
-
-@ExportLibCpp.register("Hardmax", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class HardmaxCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        assert self.node.get_nb_inputs() == 1, (
-            f"export hardmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
-        )
-
-        tensor = self.operator.get_input(0)
-        nbDims = len(tensor.dims())
-        axis = node.get_operator().attr.axis if node.get_operator().attr.axis >= 0 else node.get_operator().attr.axis + nbDims
-
-        assert axis >= -nbDims and axis < nbDims, (
-            f"export hardmax: attribute axis == {node.get_operator().attr.axis} should be comprised within [-{nbDims},{nbDims}]."
-        )
-
-
-        post_axis_elems = 1
-        for i in range(axis + 1, nbDims):
-            post_axis_elems *= tensor.dims()[i]
-
-        preaxis_elems = 1
-        for i in range(axis):
-            preaxis_elems *= tensor.dims()[i]
-
-        axis_elems = post_axis_elems * tensor.dims()[axis]
-        nb_elems = preaxis_elems * axis_elems
-
-        self.attributes["axis_dim_size"] = tensor.dims()[axis]
-        self.attributes["preaxis_stride"] = preaxis_elems
-        self.attributes["axis_stride"] = axis_elems
-        self.attributes["postaxis_stride"] = post_axis_elems
-        self.attributes["out_nb_elts"] = nb_elems
-
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "hardmax_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "hardmax_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "hardmax.hpp"),
-            str(ROOT / "kernels" / "macs.hpp"),
-        ]
-
-@ExportLibCpp.register("BatchNorm2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class BatchNorm2DCPP(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
-        self.attributes["epsilon"] = node.get_operator().attr.epsilon
-        self.config_template = str(
-            ROOT / "templates" / "configuration" / "batchnorm_config.jinja")
-        self.forward_template = str(
-            ROOT / "templates" / "kernel_forward" / "batchnorm_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "batchnorm.hpp"),
-            str(ROOT / "kernels" / "macs.hpp"),
-            str(ROOT / "kernels" / "activation.hpp"),
-            str(ROOT / "kernels" / "rescaling.hpp")
-        ]
-
-@ExportLibCpp.register("Concat", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class Concat(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-        assert self.node.get_nb_inputs() >= 1, (
-            f"export softmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
-        )
-
-        inputIndex = 0
-
-        tensor = self.operator.get_input(0)
-        for idx, _ in enumerate(self.node.inputs()):
-            if self.operator.get_input(idx) is not None:
-                tensor = self.operator.get_input(idx)
-                nbDims = len(tensor.dims())
-                axis = node.get_operator().attr.axis if node.get_operator().attr.axis >= 0 else node.get_operator().attr.axis + nbDims
-
-                assert axis < nbDims, (
-                    f"export softmax: attribute axis == {axis} should be less than {nbDims}"
-                )
-
-                postAxisElems = 1
-                for i in range(axis + 1, nbDims):
-                    postAxisElems *= tensor.dims()[i]
-
-                preAxisElems = 1
-                for i in range(axis):
-                    preAxisElems *= tensor.dims()[i]
-
-                if (inputIndex == 0):
-                    self.attributes["axis_size_post"] = postAxisElems
-                    self.attributes["axis_size_pre"] = preAxisElems
-
-                    self.attributes["axis_size"] = [None] * self.attributes["nb_in"]
-                else:
-                    assert self.attributes["axis_size_post"] == postAxisElems, (
-                        f"export concat: axis_size_post {self.attributes['axis_size_post']} != {postAxisElems}"
-                    )
-                    assert self.attributes["axis_size_pre"] == preAxisElems, (
-                        f"export concat: axis_size_pre {self.attributes['axis_size_pre']} != {preAxisElems}"
-                    )
-
-                self.attributes["axis_size"][idx] = tensor.dims()[axis]
-            else:
-                assert false, (
-                    f"export concat: input {idx} is None, not implemented")
-
-            inputIndex += 1
-
-        self.config_template = str(ROOT / "templates" / "configuration" / "concat_config.jinja")
-        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "concat_forward.jinja")
-        self.include_list = []
-        self.kernels_to_copy = [
-            str(ROOT / "kernels" / "concat.hpp"),
-        ]
-- 
GitLab


From 3cffb7364c320565ac3951ed3ac37028297399fb Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 15 Apr 2025 14:59:11 +0200
Subject: [PATCH 20/93] [Refactor] Remove uneeded library

---
 aidge_export_cpp/__init__.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/aidge_export_cpp/__init__.py b/aidge_export_cpp/__init__.py
index 04a6eda..fcaabf9 100644
--- a/aidge_export_cpp/__init__.py
+++ b/aidge_export_cpp/__init__.py
@@ -5,7 +5,6 @@ Aidge Export for CPP standalone projects
 from .utils import ROOT
 from .export_registry import ExportLibCpp
 from .operators import *
-from collections import defaultdict
 from .export import *
 from . import benchmark
 from .export_utils import *
-- 
GitLab


From 79a5b4764704e0beeac6aadd6357b18e6744bb93 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 10:15:16 +0200
Subject: [PATCH 21/93] [Refactor] New operators format (operators.py ->
 [operator_name].py) for the following operators : - Batchnorm - Concat - Pad
 - Softmax

---
 aidge_export_cpp/operators/CppBatchnorm.py | 35 +++++++++++
 aidge_export_cpp/operators/CppConcat.py    | 72 ++++++++++++++++++++++
 aidge_export_cpp/operators/CppPad.py       | 36 +++++++++++
 aidge_export_cpp/operators/CppSoftmax.py   | 53 ++++++++++++++++
 4 files changed, 196 insertions(+)
 create mode 100644 aidge_export_cpp/operators/CppBatchnorm.py
 create mode 100644 aidge_export_cpp/operators/CppConcat.py
 create mode 100644 aidge_export_cpp/operators/CppPad.py
 create mode 100644 aidge_export_cpp/operators/CppSoftmax.py

diff --git a/aidge_export_cpp/operators/CppBatchnorm.py b/aidge_export_cpp/operators/CppBatchnorm.py
new file mode 100644
index 0000000..e5f6536
--- /dev/null
+++ b/aidge_export_cpp/operators/CppBatchnorm.py
@@ -0,0 +1,35 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("BatchNorm2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppBatchNorm2D(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["epsilon"] = node.get_operator().attr.epsilon
+
+        # Template for layer configutation file generation
+        self.config_template = str( ROOT / "templates" / "configuration" / "batchnorm_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "batchnorm_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "batchnorm.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+            
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppConcat.py b/aidge_export_cpp/operators/CppConcat.py
new file mode 100644
index 0000000..da55940
--- /dev/null
+++ b/aidge_export_cpp/operators/CppConcat.py
@@ -0,0 +1,72 @@
+
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Concat", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppConcat(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        assert self.node.get_nb_inputs() >= 1, (
+            f"export softmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
+        )
+
+        inputIndex = 0
+
+        tensor = self.operator.get_input(0)
+        for idx, _ in enumerate(self.node.inputs()):
+            if self.operator.get_input(idx) is not None:
+                tensor = self.operator.get_input(idx)
+                nbDims = len(tensor.dims())
+                axis = node.get_operator().attr.axis if node.get_operator().attr.axis >= 0 else node.get_operator().attr.axis + nbDims
+
+                assert axis < nbDims, (
+                    f"export softmax: attribute axis == {axis} should be less than {nbDims}"
+                )
+
+                postAxisElems = 1
+                for i in range(axis + 1, nbDims):
+                    postAxisElems *= tensor.dims()[i]
+
+                preAxisElems = 1
+                for i in range(axis):
+                    preAxisElems *= tensor.dims()[i]
+
+                if (inputIndex == 0):
+                    self.attributes["axis_size_post"] = postAxisElems
+                    self.attributes["axis_size_pre"] = preAxisElems
+
+                    self.attributes["axis_size"] = [None] * self.attributes["nb_in"]
+                else:
+                    assert self.attributes["axis_size_post"] == postAxisElems, (
+                        f"export concat: axis_size_post {self.attributes['axis_size_post']} != {postAxisElems}"
+                    )
+                    assert self.attributes["axis_size_pre"] == preAxisElems, (
+                        f"export concat: axis_size_pre {self.attributes['axis_size_pre']} != {preAxisElems}"
+                    )
+
+                self.attributes["axis_size"][idx] = tensor.dims()[axis]
+            else:
+                assert False, (
+                    f"export concat: input {idx} is None, not implemented")
+
+            inputIndex += 1
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "concat_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "concat_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "concat.hpp")
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppPad.py b/aidge_export_cpp/operators/CppPad.py
new file mode 100644
index 0000000..163e33c
--- /dev/null
+++ b/aidge_export_cpp/operators/CppPad.py
@@ -0,0 +1,36 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Pad2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class CppPad(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["padding"] = node.get_operator().attr.begin_end_borders
+        self.attributes["border_type"] = node.get_operator().attr.border_type
+        self.attributes["border_value"] = node.get_operator().attr.border_value
+
+        assert self.attributes["border_type"] == aidge_core.pad_border_type.Constant, (
+            f"export Pad2d: border_type == {node.get_operator().attr.border_type} not implemented"
+        )
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "pad_config.jinja")
+
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "pad_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "pad.hpp")
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+            
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppSoftmax.py b/aidge_export_cpp/operators/CppSoftmax.py
new file mode 100644
index 0000000..09f6157
--- /dev/null
+++ b/aidge_export_cpp/operators/CppSoftmax.py
@@ -0,0 +1,53 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register("Softmax", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class CppSoftmax(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        assert self.node.get_nb_inputs() == 1, (
+            f"export softmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
+        )
+
+        tensor = self.operator.get_input(0)
+        nbDims = len(tensor.dims())
+        axis = node.get_operator().attr.axis if node.get_operator().attr.axis >= 0 else node.get_operator().attr.axis + nbDims
+
+        assert axis < nbDims, (
+            f"export softmax: attribute axis == {node.get_operator().attr.axis} should be less than {nbDims}"
+        )
+
+        postAxisElems = 1
+        for i in range(axis + 1, nbDims):
+            postAxisElems *= tensor.dims()[i]
+
+        preAxisElems = 1
+        for i in range(axis):
+            preAxisElems *= tensor.dims()[i]
+
+        # Set kernel attributes
+        self.attributes["axis_size"] = tensor.dims()[axis]
+        self.attributes["axis_size_post"] = postAxisElems
+        self.attributes["axis_size_pre"] = preAxisElems
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "softmax_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "softmax_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "softmax.hpp")
+        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+            
\ No newline at end of file
-- 
GitLab


From f8e319962888bbc2a457a97d77997b5906ae5d28 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:30:05 +0200
Subject: [PATCH 22/93] [Fix] Ignore attribute is now optional

---
 aidge_export_cpp/operators/Producer.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/operators/Producer.py b/aidge_export_cpp/operators/Producer.py
index 1ae54d3..2798243 100644
--- a/aidge_export_cpp/operators/Producer.py
+++ b/aidge_export_cpp/operators/Producer.py
@@ -47,7 +47,11 @@ class ProducerCPP(ExportNode):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
         self.values = np.array(self.operator.get_output(0))
-        self.ignore = node.attributes().ignore
+
+        if node.attributes().has_attr("ignore"):
+            self.ignore = node.attributes().ignore
+        else:
+            self.ignore = False
 
         if len(self.values.shape) == 4:  # Note: export in HWC
             self.values = np.transpose(self.values, (0, 2, 3, 1))
-- 
GitLab


From 04d4ac90f8266de3a34d7f010a0731dfebe2b753 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:30:58 +0200
Subject: [PATCH 23/93] [Chore] Change batchnorm operator file name

---
 .../operators/{CppBatchnorm.py => CppBatchNorm.py}          | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
 rename aidge_export_cpp/operators/{CppBatchnorm.py => CppBatchNorm.py} (92%)

diff --git a/aidge_export_cpp/operators/CppBatchnorm.py b/aidge_export_cpp/operators/CppBatchNorm.py
similarity index 92%
rename from aidge_export_cpp/operators/CppBatchnorm.py
rename to aidge_export_cpp/operators/CppBatchNorm.py
index e5f6536..285a64c 100644
--- a/aidge_export_cpp/operators/CppBatchnorm.py
+++ b/aidge_export_cpp/operators/CppBatchNorm.py
@@ -4,7 +4,7 @@ from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
 @ExportLibCpp.register("BatchNorm2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class CppBatchNorm2D(ExportNodeCpp):
+class CppBatchNorm(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
@@ -12,6 +12,7 @@ class CppBatchNorm2D(ExportNodeCpp):
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["epsilon"] = node.get_operator().attr.epsilon
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         # Template for layer configutation file generation
         self.config_template = str( ROOT / "templates" / "configuration" / "batchnorm_config.jinja")
@@ -31,5 +32,4 @@ class CppBatchNorm2D(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
-            
\ No newline at end of file
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
-- 
GitLab


From bcfc4ab639518e14d40509e5482d5b4839f4e788 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:33:48 +0200
Subject: [PATCH 24/93] [Fix] the "aidge_cmp" attribute is now set in operators
 export nodes

---
 aidge_export_cpp/operators/CppActivation.py | 2 +-
 aidge_export_cpp/operators/CppConcat.py     | 2 ++
 aidge_export_cpp/operators/CppConv.py       | 2 +-
 aidge_export_cpp/operators/CppElemWise.py   | 2 +-
 aidge_export_cpp/operators/CppFc.py         | 2 +-
 aidge_export_cpp/operators/CppPad.py        | 4 ++--
 aidge_export_cpp/operators/CppPool.py       | 2 +-
 aidge_export_cpp/operators/CppSoftmax.py    | 5 +++--
 8 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
index 01ac0a3..f3ffc8e 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -10,7 +10,7 @@ class CppActivation(ExportNodeCpp):
 
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
-        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
diff --git a/aidge_export_cpp/operators/CppConcat.py b/aidge_export_cpp/operators/CppConcat.py
index da55940..b0c91e2 100644
--- a/aidge_export_cpp/operators/CppConcat.py
+++ b/aidge_export_cpp/operators/CppConcat.py
@@ -9,6 +9,8 @@ class CppConcat(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
+
         assert self.node.get_nb_inputs() >= 1, (
             f"export softmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
         )
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
index 466f9a1..5c39fe6 100644
--- a/aidge_export_cpp/operators/CppConv.py
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -11,7 +11,7 @@ class CppConv(ExportNodeCpp):
         # Initialize kernel attributes
         self.attributes["padding"] = [0, 0, 0, 0]
         self.attributes["activation"] = "Linear"
-        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index 416ef61..c4d9070 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -12,7 +12,7 @@ class CppElemWise(ExportNodeCpp):
         self.attributes["elemwise_op"] = node.type()    # [Add, Mul, Sub]
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
-        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index 4bb8d4e..f10032c 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -11,7 +11,7 @@ class CppFc(ExportNodeCpp):
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
-        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
diff --git a/aidge_export_cpp/operators/CppPad.py b/aidge_export_cpp/operators/CppPad.py
index 163e33c..f84f2cf 100644
--- a/aidge_export_cpp/operators/CppPad.py
+++ b/aidge_export_cpp/operators/CppPad.py
@@ -12,6 +12,7 @@ class CppPad(ExportNodeCpp):
         self.attributes["padding"] = node.get_operator().attr.begin_end_borders
         self.attributes["border_type"] = node.get_operator().attr.border_type
         self.attributes["border_value"] = node.get_operator().attr.border_value
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         assert self.attributes["border_type"] == aidge_core.pad_border_type.Constant, (
             f"export Pad2d: border_type == {node.get_operator().attr.border_type} not implemented"
@@ -32,5 +33,4 @@ class CppPad(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
-            
\ No newline at end of file
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
index c2fb048..755bcf2 100644
--- a/aidge_export_cpp/operators/CppPool.py
+++ b/aidge_export_cpp/operators/CppPool.py
@@ -13,7 +13,7 @@ class CppPool(ExportNodeCpp):
         self.attributes["padding"] = [0, 0, 0, 0]
         self.attributes["pool_type"] = "Max"
         self.attributes["activation"] = "Linear"
-        self.attributes["aidge_cmp"] = node.attributes().aidge_cmp
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
diff --git a/aidge_export_cpp/operators/CppSoftmax.py b/aidge_export_cpp/operators/CppSoftmax.py
index 09f6157..90bcacf 100644
--- a/aidge_export_cpp/operators/CppSoftmax.py
+++ b/aidge_export_cpp/operators/CppSoftmax.py
@@ -8,6 +8,8 @@ class CppSoftmax(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
+
         assert self.node.get_nb_inputs() == 1, (
             f"export softmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
         )
@@ -49,5 +51,4 @@ class CppSoftmax(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
-            
\ No newline at end of file
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
-- 
GitLab


From e54e5e746c95db28796257518c472d4f0b9db551 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:36:21 +0200
Subject: [PATCH 25/93] [Fix](Add) MetaOp type was used instead of actual node
 type

---
 aidge_export_cpp/operators/CppElemWise.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index c4d9070..d9803ca 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -9,7 +9,6 @@ class CppElemWise(ExportNodeCpp):
         super().__init__(node, mem_info)
 
         # Initialize kernel attributes
-        self.attributes["elemwise_op"] = node.type()    # [Add, Mul, Sub]
         self.attributes["activation"] = "Linear"
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
@@ -23,6 +22,8 @@ class CppElemWise(ExportNodeCpp):
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
+            elif n.type() in ["Add", "Mul", "Sub"]:
+                self.attributes["elemwise_op"] = n.type()
 
         ## Get the scaling values
         for prod in node.get_parents():
-- 
GitLab


From af448412014a24ac54b34a383be8560c1e4d0593 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:37:16 +0200
Subject: [PATCH 26/93] [Fix] "aidge_cmp" is now considered as a flag (so that
 it can be optional)

---
 examples/export_LeNet/lenet.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index af529f9..8cacceb 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -469,8 +469,9 @@ This option has to be passed to each node in order to be used within the Export
 (JConv, JPad, ...) that you can find in the "operators" folder. 
 """
 
-for node in model.get_nodes():
-    node.attributes().aidge_cmp = AIDGE_CMP
+if AIDGE_CMP:
+    for node in model.get_nodes():
+        node.attributes().aidge_cmp = True
 
 # --------------------------------------------------------------
 # EXPORT THE MODEL
-- 
GitLab


From 7fbafcd2a2b404c670f39be56bfccced6737d346 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:37:56 +0200
Subject: [PATCH 27/93] [Fix] Graph needs to be fused and metaOps names set
 before exporting

---
 aidge_export_cpp/unit_tests/test_export.py | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/aidge_export_cpp/unit_tests/test_export.py b/aidge_export_cpp/unit_tests/test_export.py
index 9215ff8..82a9a35 100644
--- a/aidge_export_cpp/unit_tests/test_export.py
+++ b/aidge_export_cpp/unit_tests/test_export.py
@@ -11,6 +11,8 @@ import re
 import shutil
 from aidge_core.utils import run_command
 
+from aidge_export_cpp import cpp_fuse_to_metaops, set_nodes_names
+
 def initFiller(model):
     # Initialize parameters (weights and biases)
     for node in model.get_nodes():
@@ -98,6 +100,8 @@ class test_operator_export(unittest.TestCase):
                 else:
                     aidge_core.constant_filler(value, default_value)
 
+        # Fuse operators to match implemented cpp kernels
+        cpp_fuse_to_metaops(graph_view)
 
         scheduler = aidge_core.SequentialScheduler(graph_view)
 
@@ -108,6 +112,9 @@ class test_operator_export(unittest.TestCase):
 
         scheduler.forward(data=in_tensor)
 
+        # Name the metaops
+        set_nodes_names(scheduler)
+
         # Note the convention ``<op_name>_test`` is useful for gitignore to avoid pushing generated export by accident.
         export_folder = op_name + "_test"
 
-- 
GitLab


From 43fdbd9f841c7d3067cf2f95f135b889237accc5 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:38:23 +0200
Subject: [PATCH 28/93] [Fix] Wrong rescaling configuration

---
 .../templates/configuration/batchnorm_config.jinja             | 2 +-
 aidge_export_cpp/templates/configuration/elemwise_config.jinja | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/aidge_export_cpp/templates/configuration/batchnorm_config.jinja b/aidge_export_cpp/templates/configuration/batchnorm_config.jinja
index ae7ef57..751c55f 100644
--- a/aidge_export_cpp/templates/configuration/batchnorm_config.jinja
+++ b/aidge_export_cpp/templates/configuration/batchnorm_config.jinja
@@ -8,6 +8,6 @@
 {% include "./_meminfo.jinja" %}
 #define {{ name|upper }}_ACTIVATION {{ activation }}
 #define {{ name|upper }}_EPSILON {{ epsilon }}
-static const {{ rescaling }} {{ name|upper }}_RESCALING = {};
+{% include "./_rescaling.jinja" %}
 
 #endif /* {{ name|upper }}_LAYER_H */
diff --git a/aidge_export_cpp/templates/configuration/elemwise_config.jinja b/aidge_export_cpp/templates/configuration/elemwise_config.jinja
index 91a0be4..41c9c3f 100644
--- a/aidge_export_cpp/templates/configuration/elemwise_config.jinja
+++ b/aidge_export_cpp/templates/configuration/elemwise_config.jinja
@@ -9,5 +9,6 @@
 #define {{ name|upper }}_NB_ELTS {{ in_dims[0]|join('*') }}
 #define {{ name|upper }}_ACTIVATION {{ activation }}
 #define {{ name|upper }}_ELEM_OP {{ elemwise_op }}
-static const {{ rescaling }} {{ name|upper }}_RESCALING = {};
+{% include "./_rescaling.jinja" %}
+
 #endif /* {{ name|upper }}_LAYER_H */
-- 
GitLab


From 8599ddcf85d9a0cc4fe1d76a8ae34287a581647e Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 13:50:32 +0200
Subject: [PATCH 29/93] [Fix] Missing IN_BATCH macro definition for Pad kernel

---
 aidge_export_cpp/templates/configuration/_def_io.jinja | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aidge_export_cpp/templates/configuration/_def_io.jinja b/aidge_export_cpp/templates/configuration/_def_io.jinja
index c420f7a..314ae39 100644
--- a/aidge_export_cpp/templates/configuration/_def_io.jinja
+++ b/aidge_export_cpp/templates/configuration/_def_io.jinja
@@ -7,6 +7,7 @@
 {% endfor %} #}
 
 // INPUT CONF
+#define {{ in_name[0]|upper }}_IN_BATCH {{ in_batch[0] }}
 #define {{ in_name[0]|upper }}_NB_CHANNELS {{ in_chan[0] }}
 #define {{ in_name[0]|upper }}_IN_HEIGHT {{ in_height[0] }}
 #define {{ in_name[0]|upper }}_IN_WIDTH {{ in_width[0] }}
-- 
GitLab


From 7a4fd8a8980248287b7f3f2a7f160a1578ee7dc9 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 15:06:39 +0200
Subject: [PATCH 30/93] [Chore] Move macros from utils.py to __init__.py

---
 aidge_export_cpp/__init__.py | 9 +++++++--
 aidge_export_cpp/utils.py    | 6 ------
 2 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/aidge_export_cpp/__init__.py b/aidge_export_cpp/__init__.py
index fcaabf9..7a5bb30 100644
--- a/aidge_export_cpp/__init__.py
+++ b/aidge_export_cpp/__init__.py
@@ -2,10 +2,15 @@ r"""
 Aidge Export for CPP standalone projects
 
 """
-from .utils import ROOT
+from pathlib import Path
+
+# Constants
+FILE = Path(__file__).resolve()
+ROOT = FILE.parents[0]
+
 from .export_registry import ExportLibCpp
+from .export_utils import *
 from .operators import *
 from .export import *
 from . import benchmark
-from .export_utils import *
 
diff --git a/aidge_export_cpp/utils.py b/aidge_export_cpp/utils.py
index 915c2c6..f25e969 100644
--- a/aidge_export_cpp/utils.py
+++ b/aidge_export_cpp/utils.py
@@ -1,11 +1,5 @@
-from pathlib import Path
 from importlib.metadata import version
 
-# Constants
-FILE = Path(__file__).resolve()
-ROOT = FILE.parents[0]
-
-
 def show_version():
     version_aidge_export_cpp = version("aidge_export_cpp")
     print(f"Aidge Export CPP: {version_aidge_export_cpp}")
-- 
GitLab


From 65d5f83a6b13d86906380cc41e94cbfc6be28ec9 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 15:14:06 +0200
Subject: [PATCH 31/93] [Fix] Missing rescaling argument for batchnorm
 generation

---
 .../templates/kernel_forward/batchnorm_forward.jinja         | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja b/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
index 39e7774..69fa69e 100644
--- a/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/batchnorm_forward.jinja
@@ -5,7 +5,10 @@ batchnorm_forward<{{ out_name[0]|upper }}_OUT_BATCH,
                   {{ out_name[0]|upper }}_OUT_HEIGHT,
                   {{ out_name[0]|upper }}_OUT_WIDTH,
                   {{name|upper}}_ACTIVATION>
-                  ({{in_name[0]}}, {{out_name[0]}}, {{in_name[1]}}, {{in_name[2]}}, {{in_name[3]}}, {{in_name[4]}}, {{name|upper}}_EPSILON);
+                  ({{in_name[0]}}, {{out_name[0]}}, 
+                   {{in_name[1]}}, {{in_name[2]}}, 
+                   {{in_name[3]}}, {{in_name[4]}}, 
+                   {{name|upper}}_EPSILON, {{name|upper}}_RESCALING);
 {% include "./_save_outputs.jinja" %}
 {% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
-- 
GitLab


From 646734cdcf45665ed254175da6bae6b3fddd5b9a Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 15:14:36 +0200
Subject: [PATCH 32/93] [Chore] Better indentation

---
 .../templates/kernel_forward/concat_forward.jinja    | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/concat_forward.jinja b/aidge_export_cpp/templates/kernel_forward/concat_forward.jinja
index 7a77e90..88cbc9a 100644
--- a/aidge_export_cpp/templates/kernel_forward/concat_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/concat_forward.jinja
@@ -14,9 +14,9 @@ unsigned int {{ name|upper }}_SIZES[] = {
 
 concat_forward<{{ name|upper }}_AXIS_SIZE_POST,
                {{ name|upper }}_AXIS_SIZE_PRE,
-               {{ nb_in }},
-               float> (
-    {{ name|upper }}_INPUTS,
-    {{ name|upper }}_SIZES,
-    {{ out_name[0] }});
-    {% endfilter %}
+               {{ nb_in }}, float> 
+              ({{ name|upper }}_INPUTS,
+               {{ name|upper }}_SIZES,
+               {{ out_name[0] }});
+
+{%- endfilter %}
-- 
GitLab


From 69adcf1ad6db1bc40acbf6effaf8a82b9ea91a0e Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 15:29:43 +0200
Subject: [PATCH 33/93] [Fix] New way to handle scaling data inside export
 nodes

---
 aidge_export_cpp/export_utils.py          | 28 +++++++++++++++++++----
 aidge_export_cpp/operators/CppElemWise.py | 22 ++++++++++--------
 2 files changed, 35 insertions(+), 15 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 7b7477d..7e2fe19 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -7,16 +7,16 @@ def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
     """ 
     Fuse nodes into metaops adapted for the CPP Export
 
-    :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
+    :param graph_view: An instance of :py:class:`aidge_core.GraphView`, providing access to nodes and
                        ordered input/output data within the computational graph.
     """
 
     cpp_recipes = OrderedDict({
-        "Rescaling":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
-        "CppFc":          "FC->Rescaling?->ReLU?",
-        "CppConv":        "Conv2D#->Rescaling?->ReLU?; Conv2D#<-Pad2D?",
+        "Quantizer":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
+        "CppFc":          "FC->Quantizer?->ReLU?",
+        "CppConv":        "Conv2D#->Quantizer?->ReLU?; Conv2D#<-Pad2D?",
         "CppPool":        "(MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)->ReLU?; (MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)<-Pad2D?",
-        "CppElemWise":    "(Add|Mul|Sub)->Rescaling?->ReLU?",
+        "CppElemWise":    "(Add|Mul|Sub)->Quantizer?->ReLU?",
         "CppActivation":  "ReLU"
     })
 
@@ -174,3 +174,21 @@ def exclude_unwanted_producers(model):
                 if node_type in children_nodes:
                     node.attributes().ignore = True
                     break
+
+def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, QNode: aidge_core.Node):
+    """
+    Set shift and coef attributes of the given export node.
+    [TODO] Should be moved into aidge_core.ExportNode
+
+    :param export_node: An instance of :py:class:`aidge_core.export_utils.ExportNode` to set the scaling
+                        attributes needed for a quantized export. 
+    :type export_node: aidge_core.export_utils.ExportNode
+    :param QNode: Quantizer node holding the shift and coef values. 
+    :type QNode: aidge_core.Node
+    """
+
+    for node in QNode.get_operator().get_micro_graph().get_nodes():
+        if node.type() == "BitShift":
+            export_node.attributes["shift_value"] = node.get_operator().get_input(1)[0]
+        elif node.type() == "Mul":
+            export_node.attributes["coef_value"] = node.get_operator().get_input(1)[0]
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index d9803ca..f2e7550 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -2,6 +2,7 @@ import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
+from aidge_export_cpp import set_scaling_attributes
 
 @ExportLibCpp.register_metaop("CppElemWise", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppElemWise(ExportNodeCpp):
@@ -10,7 +11,6 @@ class CppElemWise(ExportNodeCpp):
 
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
         self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
@@ -24,17 +24,19 @@ class CppElemWise(ExportNodeCpp):
                 self.attributes["activation"] = "Rectifier"
             elif n.type() in ["Add", "Mul", "Sub"]:
                 self.attributes["elemwise_op"] = n.type()
+            elif n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
 
         ## Get the scaling values
-        for prod in node.get_parents():
-            if prod is not None:
-                if prod.type() == "Producer":
-                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
-                    # elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
-                    #     self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
-                    else:
-                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+        # for node in node.get_parents():
+        #     if node is not None:
+        #         if node.type() == "Producer":
+        #             if node.attributes().has_attr("quantization.ptq.ShiftAmount"):
+        #                 self.attributes["shift_value"] = node.attributes().quantization.ptq.ShiftAmount
+        #             # elif node.attributes().has_attr("quantization.ptq.CompensationCoeff"):
+        #             #     self.attributes["coef_value"] = node.attributes().quantization.ptq.CompensationCoeff
+        #             else:
+        #                 self.attributes["coef_value"] = node.get_operator().get_output(0)[0]
 
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
-- 
GitLab


From 3895052ebb6dd88c0bba1fe93c891136f5a3564e Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 20 Mar 2025 16:41:14 +0100
Subject: [PATCH 34/93] [Feat] Add int8 support

- Add rescaling functions : SingleShift and FixedPoint (Mul->Shift)
- Add a rescaling node to the metaops recipes corresponding to the Quantizer node added by the PTQ
- PoC(export_utils.py) new recipe system
---
 aidge_export_cpp/operators/CppElemWise.py | 17 +++++++++++++++++
 aidge_export_cpp/operators/CppFc.py       |  4 ++++
 aidge_export_cpp/operators/Producer.py    |  1 +
 3 files changed, 22 insertions(+)

diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index f2e7550..041bce9 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -38,6 +38,23 @@ class CppElemWise(ExportNodeCpp):
         #             else:
         #                 self.attributes["coef_value"] = node.get_operator().get_output(0)[0]
 
+        ## Set the scaling type
+        if self.attributes["coef_value"] != 1:
+            self.attributes["rescaling"] = "FixedPointScaling"
+        elif self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
+        ## Get the scaling values
+        for prod in node.get_parents():
+            if prod is not None:
+                if prod.type() == "Producer":
+                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+                    # elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
+                    #     self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
+                    else:
+                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
             self.attributes["rescaling"] = "FixedPointScaling"
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index f10032c..793d74f 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -17,6 +17,10 @@ class CppFc(ExportNodeCpp):
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["shift_value"] = 0
 
+        ## Scaling
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
diff --git a/aidge_export_cpp/operators/Producer.py b/aidge_export_cpp/operators/Producer.py
index 2798243..1d7a211 100644
--- a/aidge_export_cpp/operators/Producer.py
+++ b/aidge_export_cpp/operators/Producer.py
@@ -47,6 +47,7 @@ class ProducerCPP(ExportNode):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
         self.values = np.array(self.operator.get_output(0))
+        self.ignore = node.attributes().ignore
 
         if node.attributes().has_attr("ignore"):
             self.ignore = node.attributes().ignore
-- 
GitLab


From a2fc48d05f16f482eae7c5444d22f9ccb7ac419a Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:40:07 +0000
Subject: [PATCH 35/93] [Fix](Pool) Missing kernel dimension

---
 aidge_export_cpp/operators/CppPool.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
index 755bcf2..fa9aaec 100644
--- a/aidge_export_cpp/operators/CppPool.py
+++ b/aidge_export_cpp/operators/CppPool.py
@@ -23,7 +23,7 @@ class CppPool(ExportNodeCpp):
                 self.attributes["padding"] = n.get_operator().attr.begin_end_borders
             elif n.type() == "GlobalAveragePooling":
                 self.attributes["pool_type"] = "Average"
-                self.attributes["kernel_dims"] = self.attributes["in_width"][0]
+                self.attributes["kernel_dims"] = [self.attributes["in_width"][0], self.attributes["in_height"][0]]
             elif n.type() == "MaxPooling2D":
                 self.attributes["pool_type"] = "Max"
                 self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
-- 
GitLab


From 2337577568cde64747976836c3cd3e65b44a6838 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:46:11 +0000
Subject: [PATCH 36/93] [Fix] Rounding integration for int data type

---
 aidge_export_cpp/kernels/pooling.hpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/aidge_export_cpp/kernels/pooling.hpp b/aidge_export_cpp/kernels/pooling.hpp
index 30fa766..96c0223 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -115,7 +115,12 @@ void pooling_forward(
                         }
                     }
 
+                    if constexpr (std::is_integral<Output_T>::value) {
+                        outputs[oOffset + output] = (Output_T) std::round((float)sum / (POOL_HEIGHT * POOL_WIDTH));
+                    } else {
                     outputs[oOffset + output] = (Output_T) (sum / (POOL_HEIGHT * POOL_WIDTH));
+                    }
+
                 }
                 else {
                     throw std::runtime_error("The export only supports Max and Average pooling.");
-- 
GitLab


From d74b58d75c86cb00c91c0a4a5ecc15e18436c8d7 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:49:11 +0000
Subject: [PATCH 37/93] [Fix] Naming change and normalize function integration

---
 aidge_export_cpp/export_utils.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 7e2fe19..a7f32bb 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -192,3 +192,11 @@ def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, QNod
             export_node.attributes["shift_value"] = node.get_operator().get_input(1)[0]
         elif node.type() == "Mul":
             export_node.attributes["coef_value"] = node.get_operator().get_input(1)[0]
+            
+def normalize(array):
+    """
+    Normalize an input image between -1 and 1
+    """
+    array = (array - array.min()) / (array.max() - array.min())
+    return 2 * array - 1
+
-- 
GitLab


From 73fe49746b24fc13a88ff887f46b1f33ed773131 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:50:24 +0000
Subject: [PATCH 38/93] [Feat] inputs_tensors use for main cpp generation

---
 aidge_export_cpp/export.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py
index 58777f8..43de6e5 100644
--- a/aidge_export_cpp/export.py
+++ b/aidge_export_cpp/export.py
@@ -16,7 +16,7 @@ def export(export_folder_name: str,
            graphview: aidge_core.GraphView,
            scheduler: Union[List[aidge_core.Node],
                             aidge_core.Scheduler],
-           input_tensor: aidge_core.Tensor = None,  # Coming Soon
+           inputs_tensor: aidge_core.Tensor = None,
            labels: aidge_core.Tensor = None,
            dev_mode: bool = False,
            aidge_cmp: bool = False):
@@ -67,7 +67,7 @@ def export(export_folder_name: str,
                      dev_mode=dev_mode)
     
     # Generate main file
-    generate_main_cpp(export_folder_name, graphview, labels=labels)
+    generate_main_cpp(export_folder_name, graphview, labels=labels, inputs_tensor=inputs_tensor)
 
     # Generate log files (aidge_cmp option)
     """
-- 
GitLab


From 9bff2c9d16d0376ac4dbb3da631059a837d13213 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:52:45 +0000
Subject: [PATCH 39/93] [Fix] add cmath for rounding feature

---
 aidge_export_cpp/kernels/pooling.hpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aidge_export_cpp/kernels/pooling.hpp b/aidge_export_cpp/kernels/pooling.hpp
index 96c0223..0908c00 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -4,6 +4,7 @@
 #include "network/typedefs.hpp"
 #include "network/utils.hpp"
 #include <limits>
+#include <cmath>
 #include <stdexcept>
 
 
-- 
GitLab


From 8b2b999fef245f14c2f4e173941b1de44bba0674 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:54:12 +0000
Subject: [PATCH 40/93] [Feat] Add rescaling function

---
 aidge_export_cpp/kernels/rescaling.hpp | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/aidge_export_cpp/kernels/rescaling.hpp b/aidge_export_cpp/kernels/rescaling.hpp
index 9921047..117a0cd 100644
--- a/aidge_export_cpp/kernels/rescaling.hpp
+++ b/aidge_export_cpp/kernels/rescaling.hpp
@@ -1,6 +1,29 @@
 #ifndef __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
 #define __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
 
+#include "kernels/activation.hpp"
+
+
+template<int NB_DATA,
+         ActivationFunction_T ACTIVATION,
+         typename Input_T,
+         typename Output_T,
+         typename Rescaling_T>
+__attribute__((always_inline)) inline 
+void rescaling_forward (
+    const Input_T* __restrict inputs,
+    Output_T* __restrict outputs,
+    const Rescaling_T& __restrict rescaling)
+{
+#ifdef _OPENMP
+    #pragma omp parallel
+#endif
+    for (int i = 0; i < NB_DATA; ++i) {
+        outputs[i] = activation_forward_value<Output_T>(inputs[i] , 0, ACTIVATION, rescaling);
+    }
+}
+
+
 // ---------------------------------------------------
 // ----------------- Saturate Utils ------------------
 // ---------------------------------------------------
-- 
GitLab


From b588a18f8876e9b38f76b45ed9c20b7880c66276 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:56:57 +0000
Subject: [PATCH 41/93] [Feat] Add Rescaling registrar file with attribute
 resolution

---
 aidge_export_cpp/operators/CppRescaling.py | 57 ++++++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 aidge_export_cpp/operators/CppRescaling.py

diff --git a/aidge_export_cpp/operators/CppRescaling.py b/aidge_export_cpp/operators/CppRescaling.py
new file mode 100644
index 0000000..815e5c2
--- /dev/null
+++ b/aidge_export_cpp/operators/CppRescaling.py
@@ -0,0 +1,57 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+@ExportLibCpp.register_metaop("CppRescaling", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class CppRescaling(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["activation"] = "Linear"
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+        self.attributes["coef_value"] = 1
+
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+
+
+        ## Get the scaling values
+        for prod in node.get_parents():
+            if prod is not None:
+                if prod.type() == "Producer":
+                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
+                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+#                    elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
+#                        self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
+                    else:
+                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+
+        ## Set the scaling type
+        if self.attributes["coef_value"] != 1:
+            self.attributes["rescaling"] = "FixedPointScaling"
+        elif self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "rescaling_config.jinja")
+
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "rescaling_forward.jinja")
+
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
+        
+#        # Include aidge outputs within the fwd file
+#        if self.attributes["aidge_cmp"]:
+#            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+#            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
-- 
GitLab


From e30e6b949f3401ac294d24c09c11eb5ae95a0375 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:01:45 +0000
Subject: [PATCH 42/93] [Fix] Replacing a hard cast with a cast based on the
 output typ

---
 aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja b/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja
index 4ca8af8..bf8b4d7 100644
--- a/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/_aidge_cmp.jinja
@@ -3,6 +3,6 @@
     aidge_cmp<{{ out_name[0] | upper }}_NB_OUTPUTS, 
               {{ out_name[0] | upper }}_OUT_HEIGHT, 
               {{ out_name[0] | upper }}_OUT_WIDTH>
-             ("{{ name }}", (int8_t*) {{ out_name[0] }}_aidge, {{ out_name[0] }});
+             ("{{ name }}", ({{out_cdtype[0]}}*) {{ out_name[0] }}_aidge, {{ out_name[0] }});
 #endif
 {%- endif %}
\ No newline at end of file
-- 
GitLab


From 7235765afb93dde5851cffa8676e3dfae8d90fcc Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:05:09 +0000
Subject: [PATCH 43/93] [Feat] Adaptation of the Aidge_cmp function for an
 integer datatype

---
 aidge_export_cpp/static/utils.hpp | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/aidge_export_cpp/static/utils.hpp b/aidge_export_cpp/static/utils.hpp
index 8316e5e..e3776a3 100644
--- a/aidge_export_cpp/static/utils.hpp
+++ b/aidge_export_cpp/static/utils.hpp
@@ -144,7 +144,8 @@ inline void saveOutputs(
 #if AIDGE_CMP
 
 template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, typename AidgeOutput_T, typename DevOutput_T>
-void aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
+typename std::enable_if<std::is_floating_point<DevOutput_T>::value>::type 
+aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
 
     printf("[AIDGE COMPARE] - %s\n", layer_name.c_str());
 
@@ -154,7 +155,30 @@ void aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T*
                 const int aidge_ofst = out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
                 const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
                 if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
-                    printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
+                    printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
+                            out, h, w, aidge_output[aidge_ofst], dev_output[dev_ofst]);                
+                    printf("Abort program.\n");
+                    exit(1);
+                }
+            }
+        }
+    }
+    printf("[SUCCESS]\n\n");
+}
+
+template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, typename AidgeOutput_T, typename DevOutput_T>
+typename std::enable_if<std::is_integral<DevOutput_T>::value>::type 
+aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
+
+    printf("[AIDGE COMPARE] - %s\n", layer_name.c_str());
+
+    for (auto out = 0; out < NB_OUTPUTS; ++out) {
+        for (auto h = 0; h < OUT_HEIGHT; ++h) {
+            for (auto w = 0; w < OUT_WIDTH; ++w) {
+                const int aidge_ofst = out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
+                const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
+                if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
+                    printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
                            out, h, w, aidge_output[aidge_ofst], dev_output[dev_ofst]);
                     printf("Abort program.\n");
                     exit(1);
-- 
GitLab


From 6694efc12ef7bd092c8a617fe6c30797f3fbe6df Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:06:27 +0000
Subject: [PATCH 44/93] [Refactor] Change formatting

---
 .../templates/kernel_forward/elemwise_forward.jinja          | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja b/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
index 0a3259b..1a99921 100644
--- a/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/elemwise_forward.jinja
@@ -3,7 +3,10 @@
 elemwise_forward<{{name|upper}}_NB_ELTS,
                  {{name|upper}}_ELEM_OP,
                  {{name|upper}}_ACTIVATION>
-                 ({{out_name[0]}}, {{name|upper}}_RESCALING, {{in_name[0]}}, {{in_name[1]}});
+                 ({{out_name[0]}},
+                 {{name|upper}}_RESCALING,
+                 {{in_name[0]}},
+                 {{in_name[1]}});
 {% include "./_save_outputs.jinja" %}
 {% include "./_aidge_cmp.jinja" %}
 {% endfilter %}
-- 
GitLab


From c101f945a7fd95ecc2a0b21cec87bce62ff94757 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:07:26 +0000
Subject: [PATCH 45/93] [Feat] Add jinja file for forward build

---
 .../configuration/rescaling_config.jinja          | 15 +++++++++++++++
 .../kernel_forward/rescaling_forward.jinja        |  9 +++++++++
 2 files changed, 24 insertions(+)
 create mode 100644 aidge_export_cpp/templates/configuration/rescaling_config.jinja
 create mode 100644 aidge_export_cpp/templates/kernel_forward/rescaling_forward.jinja

diff --git a/aidge_export_cpp/templates/configuration/rescaling_config.jinja b/aidge_export_cpp/templates/configuration/rescaling_config.jinja
new file mode 100644
index 0000000..ee9c69e
--- /dev/null
+++ b/aidge_export_cpp/templates/configuration/rescaling_config.jinja
@@ -0,0 +1,15 @@
+{#- For name header -#}
+#ifndef {{ name|upper }}_LAYER_H
+#define {{ name|upper }}_LAYER_H
+
+{# For layer configuration -#}
+{% include "./_def_io.jinja" %}
+{% include "./_meminfo.jinja" %}
+
+#define {{ name|upper }}_NB_DATA {{ in_chan[0] * in_height[0] * in_width[0] }}
+
+// Activation
+#define {{ name|upper }}_ACTIVATION     {{ activation }}
+{% include "./_rescaling.jinja" %}
+
+#endif /* {{ name|upper }}_LAYER_H */
diff --git a/aidge_export_cpp/templates/kernel_forward/rescaling_forward.jinja b/aidge_export_cpp/templates/kernel_forward/rescaling_forward.jinja
new file mode 100644
index 0000000..ce4ffb8
--- /dev/null
+++ b/aidge_export_cpp/templates/kernel_forward/rescaling_forward.jinja
@@ -0,0 +1,9 @@
+{% filter indent(width=4, first=False) %}
+{% include "./_mem_offset.jinja" %}
+rescaling_forward<{{name|upper}}_NB_DATA,
+                   {{name|upper}}_ACTIVATION>
+                   ({{in_name[0]}},
+                   {{out_name[0]}},
+                   {{name|upper}}_RESCALING);
+{% include "./_save_outputs.jinja" %}
+{% endfilter %}
-- 
GitLab


From 00facd0a83cf5004ba6e8f29dae25a5946b42112 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:13:27 +0000
Subject: [PATCH 46/93] [Fix] Add #define to enable or disable the OpenMP
 option for compilation

---
 aidge_export_cpp/kernels/pooling.hpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/kernels/pooling.hpp b/aidge_export_cpp/kernels/pooling.hpp
index 0908c00..0829cc4 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -119,7 +119,7 @@ void pooling_forward(
                     if constexpr (std::is_integral<Output_T>::value) {
                         outputs[oOffset + output] = (Output_T) std::round((float)sum / (POOL_HEIGHT * POOL_WIDTH));
                     } else {
-                    outputs[oOffset + output] = (Output_T) (sum / (POOL_HEIGHT * POOL_WIDTH));
+                        outputs[oOffset + output] = (Output_T) (sum / (POOL_HEIGHT * POOL_WIDTH));
                     }
 
                 }
-- 
GitLab


From 057055053d3ba74bf987e5207cfaf7fa10bb8657 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:31:17 +0000
Subject: [PATCH 47/93] [Feature] Add Python script for ResNet18 export (cpp)

---
 examples/export_LeNet/lenet.py       |   4 +-
 examples/export_ResNet18/resnet18.py | 587 +++++++++++++++++++++++++++
 2 files changed, 589 insertions(+), 2 deletions(-)
 create mode 100644 examples/export_ResNet18/resnet18.py

diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index 8cacceb..1f6eebc 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -346,11 +346,11 @@ if USE_CUDA:
 # --------------------------------------------------------------
 
 """
-Here is made the link between the Aidge model and the Jacinto
+Here is made the link between the Aidge model and the CPP
 kernels implementation. In aidge, all the nodes calculations
 are performed separately (Pad -> Conv -> Quantizer -> ReLU -> ...).
 
-However within the Jacinto export, some core operators are merged
+However within the CPP export, some core operators are merged
 in meta operators. For instance, the padding, scaling and ReLU are
 performed within the Conv kernel. 
 
diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
new file mode 100644
index 0000000..6ca0223
--- /dev/null
+++ b/examples/export_ResNet18/resnet18.py
@@ -0,0 +1,587 @@
+"""
+resnet.py
+
+This file allows the generation of a resnet18 CPP export.
+
+In order for this file to work properly, you should first download the imagenet dataset
+(search for "ILSVRC2012").
+"""
+
+import random
+import numpy as np
+import os
+import shutil
+from PIL import Image
+
+# Aidge Modules
+import aidge_core
+import aidge_onnx
+import aidge_backend_cpu
+import aidge_quantization
+import aidge_export_cpp
+
+from aidge_export_cpp.export_utils import (
+    cpp_fuse_to_metaops, 
+    exclude_unwanted_producers, 
+    set_nodes_names, 
+    set_nodes_datatypes,
+    normalize)
+
+from aidge_core.export_utils import remove_optional_inputs
+
+# Torch (Dataset)
+import torch
+import torch.nn.functional as F
+from torch import nn
+from torchvision import transforms, datasets
+
+# Arguments
+import argparse
+
+supported_types = ["float32", "int8"]
+
+parser = argparse.ArgumentParser(description="Export the ResNet18 model with the aidge_export_cpp module.")
+parser.add_argument("--dev", action="store_true", help="Export in dev mode")
+parser.add_argument("--no_cuda", action="store_true", help="Disable USE_CUDA usage to perform inferences and training.")
+parser.add_argument("--dtype", type=str, choices=supported_types, default="float32", help="Specify the targeted datatype : [int8, float32]")
+parser.add_argument("--aidge_cmp", action="store_true", help="Use aidge tensor results as reference.")
+parser.add_argument(
+    '-v', '--verbose',
+    action='count',
+    default=0,
+    help = (
+        "Set the verbosity level of the console output."
+        "Use -v to increase verbosity, with the following levels in ascending ordern"
+        "default WARN - Only warnings and higher (WARN, ERROR, FATAL) are displayed.n"
+        "-v NOTICE - Notices and higher (NOTICE, WARN, ERROR, FATAL) are displayed.n"
+        "-vv INFO - Informational messages and higher (INFO, NOTICE, WARN, ERROR, FATAL) are displayed.n"
+        "-vvv DEBUG - All messages, including debug information, are displayed.n"
+        "Available levels in descending order of severityn"
+        "DEBUG < INFO < NOTICE < WARN < ERROR < FATAL."
+    )
+)
+args = parser.parse_args()
+
+USE_CUDA = not args.no_cuda
+
+# Setting Aidge verbose level
+if args.verbose == 0:
+    aidge_core.Log.set_console_level(aidge_core.Level.Error)
+elif args.verbose == 1:
+    aidge_core.Log.set_console_level(aidge_core.Level.Notice)
+elif args.verbose == 2:
+    aidge_core.Log.set_console_level(aidge_core.Level.Info)
+elif args.verbose >= 3:
+    aidge_core.Log.set_console_level(aidge_core.Level.Debug) 
+
+if USE_CUDA:
+    import aidge_backend_cuda
+
+# ------------------------------------------------------------
+# EXPORT CONFIG
+# ------------------------------------------------------------
+
+"""
+Export configuration details :
+- RNG_SEED :        Fix a random seed for torch to always get the same images from the dataset,
+                        therefore always getting the same output. 
+- NB_TEST :         Number of example inferences to perform (used to get an accuracy approximation).
+- NB_CALIB :        Number of samples used for the calibration step of quantization. 
+- MODEL_NAME :      Should be the same name as the onnx file you want to load and export. 
+- DO_EXAMPLES :     Perform example inferences (and allow to get accuracy approximation)
+- NB_BITS :         Quantization output precision. Should be 8 to work with this export. 
+- TARGET_TYPE :     The aidge datatype for tensors to be casted after the quantization step.
+- OPTIM_SIGN :      Quantization optional optimization based on data sign. 
+- SINGLE_SHIFT :    Quantization option specifying if inserted scaling nodes should be
+                        single shift or floating point.
+- ROUNDING :        Apply rounding on the data after the single shift step. 
+- NO_QUANTIZATION : Skip the quantization step. Should be set to False. 
+- CLIPPING :        Clipping method during quantization. 
+- FOLD_GRAPH :      The quantization step adds cast nodes to cast the graph into the given TARGET_TYPE.
+                        Enabling the FOLD_GRAPH will automatically fold these nodes into the following
+                        ones at the end of quantization step. 
+- USE_CUDA :        Determine if the quantization step uses the GPU. It is generally recommended
+                        to enable this option if you have access to GPUs as the quantization step
+                        may take a while to complete.
+- DEV_MODE :        The dev mode allows to identify errors more easily export the model with 
+                        symbolic links enabling to modify the source files directly in the
+                        generated export (make sure you installed the export plugin running
+                        `pip install -e .`). 
+                        Enabled running this python file, adding the --test argument. 
+- AIDGE_MODE :      Saves and export the outputs generated by the aidge inferences in order
+                        to compare it with the export outputs. 
+                        Enabled running this python file, adding the --aidge_cmp argument.
+"""
+
+print(" Available backends : ", aidge_core.Tensor.get_available_backends())
+
+quantize_model = False
+NB_BITS = 32
+TARGET_TYPE = aidge_core.dtype.float32 
+
+if args.dtype == "float32":
+    quantize_model = False
+elif args.dtype == "int8":
+    quantize_model = True
+    NB_BITS = 8
+    TARGET_TYPE = aidge_core.dtype.int32    # int8 not yet available
+else:
+    print(f"[ERROR] Datatype '{args.dtype}' not supported.")
+    print(f"[ERROR] Supported datatypes : {supported_types}.")
+    exit(1)
+
+RNG_SEED        = 1234 
+NB_TEST         = 20 # Test set
+NB_CALIB        = 20 # Calibration set
+MODEL_NAME      = 'resnet18'
+EXPORT_FOLDER   = f"export_{MODEL_NAME}_int8"
+DO_EXAMPLES     = True
+ 
+# Quantization params
+OPTIM_SIGN      = False   
+SINGLE_SHIFT    = True    
+ROUNDING        = True 
+NO_QUANTIZATION = False
+CLIPPING        = aidge_quantization.Clipping.MSE  # 'MAX'
+FOLD_GRAPH      = True
+
+# Export modes
+DEV_MODE      = args.dev
+AIDGE_CMP     = args.aidge_cmp
+
+IMAGENET_PATH = "/database2/ILSVRC2012/val"    # Search for ILSVRC2012
+
+def print_cfg():
+    print('\n RNG_SEED         = ', RNG_SEED)
+    print(' MODEL_NAME       = ', MODEL_NAME)
+    print(' NB_TEST          = ', NB_TEST)
+    print(' NB_CALIB         = ', NB_CALIB)
+    print(' NB_BITS          = ', NB_BITS)
+    print(' OPTIM_SIGN       = ', OPTIM_SIGN)
+    print(' NO_QUANTIZATION  = ', NO_QUANTIZATION)
+    print(' CLIPPING         = ', CLIPPING)
+    print(' SINGLE_SHIFT     = ', SINGLE_SHIFT)
+    print(' TARGET_TYPE      = ', TARGET_TYPE)
+    print(' FOLD_GRAPH       = ', FOLD_GRAPH)
+    print(' USE_CUDA         = ', USE_CUDA)
+    print(' DEV_MODE        = ', DEV_MODE)
+    print(' ROUNDING         = ', ROUNDING)
+
+print_cfg()
+
+torch.manual_seed(RNG_SEED)
+random.seed(RNG_SEED)
+np.random.seed(RNG_SEED)
+
+backend = "cuda" if USE_CUDA else "cpu"
+
+VAL_PATH = "/database2/ILSVRC2012/val.txt"  # File containing labels of image of val folder
+
+image_label_pairs = []
+with open(VAL_PATH, 'r') as f:
+    for line in f:
+        parts = line.strip().split()
+        if len(parts) == 2:
+            image_name, label = parts
+            image_label_pairs.append((image_name, int(label)))
+
+#random.shuffle(image_label_pairs)
+np.random.seed(RNG_SEED)
+#image_label_pairs = np.random.permutation(image_label_pairs).tolist()
+NB_SELECT = max(NB_TEST, NB_CALIB)  # Vérifie que NB_TEST et NB_CALIB sont fixés
+selected_pairs = image_label_pairs[:NB_SELECT]
+
+#selected_pairs = image_label_pairs[:max(NB_TEST, NB_CALIB)]
+
+# --------------------------------------------------------------
+# CREATE THE SAMPLES
+# --------------------------------------------------------------
+
+transform_val = transforms.Compose([transforms.Resize(256),
+                                    transforms.CenterCrop(224),
+                                    transforms.ToTensor(),
+                                    transforms.Normalize(mean=[0.485,0.456, 0.406], std=[0.229, 0.224, 0.225])
+                                    ])
+
+tensors = []
+labels  = []
+paths   = []
+index = 0
+
+for image_name, label in selected_pairs:
+    image_path = os.path.join(IMAGENET_PATH, image_name)
+    if os.path.exists(image_path):
+        try:
+            image = Image.open(image_path)
+            if image.mode != 'RGB':
+                image = image.convert('RGB')
+            tensor = transform_val(image)
+            tensors.append(tensor)
+            labels.append(label)
+            paths.append(image_path)
+        except Exception as e:
+            print(f"Error with image {image_path}: {e}")
+
+#print(f"Number of loaded tensors: {len(tensors)}")
+#for lbl, img_path in zip(labels, paths):
+#    print(f"Label: {lbl} -> Image Path: {img_path}")
+
+backend = "cuda" if USE_CUDA else "cpu"
+aidge_tensors = []
+for tensor in tensors:
+    array = tensor.numpy()
+    array = np.reshape(array, (1, 3, 224, 224))
+    array = normalize(array)
+    aidge_tensor = aidge_core.Tensor(array)
+    aidge_tensor.set_backend(backend)
+    aidge_tensor.set_datatype(aidge_core.dtype.float32)
+    aidge_tensors.append(aidge_tensor)
+
+
+# --------------------------------------------------------------
+# LOAD THE MODEL
+# --------------------------------------------------------------
+
+"""
+Load the .onnx model and perform some usual graph modifications :
+    - Remove the flatten nodes;
+    - Fuse the batchnorm nodes into the biases producers. 
+"""
+
+model = aidge_onnx.load_onnx(MODEL_NAME + ".onnx", verbose=False)
+model.save("imported_model")
+aidge_core.remove_flatten(model)
+aidge_core.fuse_batchnorm(model)
+model.save("imported_model_fused_bn")
+
+# --------------------------------------------------------------
+# SET UP THE AIDGE SCHEDULER
+# --------------------------------------------------------------
+
+"""
+The scheduler is an ordered version of the model, allowing to schedule
+nodes to be able to run inferences, for instance. 
+"""
+
+# Set up the backend
+model.set_datatype(aidge_core.dtype.float32)
+model.set_backend(backend)
+
+# Create the Scheduler 
+scheduler = aidge_core.SequentialScheduler(model)
+
+# --------------------------------------------------------------
+# RUN SOME EXAMPLES INFERENCES
+# --------------------------------------------------------------
+
+def propagate(model, scheduler, aidge_tensor):
+    """ Propagate the given tensor into the model
+    """
+    # Run the inference 
+    scheduler.forward(True, [aidge_tensor])    
+    # Gather the results
+    output_node = model.get_output_nodes().pop()
+    output_tensor = output_node.get_operator().get_output(0).clone()
+    output_tensor.set_backend("cpu")
+    return np.array(output_tensor)
+
+accuracy = 0
+if (DO_EXAMPLES):
+    print('\n EXAMPLE INFERENCES :')
+    nb_valid = 0
+    base_values = []
+    for i in range(NB_TEST):
+        output_array = propagate(model, scheduler, aidge_tensors[i])
+        print(labels[i], ' VS ', np.argmax(output_array), ' -> ', np.max(output_array))
+        base_values.append(np.max(output_array))
+        if (labels[i] == np.argmax(output_array)):
+            nb_valid += 1
+    accuracy = nb_valid / NB_TEST
+    print('\n MODEL ACCURACY = ', accuracy * 100, '%')
+
+
+#--------------------------------------------------------------
+# PERFORM THE QUANTIZATION
+# --------------------------------------------------------------
+
+if quantize_model:
+    aidge_quantization.quantize_network(
+        network = model, 
+        nb_bits = NB_BITS, 
+        input_dataset = aidge_tensors[0:NB_CALIB], 
+        clipping_mode = CLIPPING,
+        target_type = TARGET_TYPE,
+        no_quantization = NO_QUANTIZATION,
+        optimize_signs = OPTIM_SIGN, 
+        single_shift = SINGLE_SHIFT, 
+        use_cuda = USE_CUDA,
+        fold_graph = FOLD_GRAPH,
+        bitshift_rounding = ROUNDING)
+
+# --------------------------------------------------------------
+# RESCALE THE INPUT SAMPLES 
+# --------------------------------------------------------------
+
+"""
+Once the quantization is done, the graph now only accepts integer inputs. 
+So we need to rescale the dataset for the data to be within [0, 255].
+Also, tensors should be casted to be the same type as TARGET_TYPE. 
+"""
+if quantize_model:
+    rescaling = 2**(NB_BITS-1)-1
+    for i in range(max(NB_TEST, NB_CALIB)):
+        array = np.array(aidge_tensors[i]) * rescaling 
+        aidge_tensors[i] = aidge_core.Tensor(array)
+        aidge_tensors[i].set_datatype(TARGET_TYPE)
+
+# --------------------------------------------------------------
+# GENERATE NEW SCHEDULER
+# --------------------------------------------------------------
+
+"""
+Each time the graph has been change, it has to be reset. 
+Here some Quantizer and Cast nodes have been added. 
+"""
+
+""" [START Fix]
+We need first to manually add an input tensor with the correct datatype, 
+as it is not automatically done in PTQ. 
+"""
+# input_node = model.get_ordered_inputs()[0]
+# input_node[0].get_operator().set_input(0,aidge_tensors[0])
+""" [END Fix]"""
+if quantize_model:
+    scheduler.reset_scheduling()
+
+# --------------------------------------------------------------
+# PERFORM THE EXAMPLE INFERENCES AGAIN
+# --------------------------------------------------------------
+
+
+
+#for node in model.get_input_nodes():
+#    if node.type() == "Pad2D":
+#        node.set_name("Pad2D_input")
+#
+#for node in model.get_nodes():
+#    if (node.type() == "Conv2D"):
+#        if node.get_parent(0).name() == "Pad2D_input":
+#           node.set_name("Conv2D_input")
+
+model.save("post_ptq")
+
+if (DO_EXAMPLES and quantize_model):
+    print('\n QUANTIZED EXAMPLE INFERENCES :')
+    nb_valid = 0
+    post_values = []
+    for i in range(NB_TEST):
+        output_array = propagate(model, scheduler, aidge_tensors[i])
+        print(labels[i], ' VS ', np.argmax(output_array), ' -> ', np.max(output_array))
+        post_values.append(np.max(output_array))
+        if (labels[i] == np.argmax(output_array)):
+            nb_valid += 1
+
+    quant_accuracy = nb_valid / NB_TEST
+    print('\n MODEL ACCURACY = ', accuracy * 100, '%')
+    print('\n QUANTIZED ACCURACY = ', quant_accuracy * 100, '%')
+
+    print("post ptq")
+#    output_array = propagate(model, scheduler, aidge_tensors[0])
+
+#model.log_outputs("log_outputs_post_ptq")
+
+if USE_CUDA:    
+    model.set_backend("cpu")
+    for aidge_tensor in aidge_tensors:
+        aidge_tensor.set_backend("cpu")
+
+# --------------------------------------------------------------
+# FUSE NODES INTO METAOPS
+# --------------------------------------------------------------
+
+"""
+Here is made the link between the Aidge model and the CPP
+kernels implementation. In aidge, all the nodes calculations
+are performed separately (Pad -> Conv -> Quantizer -> ReLU -> ...).
+
+However within the CPP export, some core operators are merged
+in meta operators. For instance, the padding, scaling and ReLU are
+performed within the Conv kernel. 
+
+In this step, we use graph regex techniques to find the desired patterns
+within the graph in order to match the export implementation of the kernels. 
+"""
+
+# Expand meta ops
+"""
+We first need to expand the graph to break all the metaops that may already
+exist. For instance, PaddedConv will become Pad -> Conv. 
+"""
+aidge_core.expand_metaops(model)
+
+
+model.save("after_expand")
+
+# Exclude unwanted producers 
+"""
+Before fusing the nodes, we set a tag on the Producers in order to exclude
+from the export the ones holding coefficients, as they are directly handled
+within the layers parameters. 
+"""
+exclude_unwanted_producers(model)
+
+# Fuse nodes
+cpp_fuse_to_metaops(model)
+
+# Remove optional inputs 
+"""
+Some optional inputs may be added by the quantization step (for instance with the clipping nodes).
+Here we make sure that they will not be considered as actual graph inputs by the export, by
+excluding them from the ordered_inputs list of the model. 
+"""
+remove_optional_inputs(model)
+
+# Reset scheduler to apply graph modifications
+"""
+The scheduler always needs to be reset after graph manipulation.
+"""
+scheduler.reset_scheduling()
+
+# Name newly created MetaOps
+"""
+As names are optional in Aidge, the fuse_to_metaops function will not automatically
+give a name to the newly created metaOps. However, in an export context, we need 
+our operators to be named, as this will be used to name the corresponding files.
+"""
+scheduler.generate_scheduling() # Scheduler needs to be generated as it has just been reset
+set_nodes_names(scheduler)
+
+# --------------------------------------------------------------
+# LOG OUTPUTS FOR THE LAST IMAGE OF THE TEST DATASET
+# --------------------------------------------------------------
+
+"""
+Here a final inference is made on the input we want to export and run. 
+This will ensure that all the feature maps tensors (between the layers)
+hold the data corresponding to this specific input. 
+Then, the "log_outputs()" function (called later) will store these tensors
+into log files that may be exported as well for comparison purpose. 
+"""
+
+output_array = propagate(model, scheduler, aidge_tensors[0])
+
+print("### Exported Sample ###")
+print("Aidge prediction after quantization :", np.argmax(output_array), "(" + str(np.max(output_array)) + ")")
+print("Label :", labels[0])
+
+# --------------------------------------------------------------
+# HANDLING DATATYPE
+# --------------------------------------------------------------
+
+"""
+Now, despite the quantization stage, all the tensors of the model are
+still "virtually" in Int32. Before exporting the model, we have to set
+tensors' datatypes to Int8, except for biases which should remain in Int32.
+"""
+if quantize_model:
+    set_nodes_datatypes(model)
+
+# Store tensors values into log files
+"""
+Once the tensors has been casted, the log_outputs() function can be
+called to store their values into log files. 
+"""
+
+if os.path.isdir("log_outputs"):
+    shutil.rmtree("log_outputs")
+model.log_outputs("log_outputs")
+
+# --------------------------------------------------------------
+# TEST MODE
+# --------------------------------------------------------------
+
+"""
+The test mode is mainly used for validation and benchmark. The model will be 
+exported in a way that each layer's result will be compared with the CPU implementation. 
+The timings for each layer will be displayed. 
+In case of error, you will be able to enter debug mode, showing in-layer data or 
+changing the inputs of the layer, to isolate the source of the issue. 
+"""
+
+for node in model.get_nodes():
+    node.attributes().dev_mode = DEV_MODE
+
+# --------------------------------------------------------------
+# AIDGE CMP
+# --------------------------------------------------------------
+
+"""
+If the --aidge_cmp option is enabled, the feature maps generated by aidge with the 
+backend cpu will be exported in the generated export. It will be used as reference
+to verify that the results with the optimized kernels are correct for the exported
+model. 
+This option has to be passed to each node in order to be used within the Export Nodes.
+(JConv, JPad, ...) that you can find in the "export_gen/operator_export" folder. 
+"""
+
+for node in model.get_nodes():
+    node.attributes().aidge_cmp = AIDGE_CMP
+
+# --------------------------------------------------------------
+# EXPORT THE MODEL
+# --------------------------------------------------------------
+
+
+model.save("exported_model")
+inputs_tensor = aidge_core.Tensor(np.array(aidge_tensors[0]))
+# print(np.array(inputs_tensor)[0])
+inputs_tensor.set_data_format(aidge_core.dformat.nchw)
+inputs_tensor.set_data_format(aidge_core.dformat.nhwc)
+if args.dtype == "int8":
+    inputs_tensor.set_datatype(aidge_core.dtype.int8)
+
+#print(np.array(inputs_tensor)[0,:,:,:])
+#inputs_tensor.cpy_transpose(inputs_tensor, aidge_core.get_permutation_mapping(aidge_core.dformat.nchw, aidge_core.dformat.nhwc))
+# print(np.array(inputs_tensor)[0])
+
+aidge_export_cpp.export(EXPORT_FOLDER, 
+                        model, 
+                        scheduler, 
+                        labels = aidge_core.Tensor(labels[0]),
+                        inputs_tensor=inputs_tensor,
+                        dev_mode = DEV_MODE,
+                        aidge_cmp = AIDGE_CMP)
+#
+## --------------------------------------------------------------
+## GENERATE LABELS AND INPUTS FOR EXAMPLE INFERENCE
+## --------------------------------------------------------------
+#
+#input_label = np.array(labels).astype(np.int32).reshape(len(labels), 1)
+#generate_input_file(export_folder=EXPORT_FOLDER + "/data", 
+#                    array_name="labels",
+#                    tensor=aidge_core.Tensor(input_label))
+#
+#input_tensor = np.array(aidge_tensors[0:NB_TEST]).astype(np.int8).reshape(NB_TEST, 3, 224, 224)
+#generate_input_file(export_folder=EXPORT_FOLDER + "/data", 
+#                    array_name="inputs",
+#                    tensor=aidge_core.Tensor(input_tensor))
+#
+#
+#if TEST_MODE:
+#    input_tensor = aidge_core.Tensor(input_tensor)
+#    input_tensor.set_data_format(aidge_core.dformat.nchw)
+#    input_tensor.cpy_transpose(input_tensor, aidge_core.get_permutation_mapping(aidge_core.dformat.nchw, aidge_core.dformat.nhwc))
+#    generate_input_file(export_folder=EXPORT_FOLDER + "/data", 
+#                        array_name="inputs_ref",
+#                        tensor=input_tensor)
+#    
+## --------------------------------------------------------------
+## GENERATE DOCUMENTATION
+## --------------------------------------------------------------
+#
+#"""
+#Copy the corresponding README file into the generated export. 
+#"""
+#
+#generate_documentation(EXPORT_FOLDER, TEST_MODE)
+#
\ No newline at end of file
-- 
GitLab


From cce7422440f1dd17e59cfaa910baf05433d2d286 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:31:34 +0000
Subject: [PATCH 48/93] [Git] Add gitignores

---
 examples/export_ResNet18/.gitignore | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 examples/export_ResNet18/.gitignore

diff --git a/examples/export_ResNet18/.gitignore b/examples/export_ResNet18/.gitignore
new file mode 100644
index 0000000..4c2c62d
--- /dev/null
+++ b/examples/export_ResNet18/.gitignore
@@ -0,0 +1,5 @@
+# Exclude export artefacts
+export_resnet18_int8/
+log_outputs/*
+assets/*
+data/*
-- 
GitLab


From acb0b95041d3d70b6d133325e562352018888bd6 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 08:31:34 +0000
Subject: [PATCH 49/93] [Git] Add gitignores

---
 examples/export_LeNet/.gitignore    | 1 +
 examples/export_ResNet18/.gitignore | 1 +
 2 files changed, 2 insertions(+)

diff --git a/examples/export_LeNet/.gitignore b/examples/export_LeNet/.gitignore
index 20cb419..98ce649 100644
--- a/examples/export_LeNet/.gitignore
+++ b/examples/export_LeNet/.gitignore
@@ -3,3 +3,4 @@ export_lenet_int8/
 log_outputs/*
 assets/*
 data/*
+log.txt
diff --git a/examples/export_ResNet18/.gitignore b/examples/export_ResNet18/.gitignore
index 4c2c62d..a6e4e97 100644
--- a/examples/export_ResNet18/.gitignore
+++ b/examples/export_ResNet18/.gitignore
@@ -3,3 +3,4 @@ export_resnet18_int8/
 log_outputs/*
 assets/*
 data/*
+log.txt
-- 
GitLab


From e048a71d3c1474aad8b9d2048005f45c4647e00a Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 12:46:52 +0000
Subject: [PATCH 50/93] [Fix] Suppress constexpr and simplified the choice of
 rounding

---
 aidge_export_cpp/kernels/pooling.hpp | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/aidge_export_cpp/kernels/pooling.hpp b/aidge_export_cpp/kernels/pooling.hpp
index 0829cc4..85fece5 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -116,11 +116,9 @@ void pooling_forward(
                         }
                     }
 
-                    if constexpr (std::is_integral<Output_T>::value) {
-                        outputs[oOffset + output] = (Output_T) std::round((float)sum / (POOL_HEIGHT * POOL_WIDTH));
-                    } else {
-                        outputs[oOffset + output] = (Output_T) (sum / (POOL_HEIGHT * POOL_WIDTH));
-                    }
+                    outputs[oOffset + output] = static_cast<Output_T>(
+                        std::is_integral<Output_T>::value ? std::round((float)sum / (POOL_HEIGHT * POOL_WIDTH)) : sum / (POOL_HEIGHT * POOL_WIDTH)
+                    );
 
                 }
                 else {
-- 
GitLab


From 2f2655ef2048258cace06062a0fe8c47408f77e1 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 13:21:11 +0000
Subject: [PATCH 51/93] [Refactor] Simplify aidge_cmp function to one function
 for float or interger

---
 aidge_export_cpp/static/utils.hpp | 35 +++++++------------------------
 1 file changed, 8 insertions(+), 27 deletions(-)

diff --git a/aidge_export_cpp/static/utils.hpp b/aidge_export_cpp/static/utils.hpp
index e3776a3..77d9891 100644
--- a/aidge_export_cpp/static/utils.hpp
+++ b/aidge_export_cpp/static/utils.hpp
@@ -144,8 +144,7 @@ inline void saveOutputs(
 #if AIDGE_CMP
 
 template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, typename AidgeOutput_T, typename DevOutput_T>
-typename std::enable_if<std::is_floating_point<DevOutput_T>::value>::type 
-aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
+void aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
 
     printf("[AIDGE COMPARE] - %s\n", layer_name.c_str());
 
@@ -155,31 +154,13 @@ aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_
                 const int aidge_ofst = out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
                 const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
                 if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
-                    printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
-                            out, h, w, aidge_output[aidge_ofst], dev_output[dev_ofst]);                
-                    printf("Abort program.\n");
-                    exit(1);
-                }
-            }
-        }
-    }
-    printf("[SUCCESS]\n\n");
-}
-
-template<int NB_OUTPUTS, int OUT_WIDTH, int OUT_HEIGHT, typename AidgeOutput_T, typename DevOutput_T>
-typename std::enable_if<std::is_integral<DevOutput_T>::value>::type 
-aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T* dev_output) {
-
-    printf("[AIDGE COMPARE] - %s\n", layer_name.c_str());
-
-    for (auto out = 0; out < NB_OUTPUTS; ++out) {
-        for (auto h = 0; h < OUT_HEIGHT; ++h) {
-            for (auto w = 0; w < OUT_WIDTH; ++w) {
-                const int aidge_ofst = out * OUT_HEIGHT * OUT_WIDTH + h * OUT_WIDTH + w;
-                const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
-                if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
-                    printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
-                           out, h, w, aidge_output[aidge_ofst], dev_output[dev_ofst]);
+                    if (std::is_floating_point<DevOutput_T>::value) {
+                        printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
+                                out, h, w, static_cast<double>(aidge_output[aidge_ofst]), static_cast<double>(dev_output[dev_ofst]));
+                    } else {
+                        printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
+                              out, h, w, static_cast<int>(aidge_output[aidge_ofst]), static_cast<int>(dev_output[dev_ofst]));
+                    }
                     printf("Abort program.\n");
                     exit(1);
                 }
-- 
GitLab


From 9a63d37bfedfd9436fc02207e6eb5e54cf6e7455 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 15 Apr 2025 12:37:32 +0000
Subject: [PATCH 52/93] [Fix](Producer) Add ignore attribute to False by
 default

---
 aidge_export_cpp/operators/Producer.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/operators/Producer.py b/aidge_export_cpp/operators/Producer.py
index 1d7a211..6c75471 100644
--- a/aidge_export_cpp/operators/Producer.py
+++ b/aidge_export_cpp/operators/Producer.py
@@ -47,7 +47,10 @@ class ProducerCPP(ExportNode):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
         self.values = np.array(self.operator.get_output(0))
-        self.ignore = node.attributes().ignore
+        if node.attributes().has_attr("ignore"):
+            self.ignore = node.attributes().ignore
+        else:
+            self.ignore = False
 
         if node.attributes().has_attr("ignore"):
             self.ignore = node.attributes().ignore
-- 
GitLab


From c9f1c58010c5f71ba60adb1003d13c788d0508f9 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 18:34:00 +0200
Subject: [PATCH 53/93] [Refactor] Split rescaling.hpp and activation.hpp into
 two types of files : - kernels/ : The forward call of the corresponding
 kernels (Btw activation and rescaling have exactly the same
 implementation...) - network/ : The utilitary functions which may be used by
 other kernels

Also move the `macs.hpp` file into the network folder, for coherence
---
 aidge_export_cpp/export_registry.py           |  2 +
 aidge_export_cpp/kernels/activation.hpp       | 57 +------------
 aidge_export_cpp/kernels/batchnorm.hpp        |  2 +-
 aidge_export_cpp/kernels/convolution.hpp      |  6 +-
 aidge_export_cpp/kernels/elemwise.hpp         |  2 +-
 aidge_export_cpp/kernels/fullyconnected.hpp   |  6 +-
 aidge_export_cpp/kernels/matmul.hpp           |  2 +-
 aidge_export_cpp/kernels/rescaling.hpp        | 83 +------------------
 aidge_export_cpp/kernels/softmax.hpp          |  1 -
 aidge_export_cpp/operators/CppActivation.py   |  1 -
 aidge_export_cpp/operators/CppBatchNorm.py    |  4 +-
 aidge_export_cpp/operators/CppConv.py         |  4 +-
 aidge_export_cpp/operators/CppElemWise.py     |  2 -
 aidge_export_cpp/operators/CppFc.py           |  4 +-
 aidge_export_cpp/operators/CppPool.py         |  1 -
 aidge_export_cpp/operators/CppRescaling.py    |  3 +-
 aidge_export_cpp/operators/CppSoftmax.py      |  1 -
 aidge_export_cpp/static/activation_utils.hpp  | 56 +++++++++++++
 aidge_export_cpp/{kernels => static}/macs.hpp |  0
 aidge_export_cpp/static/rescaling_utils.hpp   | 78 +++++++++++++++++
 .../configuration/activation_config.jinja     |  2 +-
 .../configuration/batchnorm_config.jinja      |  2 +-
 .../configuration/convolution_config.jinja    |  2 +-
 .../configuration/elemwise_config.jinja       |  2 +-
 .../configuration/fullyconnected_config.jinja |  2 +-
 25 files changed, 158 insertions(+), 167 deletions(-)
 create mode 100644 aidge_export_cpp/static/activation_utils.hpp
 rename aidge_export_cpp/{kernels => static}/macs.hpp (100%)
 create mode 100644 aidge_export_cpp/static/rescaling_utils.hpp

diff --git a/aidge_export_cpp/export_registry.py b/aidge_export_cpp/export_registry.py
index d3fcd9a..ee54890 100644
--- a/aidge_export_cpp/export_registry.py
+++ b/aidge_export_cpp/export_registry.py
@@ -7,4 +7,6 @@ class ExportLibCpp(ExportLib):
         str(ROOT / "static" / "Makefile"): "",
         str(ROOT / "static" / "typedefs.hpp"): "dnn/include/network",
         str(ROOT / "static" / "utils.hpp"): "dnn/include/network",
+        str(ROOT / "static" / "rescaling_utils.hpp"): "dnn/include/network",
+        str(ROOT / "static" / "activation_utils.hpp"): "dnn/include/network",
     }
diff --git a/aidge_export_cpp/kernels/activation.hpp b/aidge_export_cpp/kernels/activation.hpp
index d669515..ee80ed2 100644
--- a/aidge_export_cpp/kernels/activation.hpp
+++ b/aidge_export_cpp/kernels/activation.hpp
@@ -1,61 +1,8 @@
 #ifndef __AIDGE_EXPORT_CPP_KERNELS_ACTIVATION__
 #define __AIDGE_EXPORT_CPP_KERNELS_ACTIVATION__
 
-#include <type_traits>
-#include "network/typedefs.hpp"
-#include "network/utils.hpp"
-#include "kernels/rescaling.hpp"
-
-template<typename Output_T, typename T,
-         typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
-__attribute__((always_inline)) inline
-Output_T saturate (T value, int32_t /*sat*/)
-{
-    return value;
-}
-
-template<typename Output_T, typename T,
-         typename std::enable_if<!std::is_floating_point<T>::value>::type* = nullptr>
-__attribute__((always_inline)) inline
-Output_T saturate (T value, uint32_t sat)
-{
-    if (std::is_unsigned<Output_T>::value) {
-        return clamp(value, T(0), (T(1) << sat) - 1);
-    } else {
-        return clamp(value, -(T(1) << (sat - 1)), (T(1) << (sat - 1)) - 1);
-    }
-}
-
-template<typename Output_T,
-         typename Sum_T,
-         typename Rescaling_T>
-__attribute__((always_inline)) inline
-Output_T activation_forward_value (Sum_T weightedSum,
-                                   int output,
-                                   ActivationFunction_T func,
-                                   const Rescaling_T& __restrict rescaling)
-{
-    switch(func) {
-        case Linear:
-        case Saturation: {
-            break;
-        }
-        case Rectifier: {
-            if(weightedSum <= 0) weightedSum = 0;
-            break;
-        }
-        default:
-            // Unsupported activation function
-            break;
-    }
-
-    // Value fixed here for now but it should be generated by
-    // the export module or determined by the type of Output_T
-    // For now only works for int8_t and uint8_t
-    const uint32_t NB_BITS = 8;
-    return saturate<Output_T>(rescaling(weightedSum, output), NB_BITS);
-}
-
+#include "network/activation_utils.hpp"
+#include "network/rescaling_utils.hpp"
 
 template<int NB_DATA,
          ActivationFunction_T ACTIVATION,
diff --git a/aidge_export_cpp/kernels/batchnorm.hpp b/aidge_export_cpp/kernels/batchnorm.hpp
index f05a047..27866ab 100644
--- a/aidge_export_cpp/kernels/batchnorm.hpp
+++ b/aidge_export_cpp/kernels/batchnorm.hpp
@@ -2,7 +2,7 @@
 #define __AIDGE_EXPORT_CPP_KERNELS_BATCHNORM__
 
 #include "network/typedefs.hpp"
-#include "kernels/activation.hpp"
+#include "network/activation_utils.hpp"
 
 #include <math.h>
 
diff --git a/aidge_export_cpp/kernels/convolution.hpp b/aidge_export_cpp/kernels/convolution.hpp
index 5855654..0648d80 100644
--- a/aidge_export_cpp/kernels/convolution.hpp
+++ b/aidge_export_cpp/kernels/convolution.hpp
@@ -2,10 +2,10 @@
 #define __AIDGE_EXPORT_CPP_KERNELS_CONVOLUTION__
 
 #include "network/typedefs.hpp"
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 #include "network/utils.hpp"
-#include "kernels/macs.hpp"
-#include "kernels/activation.hpp"
+#include "network/macs.hpp"
+#include "network/activation_utils.hpp"
 
 
 template<int NB_CHANNELS,
diff --git a/aidge_export_cpp/kernels/elemwise.hpp b/aidge_export_cpp/kernels/elemwise.hpp
index 67ee574..9468b33 100644
--- a/aidge_export_cpp/kernels/elemwise.hpp
+++ b/aidge_export_cpp/kernels/elemwise.hpp
@@ -2,7 +2,7 @@
 #define __AIDGE_EXPORT_CPP_KERNELS_ELEMWISE__
 
 #include "network/typedefs.hpp"
-#include "kernels/activation.hpp"
+#include "network/activation_utils.hpp"
 
 // Generic function for two inputs
 
diff --git a/aidge_export_cpp/kernels/fullyconnected.hpp b/aidge_export_cpp/kernels/fullyconnected.hpp
index 60805e7..abaab59 100644
--- a/aidge_export_cpp/kernels/fullyconnected.hpp
+++ b/aidge_export_cpp/kernels/fullyconnected.hpp
@@ -2,10 +2,10 @@
 #define __AIDGE_EXPORT_CPP_KERNELS_FULLYCONNECTED__
 
 #include "network/typedefs.hpp"
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 #include "network/utils.hpp"
-#include "kernels/macs.hpp"
-#include "kernels/activation.hpp"
+#include "network/macs.hpp"
+#include "network/activation_utils.hpp"
 
 template<int NB_CHANNELS,
          int CHANNELS_HEIGHT, int CHANNELS_WIDTH,
diff --git a/aidge_export_cpp/kernels/matmul.hpp b/aidge_export_cpp/kernels/matmul.hpp
index 4500993..b507c4f 100644
--- a/aidge_export_cpp/kernels/matmul.hpp
+++ b/aidge_export_cpp/kernels/matmul.hpp
@@ -2,7 +2,7 @@
 #define __AIDGE_EXPORT_CPP_KERNELS_MATMUL__
 
 #include "network/typedefs.hpp"
-#include "kernels/activation.hpp"
+#include "network/activation_utils.hpp"
 
 // Generic function for matmul and activation
 
diff --git a/aidge_export_cpp/kernels/rescaling.hpp b/aidge_export_cpp/kernels/rescaling.hpp
index 117a0cd..a831fa8 100644
--- a/aidge_export_cpp/kernels/rescaling.hpp
+++ b/aidge_export_cpp/kernels/rescaling.hpp
@@ -1,8 +1,8 @@
 #ifndef __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
 #define __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
 
-#include "kernels/activation.hpp"
-
+#include "network/rescaling_utils.hpp"
+#include "network/activation_utils.hpp"
 
 template<int NB_DATA,
          ActivationFunction_T ACTIVATION,
@@ -23,83 +23,4 @@ void rescaling_forward (
     }
 }
 
-
-// ---------------------------------------------------
-// ----------------- Saturate Utils ------------------
-// ---------------------------------------------------
-
-static int64_t toInt64(uint32_t lo, uint32_t hi) {
-    return (int64_t) (((uint64_t) hi) << 32ull) | ((uint64_t) lo);
-}
-
-static int64_t smlal(int32_t lhs, int32_t rhs, 
-                     uint32_t accumLo, uint32_t accumHi) 
-{
-    return ((int64_t) lhs) * ((int64_t) rhs) + toInt64(accumLo, accumHi);
-}
-
-// ---------------------------------------------------
-// --------------- Scaling by Shifting ---------------
-// ---------------------------------------------------
-
-template<int SHIFT>
-struct SingleShiftScaling {
-
-    template<typename Sum_T>
-    Sum_T operator()(Sum_T weightedSum, size_t /*output*/) const 
-    {
-        return (SHIFT != 0) ? ((weightedSum >> (SHIFT - 1)) + 1) >> 1   // Rounding
-                            : weightedSum;   
-    }
-
-    // // Shift attribute
-    // static const int mShift = SHIFT;
-    // static const Scaling_T mScalingType = SingleShift;
-
-    // // FP Attribute
-    // static const int32_t mScaling = 0;
-    // static const int64_t mFractionalBits = 0;
-
-};
-
-// ---------------------------------------------------
-// --------------- Fixed Point Scaling ---------------
-// ---------------------------------------------------
-
-template<int64_t SHIFT, int32_t COEF>
-struct FixedPointScaling {
-
-    template<typename Sum_T>
-    Sum_T operator()(Sum_T weightedSum, size_t /*output*/) const 
-    {
-        return smlal(weightedSum, COEF, HALF_LO, HALF_HI) >> SHIFT; 
-    }
-
-    // Attributes
-    static const uint32_t HALF_LO = (SHIFT > 0)
-        ? (1ull << (SHIFT - 1)) & 0xFFFFFFFF : 0;
-    static const uint32_t HALF_HI = (SHIFT > 0)
-        ? (1ull << (SHIFT - 1)) >> 32u : 0;
-    
-    // static const int32_t mScaling = SCALING;
-    // static const int64_t mFractionalBits = FRACTIONAL_BITS;
-    // static const Scaling_T mScalingType = FixedPoint;
-    // static const int mShift = 0;
-};
-
-// ---------------------------------------------------
-// ------------------- No Scaling --------------------
-// ---------------------------------------------------
-
-struct NoScaling {
-
-    template<typename Sum_T>
-    Sum_T operator()(Sum_T weightedSum, unsigned int /*output*/) const 
-    {
-        return weightedSum;
-    }
-
-};
-
-
 #endif  // __AIDGE_EXPORT_CPP_NETWORK_RESCALING__
diff --git a/aidge_export_cpp/kernels/softmax.hpp b/aidge_export_cpp/kernels/softmax.hpp
index f5472cf..d29e9b4 100644
--- a/aidge_export_cpp/kernels/softmax.hpp
+++ b/aidge_export_cpp/kernels/softmax.hpp
@@ -3,7 +3,6 @@
 
 #include "network/typedefs.hpp"
 #include "network/utils.hpp"
-#include "kernels/macs.hpp"
 
 #include <type_traits>
 #include <cmath>
diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
index f3ffc8e..b8c9367 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -50,7 +50,6 @@ class CppActivation(ExportNodeCpp):
         
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
         
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppBatchNorm.py b/aidge_export_cpp/operators/CppBatchNorm.py
index 285a64c..091dc76 100644
--- a/aidge_export_cpp/operators/CppBatchNorm.py
+++ b/aidge_export_cpp/operators/CppBatchNorm.py
@@ -25,9 +25,7 @@ class CppBatchNorm(ExportNodeCpp):
 
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "batchnorm.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "static" / "macs.hpp", "include/network", fwd_include=False)
 
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
index 5c39fe6..40b7577 100644
--- a/aidge_export_cpp/operators/CppConv.py
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -50,9 +50,7 @@ class CppConv(ExportNodeCpp):
         
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "convolution.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "static" / "macs.hpp", "include/network", fwd_include=False)
         
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index 041bce9..e39fe15 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -72,8 +72,6 @@ class CppElemWise(ExportNodeCpp):
 
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "elemwise.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
         
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index 793d74f..9758b1a 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -48,9 +48,7 @@ class CppFc(ExportNodeCpp):
         
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "fullyconnected.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "static" / "macs.hpp", "include/network", fwd_include=False)
 
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
index fa9aaec..54a4cbb 100644
--- a/aidge_export_cpp/operators/CppPool.py
+++ b/aidge_export_cpp/operators/CppPool.py
@@ -44,7 +44,6 @@ class CppPool(ExportNodeCpp):
         
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "pooling.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
 
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppRescaling.py b/aidge_export_cpp/operators/CppRescaling.py
index 815e5c2..96e395a 100644
--- a/aidge_export_cpp/operators/CppRescaling.py
+++ b/aidge_export_cpp/operators/CppRescaling.py
@@ -48,8 +48,7 @@ class CppRescaling(ExportNodeCpp):
         self.include_list = []
 
         # Path to the kernel(s) files to copy
-        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp", fwd_include=False)
-        self.add_kernel_to_copy(ROOT / "kernels" / "activation.hpp", fwd_include=False)
+        self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp")
         
 #        # Include aidge outputs within the fwd file
 #        if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/operators/CppSoftmax.py b/aidge_export_cpp/operators/CppSoftmax.py
index 90bcacf..14c6728 100644
--- a/aidge_export_cpp/operators/CppSoftmax.py
+++ b/aidge_export_cpp/operators/CppSoftmax.py
@@ -46,7 +46,6 @@ class CppSoftmax(ExportNodeCpp):
         
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "softmax.hpp")
-        self.add_kernel_to_copy(ROOT / "kernels" / "macs.hpp", fwd_include=False)
 
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
diff --git a/aidge_export_cpp/static/activation_utils.hpp b/aidge_export_cpp/static/activation_utils.hpp
new file mode 100644
index 0000000..c6a1bcd
--- /dev/null
+++ b/aidge_export_cpp/static/activation_utils.hpp
@@ -0,0 +1,56 @@
+#pragma once
+
+#include <type_traits>
+#include "network/typedefs.hpp"
+#include "network/utils.hpp"
+#include "network/rescaling_utils.hpp"
+
+template<typename Output_T, typename T,
+         typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+__attribute__((always_inline)) inline
+Output_T saturate (T value, int32_t /*sat*/)
+{
+    return value;
+}
+
+template<typename Output_T, typename T,
+         typename std::enable_if<!std::is_floating_point<T>::value>::type* = nullptr>
+__attribute__((always_inline)) inline
+Output_T saturate (T value, uint32_t sat)
+{
+    if (std::is_unsigned<Output_T>::value) {
+        return clamp(value, T(0), (T(1) << sat) - 1);
+    } else {
+        return clamp(value, -(T(1) << (sat - 1)), (T(1) << (sat - 1)) - 1);
+    }
+}
+
+template<typename Output_T,
+         typename Sum_T,
+         typename Rescaling_T>
+__attribute__((always_inline)) inline
+Output_T activation_forward_value (Sum_T weightedSum,
+                                   int output,
+                                   ActivationFunction_T func,
+                                   const Rescaling_T& __restrict rescaling)
+{
+    switch(func) {
+        case Linear:
+        case Saturation: {
+            break;
+        }
+        case Rectifier: {
+            if(weightedSum <= 0) weightedSum = 0;
+            break;
+        }
+        default:
+            // Unsupported activation function
+            break;
+    }
+
+    // Value fixed here for now but it should be generated by
+    // the export module or determined by the type of Output_T
+    // For now only works for int8_t and uint8_t
+    const uint32_t NB_BITS = 8;
+    return saturate<Output_T>(rescaling(weightedSum, output), NB_BITS);
+}
diff --git a/aidge_export_cpp/kernels/macs.hpp b/aidge_export_cpp/static/macs.hpp
similarity index 100%
rename from aidge_export_cpp/kernels/macs.hpp
rename to aidge_export_cpp/static/macs.hpp
diff --git a/aidge_export_cpp/static/rescaling_utils.hpp b/aidge_export_cpp/static/rescaling_utils.hpp
new file mode 100644
index 0000000..4fdb321
--- /dev/null
+++ b/aidge_export_cpp/static/rescaling_utils.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+// ---------------------------------------------------
+// ----------------- Saturate Utils ------------------
+// ---------------------------------------------------
+
+static int64_t toInt64(uint32_t lo, uint32_t hi) {
+    return (int64_t) (((uint64_t) hi) << 32ull) | ((uint64_t) lo);
+}
+
+static int64_t smlal(int32_t lhs, int32_t rhs, 
+                     uint32_t accumLo, uint32_t accumHi) 
+{
+    return ((int64_t) lhs) * ((int64_t) rhs) + toInt64(accumLo, accumHi);
+}
+
+// ---------------------------------------------------
+// --------------- Scaling by Shifting ---------------
+// ---------------------------------------------------
+
+template<int SHIFT>
+struct SingleShiftScaling {
+
+    template<typename Sum_T>
+    Sum_T operator()(Sum_T weightedSum, size_t /*output*/) const 
+    {
+        return (SHIFT != 0) ? ((weightedSum >> (SHIFT - 1)) + 1) >> 1   // Rounding
+                            : weightedSum;   
+    }
+
+    // // Shift attribute
+    // static const int mShift = SHIFT;
+    // static const Scaling_T mScalingType = SingleShift;
+
+    // // FP Attribute
+    // static const int32_t mScaling = 0;
+    // static const int64_t mFractionalBits = 0;
+
+};
+
+// ---------------------------------------------------
+// --------------- Fixed Point Scaling ---------------
+// ---------------------------------------------------
+
+template<int64_t SHIFT, int32_t COEF>
+struct FixedPointScaling {
+
+    template<typename Sum_T>
+    Sum_T operator()(Sum_T weightedSum, size_t /*output*/) const 
+    {
+        return smlal(weightedSum, COEF, HALF_LO, HALF_HI) >> SHIFT; 
+    }
+
+    // Attributes
+    static const uint32_t HALF_LO = (SHIFT > 0)
+        ? (1ull << (SHIFT - 1)) & 0xFFFFFFFF : 0;
+    static const uint32_t HALF_HI = (SHIFT > 0)
+        ? (1ull << (SHIFT - 1)) >> 32u : 0;
+    
+    // static const int32_t mScaling = SCALING;
+    // static const int64_t mFractionalBits = FRACTIONAL_BITS;
+    // static const Scaling_T mScalingType = FixedPoint;
+    // static const int mShift = 0;
+};
+
+// ---------------------------------------------------
+// ------------------- No Scaling --------------------
+// ---------------------------------------------------
+
+struct NoScaling {
+
+    template<typename Sum_T>
+    Sum_T operator()(Sum_T weightedSum, unsigned int /*output*/) const 
+    {
+        return weightedSum;
+    }
+
+};
diff --git a/aidge_export_cpp/templates/configuration/activation_config.jinja b/aidge_export_cpp/templates/configuration/activation_config.jinja
index df55575..84b122b 100644
--- a/aidge_export_cpp/templates/configuration/activation_config.jinja
+++ b/aidge_export_cpp/templates/configuration/activation_config.jinja
@@ -1,7 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 
 {# For layer configuration -#}
 {%- set nb_data = in_chan[0] * in_height[0] * in_width[0] %}
diff --git a/aidge_export_cpp/templates/configuration/batchnorm_config.jinja b/aidge_export_cpp/templates/configuration/batchnorm_config.jinja
index 751c55f..0c0bc49 100644
--- a/aidge_export_cpp/templates/configuration/batchnorm_config.jinja
+++ b/aidge_export_cpp/templates/configuration/batchnorm_config.jinja
@@ -1,7 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 
 {# For layer configuration -#}
 {% include "./_def_io.jinja" %}
diff --git a/aidge_export_cpp/templates/configuration/convolution_config.jinja b/aidge_export_cpp/templates/configuration/convolution_config.jinja
index d29f9e3..f1a57db 100644
--- a/aidge_export_cpp/templates/configuration/convolution_config.jinja
+++ b/aidge_export_cpp/templates/configuration/convolution_config.jinja
@@ -1,7 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 {# For layer configuration -#}
 {% include "./_def_io.jinja" %}
 {% include "./_meminfo.jinja" %}
diff --git a/aidge_export_cpp/templates/configuration/elemwise_config.jinja b/aidge_export_cpp/templates/configuration/elemwise_config.jinja
index 41c9c3f..f839602 100644
--- a/aidge_export_cpp/templates/configuration/elemwise_config.jinja
+++ b/aidge_export_cpp/templates/configuration/elemwise_config.jinja
@@ -1,7 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 
 {% include "./_def_io.jinja" %}
 {% include "./_meminfo.jinja" %}
diff --git a/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja b/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja
index c14a6d3..856d727 100644
--- a/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja
+++ b/aidge_export_cpp/templates/configuration/fullyconnected_config.jinja
@@ -1,7 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
-#include "kernels/rescaling.hpp"
+#include "network/rescaling_utils.hpp"
 {# For layer configuration -#}
 {% include "./_def_io.jinja" %}
 {% include "./_meminfo.jinja" %}
-- 
GitLab


From 47c827fa3a2cc5e789a09ea45cb652339f02f26c Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 23 Apr 2025 18:34:43 +0200
Subject: [PATCH 54/93] [Fix] Remove redundant code

---
 aidge_export_cpp/operators/CppElemWise.py | 17 -----------------
 1 file changed, 17 deletions(-)

diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index e39fe15..9f4f20a 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -38,23 +38,6 @@ class CppElemWise(ExportNodeCpp):
         #             else:
         #                 self.attributes["coef_value"] = node.get_operator().get_output(0)[0]
 
-        ## Set the scaling type
-        if self.attributes["coef_value"] != 1:
-            self.attributes["rescaling"] = "FixedPointScaling"
-        elif self.attributes["shift_value"] != 0:
-            self.attributes["rescaling"] = "SingleShiftScaling"
-
-        ## Get the scaling values
-        for prod in node.get_parents():
-            if prod is not None:
-                if prod.type() == "Producer":
-                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
-                    # elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
-                    #     self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
-                    else:
-                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
-
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
             self.attributes["rescaling"] = "FixedPointScaling"
-- 
GitLab


From 198c83c750762409fb97105b727c4ffaee7257d7 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 10:46:10 +0200
Subject: [PATCH 55/93] [Refactor] node.ignore is now a flag instead of a
 required attribute

---
 aidge_export_cpp/export_utils.py       |  1 -
 aidge_export_cpp/operators/Producer.py | 10 +---------
 2 files changed, 1 insertion(+), 10 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index a7f32bb..9ce15f5 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -167,7 +167,6 @@ def exclude_unwanted_producers(model):
     nodes_to_ignore = ["Mul", "BitShift"]
 
     for node in model.get_nodes():
-        node.attributes().ignore = False   
         if node.type() == "Producer":
             children_nodes = [n.type() for n in node.get_children()]
             for node_type in nodes_to_ignore:
diff --git a/aidge_export_cpp/operators/Producer.py b/aidge_export_cpp/operators/Producer.py
index 6c75471..627dcb2 100644
--- a/aidge_export_cpp/operators/Producer.py
+++ b/aidge_export_cpp/operators/Producer.py
@@ -47,15 +47,7 @@ class ProducerCPP(ExportNode):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
         self.values = np.array(self.operator.get_output(0))
-        if node.attributes().has_attr("ignore"):
-            self.ignore = node.attributes().ignore
-        else:
-            self.ignore = False
-
-        if node.attributes().has_attr("ignore"):
-            self.ignore = node.attributes().ignore
-        else:
-            self.ignore = False
+        self.ignore = node.attributes().has_attr("ignore")
 
         if len(self.values.shape) == 4:  # Note: export in HWC
             self.values = np.transpose(self.values, (0, 2, 3, 1))
-- 
GitLab


From 2de12c9472de441da719a608badab33f75ea5c16 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 10:46:26 +0200
Subject: [PATCH 56/93] [Chore] Typo

---
 examples/export_LeNet/lenet.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index 1f6eebc..63411ab 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -433,7 +433,7 @@ if quantize_model:
 
 # Store tensors values into log files
 """
-Once the tensors has been casted, the log_outputs() function can be
+Once the tensors have been casted, the log_outputs() function can be
 called to store their values into log files. 
 """
 
-- 
GitLab


From 0402dfb909a31597abc6c5b5474aa271dada9c64 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 10:47:10 +0200
Subject: [PATCH 57/93] [Fix] New names for log_outputs generated folders
 requiring the use of the new get_ranked_nodes_name() function

---
 aidge_export_cpp/export.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/aidge_export_cpp/export.py b/aidge_export_cpp/export.py
index 43de6e5..10741b0 100644
--- a/aidge_export_cpp/export.py
+++ b/aidge_export_cpp/export.py
@@ -75,11 +75,12 @@ def export(export_folder_name: str,
     be copied into the generated export in order to be used as reference. 
     """
     if aidge_cmp:
+        ranked_nodes = graphview.get_ranked_nodes_name("{0}[{1}#{3}]")
         os.makedirs(export_folder_name / "data" / "aidge_outputs")
         os.makedirs(export_folder_name / "data" / "export_outputs")
         for node in graphview.get_nodes():
             if node.type() != "Producer":
-                file_path = 'log_outputs/' + node.name() + '/output_0.log'
+                file_path = 'log_outputs/' + ranked_nodes[node] + '/output_0.log'
                 data_t = aidge2c(node.get_operator().get_output(0).dtype())
                 name = node.name() + '_output_0_aidge'
                 dims = node.get_operator().get_output(0).dims()
-- 
GitLab


From 4c75fdda8afbc477301102f84232800614964dca Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:19:45 +0200
Subject: [PATCH 58/93] [Fix](AvgPool) Accumulation variable type was not large
 enough to handle quantized inference (int8 instead of int32)

---
 aidge_export_cpp/kernels/pooling.hpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aidge_export_cpp/kernels/pooling.hpp b/aidge_export_cpp/kernels/pooling.hpp
index 85fece5..12ac69f 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -89,7 +89,7 @@ void pooling_forward(
                     outputs[oOffset + output] = maxVal;
                 }
                 else if (POOLING_TYPE == Average) {
-                    Output_T sum = 0;
+                    float sum = 0;
 
                     for (int sy = 0; sy < POOL_HEIGHT; ++sy) {
                         if ((PADDING_Y != 0
@@ -117,7 +117,7 @@ void pooling_forward(
                     }
 
                     outputs[oOffset + output] = static_cast<Output_T>(
-                        std::is_integral<Output_T>::value ? std::round((float)sum / (POOL_HEIGHT * POOL_WIDTH)) : sum / (POOL_HEIGHT * POOL_WIDTH)
+                        std::is_integral<Output_T>::value ? std::round(sum / (POOL_HEIGHT * POOL_WIDTH)) : sum / (POOL_HEIGHT * POOL_WIDTH)
                     );
 
                 }
-- 
GitLab


From c762b03c3591c46c1cb6ffd9506c8208c31cf38a Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:23:12 +0200
Subject: [PATCH 59/93] [Refactor] New way to handle how scaling data is
 retrieved from the Producers within the export nodes :
 set_scaling_attributes() function.

---
 aidge_export_cpp/operators/CppActivation.py | 16 +++-------------
 aidge_export_cpp/operators/CppConv.py       | 12 +++---------
 aidge_export_cpp/operators/CppElemWise.py   | 15 +--------------
 aidge_export_cpp/operators/CppFc.py         | 12 +++---------
 aidge_export_cpp/operators/CppRescaling.py  | 21 +++++----------------
 5 files changed, 15 insertions(+), 61 deletions(-)

diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/CppActivation.py
index b8c9367..7d81939 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/CppActivation.py
@@ -1,7 +1,6 @@
 import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("CppActivation", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppActivation(ExportNodeCpp):
@@ -21,17 +20,8 @@ class CppActivation(ExportNodeCpp):
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
-
-        ## Get the scaling values
-        for prod in node.get_parents():
-            if prod is not None:
-                if prod.type() == "Producer":
-                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
-                    # elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
-                    #     self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
-                    else:
-                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+            elif n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
 
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
index 40b7577..8cc28d4 100644
--- a/aidge_export_cpp/operators/CppConv.py
+++ b/aidge_export_cpp/operators/CppConv.py
@@ -1,7 +1,6 @@
 import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("CppConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppConv(ExportNodeCpp):
@@ -27,13 +26,8 @@ class CppConv(ExportNodeCpp):
                 self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
                 self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
                 self.attributes["dilation_dims"] = n.get_operator().attr.dilation_dims
-
-        ## Get the scaling values
-        for prod in node.get_parents():
-            if prod is not None:
-                if prod.type() == "Producer":
-                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+            elif n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
 
         ## Set the scaling type
         if self.attributes["shift_value"] != 0:
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
index 9f4f20a..fd4bb26 100644
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ b/aidge_export_cpp/operators/CppElemWise.py
@@ -1,8 +1,6 @@
 import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-from aidge_export_cpp import set_scaling_attributes
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("CppElemWise", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppElemWise(ExportNodeCpp):
@@ -27,17 +25,6 @@ class CppElemWise(ExportNodeCpp):
             elif n.type() == "Quantizer":
                 set_scaling_attributes(self, n)
 
-        ## Get the scaling values
-        # for node in node.get_parents():
-        #     if node is not None:
-        #         if node.type() == "Producer":
-        #             if node.attributes().has_attr("quantization.ptq.ShiftAmount"):
-        #                 self.attributes["shift_value"] = node.attributes().quantization.ptq.ShiftAmount
-        #             # elif node.attributes().has_attr("quantization.ptq.CompensationCoeff"):
-        #             #     self.attributes["coef_value"] = node.attributes().quantization.ptq.CompensationCoeff
-        #             else:
-        #                 self.attributes["coef_value"] = node.get_operator().get_output(0)[0]
-
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
             self.attributes["rescaling"] = "FixedPointScaling"
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/CppFc.py
index 9758b1a..8b10d91 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/CppFc.py
@@ -1,7 +1,6 @@
 import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("CppFc", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppFc(ExportNodeCpp):
@@ -25,13 +24,8 @@ class CppFc(ExportNodeCpp):
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
-
-        ## Get the scaling values
-        for prod in node.get_parents():
-            if prod is not None:
-                if prod.type() == "Producer":
-                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
+            elif n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
 
         ## Set the scaling type
         if self.attributes["shift_value"] != 0:
diff --git a/aidge_export_cpp/operators/CppRescaling.py b/aidge_export_cpp/operators/CppRescaling.py
index 96e395a..69c1ec5 100644
--- a/aidge_export_cpp/operators/CppRescaling.py
+++ b/aidge_export_cpp/operators/CppRescaling.py
@@ -1,9 +1,8 @@
 import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
-@ExportLibCpp.register_metaop("CppRescaling", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+@ExportLibCpp.register_metaop("Quantizer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class CppRescaling(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
@@ -14,23 +13,13 @@ class CppRescaling(ExportNodeCpp):
         self.attributes["shift_value"] = 0
         self.attributes["coef_value"] = 1
 
-
         # Browse the metaop to update kernel attributes
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
-
-
-        ## Get the scaling values
-        for prod in node.get_parents():
-            if prod is not None:
-                if prod.type() == "Producer":
-                    if prod.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        self.attributes["shift_value"] = prod.attributes().quantization.ptq.ShiftAmount
-#                    elif prod.attributes().has_attr("quantization.ptq.CompensationCoeff"):
-#                        self.attributes["coef_value"] = prod.attributes().quantization.ptq.CompensationCoeff
-                    else:
-                        self.attributes["coef_value"] = prod.get_operator().get_output(0)[0]
+        
+        # Set scaling attributes
+        set_scaling_attributes(self, node)
 
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
-- 
GitLab


From f5aca508f5d0b79c2d493b894386b747b8c941c2 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:31:12 +0200
Subject: [PATCH 60/93] [Chore] Typo

---
 aidge_export_cpp/static/utils.hpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/aidge_export_cpp/static/utils.hpp b/aidge_export_cpp/static/utils.hpp
index 77d9891..b9b7392 100644
--- a/aidge_export_cpp/static/utils.hpp
+++ b/aidge_export_cpp/static/utils.hpp
@@ -155,10 +155,10 @@ void aidge_cmp(std::string layer_name, AidgeOutput_T* aidge_output, DevOutput_T*
                 const int dev_ofst = h * OUT_WIDTH * NB_OUTPUTS + w * NB_OUTPUTS + out;
                 if (aidge_output[aidge_ofst] != dev_output[dev_ofst]) {
                     if (std::is_floating_point<DevOutput_T>::value) {
-                        printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
+                        printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %f vs dev_out = %f\n",
                                 out, h, w, static_cast<double>(aidge_output[aidge_ofst]), static_cast<double>(dev_output[dev_ofst]));
                     } else {
-                        printf("[ERROR] - (float) First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
+                        printf("[ERROR] - First error detected at %dx%dx%d (out x h x w) : aidge_out = %d vs dev_out = %d\n",
                               out, h, w, static_cast<int>(aidge_output[aidge_ofst]), static_cast<int>(dev_output[dev_ofst]));
                     }
                     printf("Abort program.\n");
-- 
GitLab


From b20792ac6983556157f936a7b0a754cf791a7b0b Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:31:57 +0200
Subject: [PATCH 61/93] [Fix] Missing rescaling include

---
 aidge_export_cpp/templates/configuration/matmul_config.jinja    | 1 +
 aidge_export_cpp/templates/configuration/rescaling_config.jinja | 1 +
 2 files changed, 2 insertions(+)

diff --git a/aidge_export_cpp/templates/configuration/matmul_config.jinja b/aidge_export_cpp/templates/configuration/matmul_config.jinja
index 0d941c0..d0d4958 100644
--- a/aidge_export_cpp/templates/configuration/matmul_config.jinja
+++ b/aidge_export_cpp/templates/configuration/matmul_config.jinja
@@ -1,6 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
+#include "network/rescaling_utils.hpp"
 
 {% include "./_def_io.jinja" %}
 {% include "./_meminfo.jinja" %}
diff --git a/aidge_export_cpp/templates/configuration/rescaling_config.jinja b/aidge_export_cpp/templates/configuration/rescaling_config.jinja
index ee9c69e..6f4e3ad 100644
--- a/aidge_export_cpp/templates/configuration/rescaling_config.jinja
+++ b/aidge_export_cpp/templates/configuration/rescaling_config.jinja
@@ -1,6 +1,7 @@
 {#- For name header -#}
 #ifndef {{ name|upper }}_LAYER_H
 #define {{ name|upper }}_LAYER_H
+#include "network/rescaling_utils.hpp"
 
 {# For layer configuration -#}
 {% include "./_def_io.jinja" %}
-- 
GitLab


From 056e1c81fdd006f88702ca9d025cbf2cbf64049f Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:32:20 +0200
Subject: [PATCH 62/93] [Chore] Remove memory section

---
 aidge_export_cpp/templates/data/aidge_tensor.jinja | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/templates/data/aidge_tensor.jinja b/aidge_export_cpp/templates/data/aidge_tensor.jinja
index f4f873f..3f086af 100644
--- a/aidge_export_cpp/templates/data/aidge_tensor.jinja
+++ b/aidge_export_cpp/templates/data/aidge_tensor.jinja
@@ -3,5 +3,5 @@
 static const {{ data_t }} {{ name }}
 {%- for dim in dims -%}
     [{{ dim }}]
-{%- endfor %} __attribute__((section(".nn_ddr"))) = 
+{%- endfor %} = 
 {{ values }};
-- 
GitLab


From d132b5596714c79b6185dac92620fdeda133ea02 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:33:10 +0200
Subject: [PATCH 63/93] [Fix] Force save_outputs data format

---
 aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja b/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
index f9962dd..6865be5 100644
--- a/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
+++ b/aidge_export_cpp/templates/kernel_forward/_save_outputs.jinja
@@ -13,7 +13,7 @@
         {{out_name[outidx]|upper}}_STRIDE, #}
         {{out_name[outidx]}},
         {{out_name[outidx]|upper}}_STREAM,
-        Format::{{out_format[outidx]}});
+        Format::NHWC);
     fclose({{out_name[outidx]|upper}}_STREAM);
 {% endfor %}
 #endif
-- 
GitLab


From 2f01fc97a64862f6982353d46153e8e90ffb147e Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Thu, 24 Apr 2025 13:35:08 +0200
Subject: [PATCH 64/93] [Chore] Clean unused code

---
 examples/export_ResNet18/resnet18.py | 86 +++-------------------------
 1 file changed, 8 insertions(+), 78 deletions(-)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 6ca0223..354e474 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -149,7 +149,10 @@ FOLD_GRAPH      = True
 DEV_MODE      = args.dev
 AIDGE_CMP     = args.aidge_cmp
 
-IMAGENET_PATH = "/database2/ILSVRC2012/val"    # Search for ILSVRC2012
+### Add your paths here ###
+IMAGENET_PATH = "/database/ILSVRC2012/val"  # Look for ILSVRC2012/val
+VAL_PATH = "/database/ILSVRC2012/val.txt"   # File containing labels of image of val folder (Look for val.txt)
+###########################
 
 def print_cfg():
     print('\n RNG_SEED         = ', RNG_SEED)
@@ -164,7 +167,7 @@ def print_cfg():
     print(' TARGET_TYPE      = ', TARGET_TYPE)
     print(' FOLD_GRAPH       = ', FOLD_GRAPH)
     print(' USE_CUDA         = ', USE_CUDA)
-    print(' DEV_MODE        = ', DEV_MODE)
+    print(' DEV_MODE         = ', DEV_MODE)
     print(' ROUNDING         = ', ROUNDING)
 
 print_cfg()
@@ -175,8 +178,6 @@ np.random.seed(RNG_SEED)
 
 backend = "cuda" if USE_CUDA else "cpu"
 
-VAL_PATH = "/database2/ILSVRC2012/val.txt"  # File containing labels of image of val folder
-
 image_label_pairs = []
 with open(VAL_PATH, 'r') as f:
     for line in f:
@@ -185,14 +186,10 @@ with open(VAL_PATH, 'r') as f:
             image_name, label = parts
             image_label_pairs.append((image_name, int(label)))
 
-#random.shuffle(image_label_pairs)
 np.random.seed(RNG_SEED)
-#image_label_pairs = np.random.permutation(image_label_pairs).tolist()
-NB_SELECT = max(NB_TEST, NB_CALIB)  # Vérifie que NB_TEST et NB_CALIB sont fixés
+NB_SELECT = max(NB_TEST, NB_CALIB)  # Check that NB_TEST and NB_CALIB are fixed
 selected_pairs = image_label_pairs[:NB_SELECT]
 
-#selected_pairs = image_label_pairs[:max(NB_TEST, NB_CALIB)]
-
 # --------------------------------------------------------------
 # CREATE THE SAMPLES
 # --------------------------------------------------------------
@@ -222,10 +219,6 @@ for image_name, label in selected_pairs:
         except Exception as e:
             print(f"Error with image {image_path}: {e}")
 
-#print(f"Number of loaded tensors: {len(tensors)}")
-#for lbl, img_path in zip(labels, paths):
-#    print(f"Label: {lbl} -> Image Path: {img_path}")
-
 backend = "cuda" if USE_CUDA else "cpu"
 aidge_tensors = []
 for tensor in tensors:
@@ -343,13 +336,6 @@ Each time the graph has been change, it has to be reset.
 Here some Quantizer and Cast nodes have been added. 
 """
 
-""" [START Fix]
-We need first to manually add an input tensor with the correct datatype, 
-as it is not automatically done in PTQ. 
-"""
-# input_node = model.get_ordered_inputs()[0]
-# input_node[0].get_operator().set_input(0,aidge_tensors[0])
-""" [END Fix]"""
 if quantize_model:
     scheduler.reset_scheduling()
 
@@ -357,17 +343,6 @@ if quantize_model:
 # PERFORM THE EXAMPLE INFERENCES AGAIN
 # --------------------------------------------------------------
 
-
-
-#for node in model.get_input_nodes():
-#    if node.type() == "Pad2D":
-#        node.set_name("Pad2D_input")
-#
-#for node in model.get_nodes():
-#    if (node.type() == "Conv2D"):
-#        if node.get_parent(0).name() == "Pad2D_input":
-#           node.set_name("Conv2D_input")
-
 model.save("post_ptq")
 
 if (DO_EXAMPLES and quantize_model):
@@ -385,11 +360,6 @@ if (DO_EXAMPLES and quantize_model):
     print('\n MODEL ACCURACY = ', accuracy * 100, '%')
     print('\n QUANTIZED ACCURACY = ', quant_accuracy * 100, '%')
 
-    print("post ptq")
-#    output_array = propagate(model, scheduler, aidge_tensors[0])
-
-#model.log_outputs("log_outputs_post_ptq")
-
 if USE_CUDA:    
     model.set_backend("cpu")
     for aidge_tensor in aidge_tensors:
@@ -531,19 +501,13 @@ for node in model.get_nodes():
 # EXPORT THE MODEL
 # --------------------------------------------------------------
 
-
 model.save("exported_model")
 inputs_tensor = aidge_core.Tensor(np.array(aidge_tensors[0]))
-# print(np.array(inputs_tensor)[0])
-inputs_tensor.set_data_format(aidge_core.dformat.nchw)
-inputs_tensor.set_data_format(aidge_core.dformat.nhwc)
+inputs_tensor.set_data_format(aidge_core.dformat.nchw)  # Init the dataformat (default -> nchw)
+inputs_tensor.set_data_format(aidge_core.dformat.nhwc)  # Transpose the data  (nchw -> nhwc)
 if args.dtype == "int8":
     inputs_tensor.set_datatype(aidge_core.dtype.int8)
 
-#print(np.array(inputs_tensor)[0,:,:,:])
-#inputs_tensor.cpy_transpose(inputs_tensor, aidge_core.get_permutation_mapping(aidge_core.dformat.nchw, aidge_core.dformat.nhwc))
-# print(np.array(inputs_tensor)[0])
-
 aidge_export_cpp.export(EXPORT_FOLDER, 
                         model, 
                         scheduler, 
@@ -551,37 +515,3 @@ aidge_export_cpp.export(EXPORT_FOLDER,
                         inputs_tensor=inputs_tensor,
                         dev_mode = DEV_MODE,
                         aidge_cmp = AIDGE_CMP)
-#
-## --------------------------------------------------------------
-## GENERATE LABELS AND INPUTS FOR EXAMPLE INFERENCE
-## --------------------------------------------------------------
-#
-#input_label = np.array(labels).astype(np.int32).reshape(len(labels), 1)
-#generate_input_file(export_folder=EXPORT_FOLDER + "/data", 
-#                    array_name="labels",
-#                    tensor=aidge_core.Tensor(input_label))
-#
-#input_tensor = np.array(aidge_tensors[0:NB_TEST]).astype(np.int8).reshape(NB_TEST, 3, 224, 224)
-#generate_input_file(export_folder=EXPORT_FOLDER + "/data", 
-#                    array_name="inputs",
-#                    tensor=aidge_core.Tensor(input_tensor))
-#
-#
-#if TEST_MODE:
-#    input_tensor = aidge_core.Tensor(input_tensor)
-#    input_tensor.set_data_format(aidge_core.dformat.nchw)
-#    input_tensor.cpy_transpose(input_tensor, aidge_core.get_permutation_mapping(aidge_core.dformat.nchw, aidge_core.dformat.nhwc))
-#    generate_input_file(export_folder=EXPORT_FOLDER + "/data", 
-#                        array_name="inputs_ref",
-#                        tensor=input_tensor)
-#    
-## --------------------------------------------------------------
-## GENERATE DOCUMENTATION
-## --------------------------------------------------------------
-#
-#"""
-#Copy the corresponding README file into the generated export. 
-#"""
-#
-#generate_documentation(EXPORT_FOLDER, TEST_MODE)
-#
\ No newline at end of file
-- 
GitLab


From d6cd17f8cd923034596cad2b50135df2f68a0e5a Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 25 Apr 2025 16:42:49 +0200
Subject: [PATCH 65/93] [Feat] New operators registration system

---
 aidge_export_cpp/export_utils.py              | 117 +++++++------
 .../{CppActivation.py => Activation.py}       |  37 ++--
 .../{CppBatchNorm.py => BatchNorm.py}         |   5 +-
 .../operators/{CppConcat.py => Concat.py}     |   2 +-
 aidge_export_cpp/operators/Conv.py            |  92 ++++++++++
 aidge_export_cpp/operators/CppConv.py         |  52 ------
 aidge_export_cpp/operators/CppElemWise.py     |  49 ------
 aidge_export_cpp/operators/CppPool.py         |  51 ------
 aidge_export_cpp/operators/ElemWise.py        | 130 ++++++++++++++
 .../operators/{CppFc.py => Fc.py}             |  51 ++++--
 .../operators/{CppPad.py => Pad.py}           |   0
 aidge_export_cpp/operators/Pool.py            | 165 ++++++++++++++++++
 .../{CppRescaling.py => Quantizer.py}         |  11 +-
 13 files changed, 518 insertions(+), 244 deletions(-)
 rename aidge_export_cpp/operators/{CppActivation.py => Activation.py} (79%)
 rename aidge_export_cpp/operators/{CppBatchNorm.py => BatchNorm.py} (96%)
 rename aidge_export_cpp/operators/{CppConcat.py => Concat.py} (98%)
 create mode 100644 aidge_export_cpp/operators/Conv.py
 delete mode 100644 aidge_export_cpp/operators/CppConv.py
 delete mode 100644 aidge_export_cpp/operators/CppElemWise.py
 delete mode 100644 aidge_export_cpp/operators/CppPool.py
 create mode 100644 aidge_export_cpp/operators/ElemWise.py
 rename aidge_export_cpp/operators/{CppFc.py => Fc.py} (68%)
 rename aidge_export_cpp/operators/{CppPad.py => Pad.py} (100%)
 create mode 100644 aidge_export_cpp/operators/Pool.py
 rename aidge_export_cpp/operators/{CppRescaling.py => Quantizer.py} (78%)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 9ce15f5..1ef9d99 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -11,48 +11,56 @@ def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
                        ordered input/output data within the computational graph.
     """
 
-    cpp_recipes = OrderedDict({
-        "Quantizer":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
-        "CppFc":          "FC->Quantizer?->ReLU?",
-        "CppConv":        "Conv2D#->Quantizer?->ReLU?; Conv2D#<-Pad2D?",
-        "CppPool":        "(MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)->ReLU?; (MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)<-Pad2D?",
-        "CppElemWise":    "(Add|Mul|Sub)->Quantizer?->ReLU?",
-        "CppActivation":  "ReLU"
-    })
-
     # cpp_recipes = OrderedDict({
-    #     # FC
-    #     "FcReLU":         "FC->ReLU",
-
-    #     # Conv
-    #     "PadConv":        "Conv2D<-Pad2D",
-    #     "ConvReLU":       "Conv2D->ReLU",
-    #     "PadConvReLU":    "PadConv->ReLU",
-
-    #     # Max Pooling
-    #     "PadMaxPool":     "MaxPooling2D<-Pad2D",
-    #     "MaxPoolReLU":    "MaxPooling2D->ReLU",
-    #     "PadMaxPoolReLU": "PadMaxPool->ReLU",
-
-    #     # Average Pooling
-    #     "PadAvgPool":     "AvgPooling2D<-Pad2D",
-    #     "AvgPoolReLU":    "AvgPooling2D->ReLU",
-    #     "PadAvgPoolReLU": "PadAvgPool->ReLU",
-
-    #     # Global Average Pooling
-    #     "PadGlobalAvgPool":     "GlobalAveragePooling2D<-Pad2D",
-    #     "GlobalAvgPoolReLU":    "GlobalAveragePooling2D->ReLU",
-    #     "PadGlobalAvgPoolReLU": "PadGlobalAveragePool->ReLU",
-
-    #     # ElemWise
-    #     "AddReLU":    "Add->ReLU",
-    #     "SubReLU":    "Sub->ReLU",
-    #     "MulReLU":    "Mul->ReLU"
+    #     "Quantizer":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
+    #     "CppFc":          "FC->Quantizer?->ReLU?",
+    #     "CppConv":        "Conv2D#->Quantizer?->ReLU?; Conv2D#<-Pad2D?",
+    #     "CppPool":        "(MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)->ReLU?; (MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)<-Pad2D?",
+    #     "CppElemWise":    "(Add|Mul|Sub)->Quantizer?->ReLU?",
+    #     "CppActivation":  "ReLU"
     # })
 
-    # Fuse Quantizers
-    # aidge_core.fuse_to_metaops(
-    #     graph_view, cpp_recipes["Quantizer"], "Quantizer")
+    cpp_recipes = OrderedDict({
+        # Quantization
+        "Quantizer":      "BitShift#->Clip; BitShift#<-Mul?",
+
+        # FC
+        "QFC":            "FC->Quantizer",
+        "FCAct":          "(FC|QFC)->(ReLU|LeakyReLU)",
+
+        # Conv
+        "QConv":          "Conv2D->Quantizer",
+        "PadConv":        "(QConv|Conv2D)<-Pad2D",
+        "ConvAct":        "(QConv|Conv2D)->(ReLU|LeakyReLU)",
+        "PadConvAct":     "PadConv->(ReLU|LeakyReLU)",
+
+        # Max Pooling
+        "PadMaxPool":     "MaxPooling2D<-Pad2D",
+        "MaxPoolAct":     "MaxPooling2D->(ReLU|LeakyReLU)",
+        "PadMaxPoolAct":  "PadMaxPool->(ReLU|LeakyReLU)",
+
+        # Average Pooling
+        "PadAvgPool":     "AvgPooling2D<-Pad2D",
+        "AvgPoolAct":     "AvgPooling2D->(ReLU|LeakyReLU)",
+        "PadAvgPoolAct":  "PadAvgPool->(ReLU|LeakyReLU)",
+
+        # Global Average Pooling
+        "PadGlobalAvgPool":     "GlobalAveragePooling2D<-Pad2D",
+        "GlobalAvgPoolAct":     "GlobalAveragePooling2D->(ReLU|LeakyReLU)",
+        "PadGlobalAvgPoolAct":  "PadGlobalAveragePool->(ReLU|LeakyReLU)",
+
+        # ElemWise
+        "QAdd":      "Add->Quantizer",
+        "QSub":      "Sub->Quantizer",
+        "QMul":      "Mul->Quantizer",
+        "AddAct":    "(QAdd|Add)->(ReLU|LeakyReLU)",
+        "SubAct":    "(QSub|Sub)->(ReLU|LeakyReLU)",
+        "MulAct":    "(QMul|Mul)->(ReLU|LeakyReLU)",
+
+        # Activation
+        "QReLU":        "ReLU->Quantizer",
+        "QLeakyReLU":   "LeakyReLU->Quantizer",
+    })
 
     for node, recipe in cpp_recipes.items():
         aidge_core.fuse_to_metaops(graph_view, recipe, node)
@@ -90,7 +98,8 @@ def set_nodes_names(scheduler):
             node_it += 1
 
             # Set producers names
-            if node_type in ["CppFc", "CppConv"]:
+            if node_type in ["QConv", "PadConv", "ConvAct", "PadConvAct",
+                             "QFC", "FCAct"]:
                 # nb_parents = len(node.get_parents())
                 node.get_parent(1).set_name(node.name() + "_weights")
                 if node.get_parent(2) is not None:
@@ -174,23 +183,33 @@ def exclude_unwanted_producers(model):
                     node.attributes().ignore = True
                     break
 
-def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, QNode: aidge_core.Node):
+
+
+def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, node: aidge_core.Node):
     """
-    Set shift and coef attributes of the given export node.
+    Look recursively for a Quantizer node inside of the given node, 
+    then set shift and coef attributes of the given export node.
     [TODO] Should be moved into aidge_core.ExportNode
 
     :param export_node: An instance of :py:class:`aidge_core.export_utils.ExportNode` to set the scaling
                         attributes needed for a quantized export. 
     :type export_node: aidge_core.export_utils.ExportNode
-    :param QNode: Quantizer node holding the shift and coef values. 
-    :type QNode: aidge_core.Node
+    :param node: Node which may hold a Quantizer node. 
+    :type node: aidge_core.Node
     """
 
-    for node in QNode.get_operator().get_micro_graph().get_nodes():
-        if node.type() == "BitShift":
-            export_node.attributes["shift_value"] = node.get_operator().get_input(1)[0]
-        elif node.type() == "Mul":
-            export_node.attributes["coef_value"] = node.get_operator().get_input(1)[0]
+    if node.type() == "Quantizer":
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "BitShift":
+                export_node.attributes["shift_value"] = n.get_operator().get_input(1)[0]
+            elif n.type() == "Mul":
+                export_node.attributes["coef_value"] = n.get_operator().get_input(1)[0]
+
+    elif isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            set_scaling_attributes(export_node, n)
+
+      
             
 def normalize(array):
     """
diff --git a/aidge_export_cpp/operators/CppActivation.py b/aidge_export_cpp/operators/Activation.py
similarity index 79%
rename from aidge_export_cpp/operators/CppActivation.py
rename to aidge_export_cpp/operators/Activation.py
index 7d81939..8bb52f9 100644
--- a/aidge_export_cpp/operators/CppActivation.py
+++ b/aidge_export_cpp/operators/Activation.py
@@ -2,13 +2,13 @@ import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
-@ExportLibCpp.register_metaop("CppActivation", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class CppActivation(ExportNodeCpp):
+@ExportLibCpp.register("ReLU", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class ReLU(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
         # Initialize kernel attributes
-        self.attributes["activation"] = "Linear"
+        self.attributes["activation"] = "Rectifier"
         self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
@@ -16,19 +16,6 @@ class CppActivation(ExportNodeCpp):
         self.attributes["shift_value"] = 0
         self.attributes["coef_value"] = 1
 
-        # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
-
-        ## Set the scaling type
-        if self.attributes["coef_value"] != 1:
-            self.attributes["rescaling"] = "FixedPointScaling"
-        elif self.attributes["shift_value"] != 0:
-            self.attributes["rescaling"] = "SingleShiftScaling"
-
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "activation_config.jinja")
         
@@ -45,4 +32,20 @@ class CppActivation(ExportNodeCpp):
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
             self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
-        
\ No newline at end of file
+    
+        
+@ExportLibCpp.register_metaop("QReLU", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QReLU(ReLU):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
+
+        # Update the scaling type
+        if self.attributes["coef_value"] != 1:
+            self.attributes["rescaling"] = "FixedPointScaling"
+        elif self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
diff --git a/aidge_export_cpp/operators/CppBatchNorm.py b/aidge_export_cpp/operators/BatchNorm.py
similarity index 96%
rename from aidge_export_cpp/operators/CppBatchNorm.py
rename to aidge_export_cpp/operators/BatchNorm.py
index 091dc76..b0f5a16 100644
--- a/aidge_export_cpp/operators/CppBatchNorm.py
+++ b/aidge_export_cpp/operators/BatchNorm.py
@@ -4,7 +4,7 @@ from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
 @ExportLibCpp.register("BatchNorm2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class CppBatchNorm(ExportNodeCpp):
+class BatchNorm(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
@@ -30,4 +30,5 @@ class CppBatchNorm(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+            
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppConcat.py b/aidge_export_cpp/operators/Concat.py
similarity index 98%
rename from aidge_export_cpp/operators/CppConcat.py
rename to aidge_export_cpp/operators/Concat.py
index b0c91e2..ea65f8d 100644
--- a/aidge_export_cpp/operators/CppConcat.py
+++ b/aidge_export_cpp/operators/Concat.py
@@ -5,7 +5,7 @@ from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
 @ExportLibCpp.register("Concat", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class CppConcat(ExportNodeCpp):
+class Concat(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
diff --git a/aidge_export_cpp/operators/Conv.py b/aidge_export_cpp/operators/Conv.py
new file mode 100644
index 0000000..e2562f7
--- /dev/null
+++ b/aidge_export_cpp/operators/Conv.py
@@ -0,0 +1,92 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
+
+@ExportLibCpp.register("Conv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class Conv(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["padding"] = [0, 0, 0, 0]
+        self.attributes["activation"] = "Linear"
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
+
+        ## Scaling
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+
+        # Browse the metaop to update kernel attributes
+        self.get_conv_attributes(node)
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "convolution.hpp")
+        self.add_kernel_to_copy(ROOT / "static" / "macs.hpp", "include/network", fwd_include=False)
+        
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+    
+    def get_conv_attributes(self, node):
+        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
+            for n in node.get_operator().get_micro_graph().get_nodes():
+                self.get_conv_attributes(n)
+
+        elif node.type() == "Conv2D":
+            self.attributes["kernel_dims"] = node.get_operator().attr.kernel_dims
+            self.attributes["stride_dims"] = node.get_operator().attr.stride_dims
+            self.attributes["dilation_dims"] = node.get_operator().attr.dilation_dims
+
+
+@ExportLibCpp.register_metaop("QConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QConv(Conv):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Look for Quantizer node and set shift and coef export node attributes
+        set_scaling_attributes(self, node)
+
+        ## Set the scaling type
+        if self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
+
+@ExportLibCpp.register_metaop("PadConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadConv(QConv):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "Pad2D":
+                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
+
+
+@ExportLibCpp.register_metaop("ConvAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class ConvAct(QConv):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
+
+
+@ExportLibCpp.register_metaop("PadConvAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadConvAct(PadConv, ConvAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
diff --git a/aidge_export_cpp/operators/CppConv.py b/aidge_export_cpp/operators/CppConv.py
deleted file mode 100644
index 8cc28d4..0000000
--- a/aidge_export_cpp/operators/CppConv.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
-
-@ExportLibCpp.register_metaop("CppConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class CppConv(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        # Initialize kernel attributes
-        self.attributes["padding"] = [0, 0, 0, 0]
-        self.attributes["activation"] = "Linear"
-        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
-
-        ## Scaling
-        self.attributes["rescaling"] = "NoScaling"
-        self.attributes["shift_value"] = 0
-
-        # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
-            elif n.type() == "Conv2D":
-                self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
-                self.attributes["dilation_dims"] = n.get_operator().attr.dilation_dims
-            elif n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
-
-        ## Set the scaling type
-        if self.attributes["shift_value"] != 0:
-            self.attributes["rescaling"] = "SingleShiftScaling"
-
-        # Template for layer configutation file generation
-        self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
-        
-        # Template layer call function generation within the forward file
-        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
-        
-        # Files to include within the generated forward.cpp file
-        self.include_list = []
-        
-        # Path to the kernel(s) files to copy
-        self.add_kernel_to_copy(ROOT / "kernels" / "convolution.hpp")
-        self.add_kernel_to_copy(ROOT / "static" / "macs.hpp", "include/network", fwd_include=False)
-        
-        # Include aidge outputs within the fwd file
-        if self.attributes["aidge_cmp"]:
-            self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppElemWise.py b/aidge_export_cpp/operators/CppElemWise.py
deleted file mode 100644
index fd4bb26..0000000
--- a/aidge_export_cpp/operators/CppElemWise.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
-
-@ExportLibCpp.register_metaop("CppElemWise", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class CppElemWise(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        # Initialize kernel attributes
-        self.attributes["activation"] = "Linear"
-        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
-
-        ## Scaling
-        self.attributes["rescaling"] = "NoScaling"
-        self.attributes["shift_value"] = 0
-        self.attributes["coef_value"] = 1
-
-        # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() in ["Add", "Mul", "Sub"]:
-                self.attributes["elemwise_op"] = n.type()
-            elif n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
-
-        ## Set the scaling type
-        if self.attributes["coef_value"] != 1:
-            self.attributes["rescaling"] = "FixedPointScaling"
-        elif self.attributes["shift_value"] != 0:
-            self.attributes["rescaling"] = "SingleShiftScaling"
-
-        # Template for layer configutation file generation
-        self.config_template = str(ROOT / "templates" / "configuration" / "elemwise_config.jinja")
-
-        # Template layer call function generation within the forward file
-        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
-
-        # Files to include within the generated forward.cpp file
-        self.include_list = []
-
-        # Path to the kernel(s) files to copy
-        self.add_kernel_to_copy(ROOT / "kernels" / "elemwise.hpp")
-        
-        # Include aidge outputs within the fwd file
-        if self.attributes["aidge_cmp"]:
-            self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppPool.py b/aidge_export_cpp/operators/CppPool.py
deleted file mode 100644
index 54a4cbb..0000000
--- a/aidge_export_cpp/operators/CppPool.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
-from aidge_export_cpp import ROOT
-from aidge_export_cpp import ExportLibCpp
-
-@ExportLibCpp.register_metaop("CppPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class CppPool(ExportNodeCpp):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-        # Initialize kernel attributes
-        self.attributes["stride_dims"] = [1, 1]
-        self.attributes["padding"] = [0, 0, 0, 0]
-        self.attributes["pool_type"] = "Max"
-        self.attributes["activation"] = "Linear"
-        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
-
-        # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
-            elif n.type() == "GlobalAveragePooling":
-                self.attributes["pool_type"] = "Average"
-                self.attributes["kernel_dims"] = [self.attributes["in_width"][0], self.attributes["in_height"][0]]
-            elif n.type() == "MaxPooling2D":
-                self.attributes["pool_type"] = "Max"
-                self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
-            elif n.type() == "AvgPooling2D":
-                self.attributes["pool_type"] = "Average"
-                self.attributes["kernel_dims"] = n.get_operator().attr.kernel_dims
-                self.attributes["stride_dims"] = n.get_operator().attr.stride_dims
-
-        # Template for layer configutation file generation
-        self.config_template = str(ROOT / "templates" / "configuration" / "pooling_config.jinja")
-        
-        # Template layer call function generation within the forward file
-        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
-        
-        # Files to include within the generated forward.cpp file
-        self.include_list = []
-        
-        # Path to the kernel(s) files to copy
-        self.add_kernel_to_copy(ROOT / "kernels" / "pooling.hpp")
-
-        # Include aidge outputs within the fwd file
-        if self.attributes["aidge_cmp"]:
-            self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/ElemWise.py b/aidge_export_cpp/operators/ElemWise.py
new file mode 100644
index 0000000..c27c351
--- /dev/null
+++ b/aidge_export_cpp/operators/ElemWise.py
@@ -0,0 +1,130 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
+
+class ElemWise(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["activation"] = "Linear"
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
+
+        ## Scaling
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+        self.attributes["coef_value"] = 1
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "elemwise_config.jinja")
+
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "elemwise_forward.jinja")
+
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "elemwise.hpp")
+        
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+
+
+class QElemWise(ElemWise):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
+
+        ## Set the scaling type
+        if self.attributes["coef_value"] != 1:
+            self.attributes["rescaling"] = "FixedPointScaling"
+        elif self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
+
+@ExportLibCpp.register("Add", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class Add(ElemWise):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["elemwise_op"] = "Add"
+
+
+@ExportLibCpp.register_metaop("QAdd", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QAdd(QElemWise, Add):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("AddAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class AddAct(QAdd):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
+
+
+@ExportLibCpp.register("Sub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class Sub(ElemWise):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["elemwise_op"] = "Sub"
+
+
+@ExportLibCpp.register_metaop("QSub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QSub(QElemWise, Sub):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("SubAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class SubAct(QSub):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
+
+
+@ExportLibCpp.register("Mul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class Mul(ElemWise):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        self.attributes["elemwise_op"] = "Mul"
+
+
+@ExportLibCpp.register_metaop("QMul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QMul(QElemWise, Mul):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("MulAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class MulAct(QMul):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppFc.py b/aidge_export_cpp/operators/Fc.py
similarity index 68%
rename from aidge_export_cpp/operators/CppFc.py
rename to aidge_export_cpp/operators/Fc.py
index 8b10d91..05eff82 100644
--- a/aidge_export_cpp/operators/CppFc.py
+++ b/aidge_export_cpp/operators/Fc.py
@@ -2,35 +2,19 @@ import aidge_core
 from aidge_core.export_utils import ExportNodeCpp
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
-@ExportLibCpp.register_metaop("CppFc", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class CppFc(ExportNodeCpp):
+@ExportLibCpp.register("FC", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class FC(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
         # Initialize kernel attributes
         self.attributes["activation"] = "Linear"
-        self.attributes["rescaling"] = "NoScaling"
         self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         ## Scaling
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["shift_value"] = 0
 
-        ## Scaling
-        self.attributes["rescaling"] = "NoScaling"
-        self.attributes["shift_value"] = 0
-
-        # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
-
-        ## Set the scaling type
-        if self.attributes["shift_value"] != 0:
-            self.attributes["rescaling"] = "SingleShiftScaling"
-
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "fullyconnected_config.jinja")
         
@@ -47,4 +31,33 @@ class CppFc(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+
+
+@ExportLibCpp.register_metaop("QFC", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QFC(FC):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "Quantizer":
+                set_scaling_attributes(self, n)
+
+        ## Set the scaling type
+        if self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
+
+@ExportLibCpp.register_metaop("FCAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class FCAct(QFC):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
diff --git a/aidge_export_cpp/operators/CppPad.py b/aidge_export_cpp/operators/Pad.py
similarity index 100%
rename from aidge_export_cpp/operators/CppPad.py
rename to aidge_export_cpp/operators/Pad.py
diff --git a/aidge_export_cpp/operators/Pool.py b/aidge_export_cpp/operators/Pool.py
new file mode 100644
index 0000000..558ec1b
--- /dev/null
+++ b/aidge_export_cpp/operators/Pool.py
@@ -0,0 +1,165 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT
+from aidge_export_cpp import ExportLibCpp
+
+class Pool(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["stride_dims"] = [1, 1]
+        self.attributes["padding"] = [0, 0, 0, 0]
+        self.attributes["pool_type"] = "Max"
+        self.attributes["activation"] = "Linear"
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "pooling_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "pooling_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "pooling.hpp")
+
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+
+
+class PadPool(Pool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "Pad2D":
+                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
+
+
+class PoolAct(Pool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        for n in node.get_operator().get_micro_graph().get_nodes():
+            if n.type() == "ReLU":
+                self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
+
+
+@ExportLibCpp.register("MaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class MaxPool(Pool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        self.get_maxpool_attributes(node)
+
+    def get_maxpool_attributes(self, node):
+        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
+            for n in node.get_operator().get_micro_graph().get_nodes():
+                self.get_maxpool_attributes(n)
+
+        elif node.type() == "MaxPooling2D":
+            self.attributes["pool_type"] = "Max"
+            self.attributes["kernel_dims"] = node.get_operator().attr.kernel_dims
+            self.attributes["stride_dims"] = node.get_operator().attr.stride_dims
+
+
+@ExportLibCpp.register_metaop("PadMaxPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadMaxPool(MaxPool, PadPool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("MaxPoolAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class MaxPoolAct(MaxPool, PoolAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("PadMaxPoolAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadMaxPoolAct(PadMaxPool, MaxPoolAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register("AvgPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class AvgPool(Pool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        self.get_avgpool_attributes(node)
+
+    def get_avgpool_attributes(self, node):
+        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
+            for n in node.get_operator().get_micro_graph().get_nodes():
+                self.get_avgpool_attributes(n)
+
+        elif node.type() == "AvgPooling2D":
+            self.attributes["pool_type"] = "Average"
+            self.attributes["kernel_dims"] = node.get_operator().attr.kernel_dims
+            self.attributes["stride_dims"] = node.get_operator().attr.stride_dims
+
+
+@ExportLibCpp.register_metaop("PadAvgPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadAvgPool(AvgPool, PadPool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("AvgPoolAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class AvgPoolAct(AvgPool, PoolAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("PadAvgPoolAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadAvgPoolAct(PadAvgPool, AvgPoolAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register("GlobalAveragePooling", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class GlobalAvgPool(Pool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        self.get_globalavgpool_attributes(node)
+
+    def get_globalavgpool_attributes(self, node):
+        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
+            for n in node.get_operator().get_micro_graph().get_nodes():
+                self.get_globalavgpool_attributes(n)
+
+        elif node.type() == "GlobalAvgPooling":
+            self.attributes["pool_type"] = "Average"
+            self.attributes["kernel_dims"] = [self.attributes["in_width"][0], self.attributes["in_height"][0]]
+
+
+@ExportLibCpp.register_metaop("PadGlobalAvgPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadGlobalAvgPool(GlobalAvgPool, PadPool):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("GlobalAvgPoolAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class GlobalAvgPoolAct(GlobalAvgPool, PoolAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+
+@ExportLibCpp.register_metaop("PadGlobalAvgPoolAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadGlobalAvgPoolAct(PadGlobalAvgPool, GlobalAvgPoolAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/CppRescaling.py b/aidge_export_cpp/operators/Quantizer.py
similarity index 78%
rename from aidge_export_cpp/operators/CppRescaling.py
rename to aidge_export_cpp/operators/Quantizer.py
index 69c1ec5..8a6c1ae 100644
--- a/aidge_export_cpp/operators/CppRescaling.py
+++ b/aidge_export_cpp/operators/Quantizer.py
@@ -17,6 +17,9 @@ class CppRescaling(ExportNodeCpp):
         for n in node.get_operator().get_micro_graph().get_nodes():
             if n.type() == "ReLU":
                 self.attributes["activation"] = "Rectifier"
+            elif n.type() == "LeakyReLU":
+                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
+                # TODO : Should not be checked manually for each activation
         
         # Set scaling attributes
         set_scaling_attributes(self, node)
@@ -39,7 +42,7 @@ class CppRescaling(ExportNodeCpp):
         # Path to the kernel(s) files to copy
         self.add_kernel_to_copy(ROOT / "kernels" / "rescaling.hpp")
         
-#        # Include aidge outputs within the fwd file
-#        if self.attributes["aidge_cmp"]:
-#            self.include_list.append("network/utils.hpp")   # aidge_cmp function
-#            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
-- 
GitLab


From f8f15ce93c3e1670d8f68b9dc15df62a92d2881b Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 25 Apr 2025 17:01:47 +0200
Subject: [PATCH 66/93] [Fix] Wrong MetaOps types for setting the weights and
 biases datatypes

---
 aidge_export_cpp/export_utils.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 1ef9d99..c82c643 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -98,8 +98,7 @@ def set_nodes_names(scheduler):
             node_it += 1
 
             # Set producers names
-            if node_type in ["QConv", "PadConv", "ConvAct", "PadConvAct",
-                             "QFC", "FCAct"]:
+            if node_type in ["QConv", "PadConv", "ConvAct", "PadConvAct", "QFC", "FCAct"]:
                 # nb_parents = len(node.get_parents())
                 node.get_parent(1).set_name(node.name() + "_weights")
                 if node.get_parent(2) is not None:
@@ -130,7 +129,7 @@ def set_nodes_datatypes(graph_view: aidge_core.GraphView):
     """
     for node in graph_view.get_nodes():
         if node.type() != "Producer":
-            if node.type() in ["CppConv", "CppFc"]:
+            if node.type() in ["QConv", "PadConv", "ConvAct", "PadConvAct", "QFC", "FCAct"]:
                 node.get_operator().get_input(0).set_datatype(aidge_core.dtype.int8)    # Input
                 node.get_operator().get_input(1).set_datatype(aidge_core.dtype.int8)    # Weights
                 if node.get_parent(2) is not None:
-- 
GitLab


From 70f7976f3ab2add04b4fbf6f232895069d644578 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Sun, 27 Apr 2025 15:43:28 +0200
Subject: [PATCH 67/93] [Chore] Add a get_node functions (aidge_core) to
 retrieve a node within a metaOp from its type

---
 aidge_export_cpp/export_utils.py         | 11 ++---
 aidge_export_cpp/operators/Activation.py |  4 +-
 aidge_export_cpp/operators/Conv.py       | 36 +++++---------
 aidge_export_cpp/operators/ElemWise.py   | 39 +++++++--------
 aidge_export_cpp/operators/Fc.py         |  4 +-
 aidge_export_cpp/operators/Pool.py       | 61 +++++++-----------------
 aidge_export_cpp/operators/Quantizer.py  | 14 +++---
 7 files changed, 62 insertions(+), 107 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index c82c643..b8f9eaf 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -2,6 +2,7 @@ import os
 from collections import OrderedDict
 
 import aidge_core
+from aidge_core.export_utils import get_node
 
 def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
     """ 
@@ -197,17 +198,15 @@ def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, node
     :type node: aidge_core.Node
     """
 
-    if node.type() == "Quantizer":
-        for n in node.get_operator().get_micro_graph().get_nodes():
+    QNode = get_node(node, "Quantizer")
+
+    if QNode is not None:
+        for n in QNode.get_operator().get_micro_graph().get_nodes():
             if n.type() == "BitShift":
                 export_node.attributes["shift_value"] = n.get_operator().get_input(1)[0]
             elif n.type() == "Mul":
                 export_node.attributes["coef_value"] = n.get_operator().get_input(1)[0]
 
-    elif isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            set_scaling_attributes(export_node, n)
-
       
             
 def normalize(array):
diff --git a/aidge_export_cpp/operators/Activation.py b/aidge_export_cpp/operators/Activation.py
index 8bb52f9..55e7e19 100644
--- a/aidge_export_cpp/operators/Activation.py
+++ b/aidge_export_cpp/operators/Activation.py
@@ -40,9 +40,7 @@ class QReLU(ReLU):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
+        set_scaling_attributes(self, node)
 
         # Update the scaling type
         if self.attributes["coef_value"] != 1:
diff --git a/aidge_export_cpp/operators/Conv.py b/aidge_export_cpp/operators/Conv.py
index e2562f7..4207184 100644
--- a/aidge_export_cpp/operators/Conv.py
+++ b/aidge_export_cpp/operators/Conv.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
+from aidge_core.export_utils import ExportNodeCpp, get_node
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register("Conv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -17,7 +17,10 @@ class Conv(ExportNodeCpp):
         self.attributes["shift_value"] = 0
 
         # Browse the metaop to update kernel attributes
-        self.get_conv_attributes(node)
+        ConvNode = get_node(node, "Conv2D") 
+        self.attributes["kernel_dims"] = ConvNode.get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = ConvNode.get_operator().attr.stride_dims
+        self.attributes["dilation_dims"] = ConvNode.get_operator().attr.dilation_dims
 
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
@@ -35,17 +38,7 @@ class Conv(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
-    
-    def get_conv_attributes(self, node):
-        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
-            for n in node.get_operator().get_micro_graph().get_nodes():
-                self.get_conv_attributes(n)
-
-        elif node.type() == "Conv2D":
-            self.attributes["kernel_dims"] = node.get_operator().attr.kernel_dims
-            self.attributes["stride_dims"] = node.get_operator().attr.stride_dims
-            self.attributes["dilation_dims"] = node.get_operator().attr.dilation_dims
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp") 
 
 
 @ExportLibCpp.register_metaop("QConv", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -67,9 +60,8 @@ class PadConv(QConv):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
+        PadNode = get_node(node, "Pad2D")
+        self.attributes["padding"] = PadNode.get_operator().attr.begin_end_borders
 
 
 @ExportLibCpp.register_metaop("ConvAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -78,13 +70,11 @@ class ConvAct(QConv):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
-
+        if get_node(node, "ReLU") is not None:
+            self.attributes["activation"] = "Rectifier"
+        elif get_node(node, "LeakyReLU") is not None:
+            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
+            # TODO : Should not be checked manually for each activation     
 
 @ExportLibCpp.register_metaop("PadConvAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class PadConvAct(PadConv, ConvAct):
diff --git a/aidge_export_cpp/operators/ElemWise.py b/aidge_export_cpp/operators/ElemWise.py
index c27c351..6a9ee7e 100644
--- a/aidge_export_cpp/operators/ElemWise.py
+++ b/aidge_export_cpp/operators/ElemWise.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
+from aidge_core.export_utils import ExportNodeCpp, get_node
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 class ElemWise(ExportNodeCpp):
@@ -38,9 +38,7 @@ class QElemWise(ElemWise):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
+        set_scaling_attributes(self, node)
 
         ## Set the scaling type
         if self.attributes["coef_value"] != 1:
@@ -68,12 +66,11 @@ class AddAct(QAdd):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
+        if get_node(node, "ReLU") is not None:
+            self.attributes["activation"] = "Rectifier"
+        elif get_node(node, "LeakyReLU") is not None:
+            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
+            # TODO : Should not be checked manually for each activation    
 
 
 @ExportLibCpp.register("Sub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -95,12 +92,11 @@ class SubAct(QSub):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
+        if get_node(node, "ReLU") is not None:
+            self.attributes["activation"] = "Rectifier"
+        elif get_node(node, "LeakyReLU") is not None:
+            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
+            # TODO : Should not be checked manually for each activation    
 
 
 @ExportLibCpp.register("Mul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -122,9 +118,8 @@ class MulAct(QMul):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
\ No newline at end of file
+        if get_node(node, "ReLU") is not None:
+            self.attributes["activation"] = "Rectifier"
+        elif get_node(node, "LeakyReLU") is not None:
+            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
+            # TODO : Should not be checked manually for each activation    
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Fc.py b/aidge_export_cpp/operators/Fc.py
index 05eff82..b83754b 100644
--- a/aidge_export_cpp/operators/Fc.py
+++ b/aidge_export_cpp/operators/Fc.py
@@ -40,9 +40,7 @@ class QFC(FC):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "Quantizer":
-                set_scaling_attributes(self, n)
+        set_scaling_attributes(self, node)
 
         ## Set the scaling type
         if self.attributes["shift_value"] != 0:
diff --git a/aidge_export_cpp/operators/Pool.py b/aidge_export_cpp/operators/Pool.py
index 558ec1b..5b483bd 100644
--- a/aidge_export_cpp/operators/Pool.py
+++ b/aidge_export_cpp/operators/Pool.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
+from aidge_core.export_utils import ExportNodeCpp, get_node
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
@@ -37,9 +37,8 @@ class PadPool(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "Pad2D":
-                self.attributes["padding"] = n.get_operator().attr.begin_end_borders
+        PadNode = get_node(node, "Pad2D")
+        self.attributes["padding"] = PadNode.get_operator().attr.begin_end_borders
 
 
 class PoolAct(Pool):
@@ -47,12 +46,11 @@ class PoolAct(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
+        if get_node(node, "ReLU") is not None:
+            self.attributes["activation"] = "Rectifier"
+        elif get_node(node, "LeakyReLU") is not None:
+            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
+            # TODO : Should not be checked manually for each activation    
 
 
 @ExportLibCpp.register("MaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -61,17 +59,10 @@ class MaxPool(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        self.get_maxpool_attributes(node)
-
-    def get_maxpool_attributes(self, node):
-        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
-            for n in node.get_operator().get_micro_graph().get_nodes():
-                self.get_maxpool_attributes(n)
-
-        elif node.type() == "MaxPooling2D":
-            self.attributes["pool_type"] = "Max"
-            self.attributes["kernel_dims"] = node.get_operator().attr.kernel_dims
-            self.attributes["stride_dims"] = node.get_operator().attr.stride_dims
+        PoolNode = get_node(node, "MaxPooling2D")
+        self.attributes["pool_type"] = "Max"
+        self.attributes["kernel_dims"] = PoolNode.get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = PoolNode.get_operator().attr.stride_dims
 
 
 @ExportLibCpp.register_metaop("PadMaxPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -98,17 +89,10 @@ class AvgPool(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        self.get_avgpool_attributes(node)
-
-    def get_avgpool_attributes(self, node):
-        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
-            for n in node.get_operator().get_micro_graph().get_nodes():
-                self.get_avgpool_attributes(n)
-
-        elif node.type() == "AvgPooling2D":
-            self.attributes["pool_type"] = "Average"
-            self.attributes["kernel_dims"] = node.get_operator().attr.kernel_dims
-            self.attributes["stride_dims"] = node.get_operator().attr.stride_dims
+        PoolNode = get_node(node, "AvgPooling2D")
+        self.attributes["pool_type"] = "Average"
+        self.attributes["kernel_dims"] = PoolNode.get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = PoolNode.get_operator().attr.stride_dims
 
 
 @ExportLibCpp.register_metaop("PadAvgPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -134,17 +118,8 @@ class GlobalAvgPool(Pool):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
-        # Browse the metaop to update kernel attributes
-        self.get_globalavgpool_attributes(node)
-
-    def get_globalavgpool_attributes(self, node):
-        if isinstance(node.get_operator(), aidge_core.MetaOperatorOp):
-            for n in node.get_operator().get_micro_graph().get_nodes():
-                self.get_globalavgpool_attributes(n)
-
-        elif node.type() == "GlobalAvgPooling":
-            self.attributes["pool_type"] = "Average"
-            self.attributes["kernel_dims"] = [self.attributes["in_width"][0], self.attributes["in_height"][0]]
+        self.attributes["pool_type"] = "Average"
+        self.attributes["kernel_dims"] = [self.attributes["in_width"][0], self.attributes["in_height"][0]]
 
 
 @ExportLibCpp.register_metaop("PadGlobalAvgPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
diff --git a/aidge_export_cpp/operators/Quantizer.py b/aidge_export_cpp/operators/Quantizer.py
index 8a6c1ae..cadc3bf 100644
--- a/aidge_export_cpp/operators/Quantizer.py
+++ b/aidge_export_cpp/operators/Quantizer.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
+from aidge_core.export_utils import ExportNodeCpp, get_node
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("Quantizer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -12,14 +12,14 @@ class CppRescaling(ExportNodeCpp):
         self.attributes["rescaling"] = "NoScaling"
         self.attributes["shift_value"] = 0
         self.attributes["coef_value"] = 1
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
+        if get_node(node, "ReLU") is not None:
+            self.attributes["activation"] = "Rectifier"
+        elif get_node(node, "LeakyReLU") is not None:
+            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
+            # TODO : Should not be checked manually for each activation     
         
         # Set scaling attributes
         set_scaling_attributes(self, node)
-- 
GitLab


From af267e02690368932634b52c49343c5fc945d715 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Sun, 27 Apr 2025 15:43:51 +0200
Subject: [PATCH 68/93] [Chore] Rename the softmax operator

---
 aidge_export_cpp/operators/{CppSoftmax.py => Softmax.py} | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
 rename aidge_export_cpp/operators/{CppSoftmax.py => Softmax.py} (98%)

diff --git a/aidge_export_cpp/operators/CppSoftmax.py b/aidge_export_cpp/operators/Softmax.py
similarity index 98%
rename from aidge_export_cpp/operators/CppSoftmax.py
rename to aidge_export_cpp/operators/Softmax.py
index 14c6728..aa83008 100644
--- a/aidge_export_cpp/operators/CppSoftmax.py
+++ b/aidge_export_cpp/operators/Softmax.py
@@ -4,7 +4,7 @@ from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
 @ExportLibCpp.register("Softmax", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
-class CppSoftmax(ExportNodeCpp):
+class Softmax(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
-- 
GitLab


From 70ca028a66137398611d68241cf9f1334d3de0ec Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Mon, 28 Apr 2025 10:06:19 +0200
Subject: [PATCH 69/93] [Chore] get_node -> get_node_from_metaop

---
 aidge_export_cpp/export_utils.py        |  4 ++--
 aidge_export_cpp/operators/Conv.py      | 10 +++++-----
 aidge_export_cpp/operators/ElemWise.py  | 14 +++++++-------
 aidge_export_cpp/operators/Pool.py      | 12 ++++++------
 aidge_export_cpp/operators/Quantizer.py |  6 +++---
 5 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index b8f9eaf..83738d3 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -2,7 +2,7 @@ import os
 from collections import OrderedDict
 
 import aidge_core
-from aidge_core.export_utils import get_node
+from aidge_core.export_utils import get_node_from_metaop
 
 def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
     """ 
@@ -198,7 +198,7 @@ def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, node
     :type node: aidge_core.Node
     """
 
-    QNode = get_node(node, "Quantizer")
+    QNode = get_node_from_metaop(node, "Quantizer")
 
     if QNode is not None:
         for n in QNode.get_operator().get_micro_graph().get_nodes():
diff --git a/aidge_export_cpp/operators/Conv.py b/aidge_export_cpp/operators/Conv.py
index 4207184..e1d17bc 100644
--- a/aidge_export_cpp/operators/Conv.py
+++ b/aidge_export_cpp/operators/Conv.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp, get_node
+from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register("Conv2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -17,7 +17,7 @@ class Conv(ExportNodeCpp):
         self.attributes["shift_value"] = 0
 
         # Browse the metaop to update kernel attributes
-        ConvNode = get_node(node, "Conv2D") 
+        ConvNode = get_node_from_metaop(node, "Conv2D") 
         self.attributes["kernel_dims"] = ConvNode.get_operator().attr.kernel_dims
         self.attributes["stride_dims"] = ConvNode.get_operator().attr.stride_dims
         self.attributes["dilation_dims"] = ConvNode.get_operator().attr.dilation_dims
@@ -60,7 +60,7 @@ class PadConv(QConv):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        PadNode = get_node(node, "Pad2D")
+        PadNode = get_node_from_metaop(node, "Pad2D")
         self.attributes["padding"] = PadNode.get_operator().attr.begin_end_borders
 
 
@@ -70,9 +70,9 @@ class ConvAct(QConv):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU") is not None:
             self.attributes["activation"] = "Rectifier"
-        elif get_node(node, "LeakyReLU") is not None:
+        elif get_node_from_metaop(node, "LeakyReLU") is not None:
             aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
             # TODO : Should not be checked manually for each activation     
 
diff --git a/aidge_export_cpp/operators/ElemWise.py b/aidge_export_cpp/operators/ElemWise.py
index 6a9ee7e..4603509 100644
--- a/aidge_export_cpp/operators/ElemWise.py
+++ b/aidge_export_cpp/operators/ElemWise.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp, get_node
+from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 class ElemWise(ExportNodeCpp):
@@ -66,9 +66,9 @@ class AddAct(QAdd):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU") is not None:
             self.attributes["activation"] = "Rectifier"
-        elif get_node(node, "LeakyReLU") is not None:
+        elif get_node_from_metaop(node, "LeakyReLU") is not None:
             aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
             # TODO : Should not be checked manually for each activation    
 
@@ -92,9 +92,9 @@ class SubAct(QSub):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU") is not None:
             self.attributes["activation"] = "Rectifier"
-        elif get_node(node, "LeakyReLU") is not None:
+        elif get_node_from_metaop(node, "LeakyReLU") is not None:
             aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
             # TODO : Should not be checked manually for each activation    
 
@@ -118,8 +118,8 @@ class MulAct(QMul):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU") is not None:
             self.attributes["activation"] = "Rectifier"
-        elif get_node(node, "LeakyReLU") is not None:
+        elif get_node_from_metaop(node, "LeakyReLU") is not None:
             aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
             # TODO : Should not be checked manually for each activation    
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Pool.py b/aidge_export_cpp/operators/Pool.py
index 5b483bd..bf7e7eb 100644
--- a/aidge_export_cpp/operators/Pool.py
+++ b/aidge_export_cpp/operators/Pool.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp, get_node
+from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT
 from aidge_export_cpp import ExportLibCpp
 
@@ -37,7 +37,7 @@ class PadPool(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        PadNode = get_node(node, "Pad2D")
+        PadNode = get_node_from_metaop(node, "Pad2D")
         self.attributes["padding"] = PadNode.get_operator().attr.begin_end_borders
 
 
@@ -46,9 +46,9 @@ class PoolAct(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU") is not None:
             self.attributes["activation"] = "Rectifier"
-        elif get_node(node, "LeakyReLU") is not None:
+        elif get_node_from_metaop(node, "LeakyReLU") is not None:
             aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
             # TODO : Should not be checked manually for each activation    
 
@@ -59,7 +59,7 @@ class MaxPool(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        PoolNode = get_node(node, "MaxPooling2D")
+        PoolNode = get_node_from_metaop(node, "MaxPooling2D")
         self.attributes["pool_type"] = "Max"
         self.attributes["kernel_dims"] = PoolNode.get_operator().attr.kernel_dims
         self.attributes["stride_dims"] = PoolNode.get_operator().attr.stride_dims
@@ -89,7 +89,7 @@ class AvgPool(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        PoolNode = get_node(node, "AvgPooling2D")
+        PoolNode = get_node_from_metaop(node, "AvgPooling2D")
         self.attributes["pool_type"] = "Average"
         self.attributes["kernel_dims"] = PoolNode.get_operator().attr.kernel_dims
         self.attributes["stride_dims"] = PoolNode.get_operator().attr.stride_dims
diff --git a/aidge_export_cpp/operators/Quantizer.py b/aidge_export_cpp/operators/Quantizer.py
index cadc3bf..a33bc14 100644
--- a/aidge_export_cpp/operators/Quantizer.py
+++ b/aidge_export_cpp/operators/Quantizer.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp, get_node
+from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("Quantizer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -15,9 +15,9 @@ class CppRescaling(ExportNodeCpp):
         self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         # Browse the metaop to update kernel attributes
-        if get_node(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU") is not None:
             self.attributes["activation"] = "Rectifier"
-        elif get_node(node, "LeakyReLU") is not None:
+        elif get_node_from_metaop(node, "LeakyReLU") is not None:
             aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
             # TODO : Should not be checked manually for each activation     
         
-- 
GitLab


From 1683248dc404116392c87ef1df6fa646149dfcd3 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Mon, 28 Apr 2025 10:36:35 +0200
Subject: [PATCH 70/93] [Fix] Exclude Clip producers

---
 aidge_export_cpp/export_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 83738d3..441d21b 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -173,7 +173,7 @@ def exclude_unwanted_producers(model):
     configuration. 
     """
 
-    nodes_to_ignore = ["Mul", "BitShift"]
+    nodes_to_ignore = ["Mul", "BitShift", "Clip"]
 
     for node in model.get_nodes():
         if node.type() == "Producer":
-- 
GitLab


From 81b628ed88b1f2ca36155fdfe37ef184d2d7be60 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:41:26 +0200
Subject: [PATCH 71/93] [Refactor] Adapt export scripts to the latest
 aidge_quantization update

---
 examples/export_LeNet/lenet.py       | 36 ++++++++++++++-------------
 examples/export_ResNet18/resnet18.py | 37 ++++++++++++++--------------
 2 files changed, 37 insertions(+), 36 deletions(-)

diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index 63411ab..c282655 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -22,7 +22,7 @@ from aidge_export_cpp.export_utils import (
     set_nodes_datatypes,
     exclude_unwanted_producers)
 
-from aidge_core.export_utils import remove_optional_inputs
+from aidge_core.export_utils import remove_optional_inputs, get_node_from_metaop
 
 # Torch (Dataset)
 import torch
@@ -89,8 +89,7 @@ Export configuration details :
 - OPTIM_SIGN :      Quantization optional optimization based on data sign. 
 - SINGLE_SHIFT :    Quantization option specifying if inserted scaling nodes should be
                         single shift or floating point.
-- ROUNDING :        Apply rounding on the data after the single shift step. 
-- NO_QUANTIZATION : Skip the quantization step.
+- NO_QUANT :        Skip the quantization step.
 - CLIPPING :        Clipping method during quantization. 
 - FOLD_GRAPH :      The quantization step adds cast nodes to cast the graph into the given TARGET_TYPE.
                         Enabling the FOLD_GRAPH will automatically fold these nodes into the following
@@ -136,7 +135,7 @@ DO_EXAMPLES   = True
 OPTIM_SIGN      = False
 SINGLE_SHIFT    = True
 ROUNDING        = True
-NO_QUANTIZATION = False  
+NO_QUANT = False  
 CLIPPING        = aidge_quantization.Clipping.MSE  # 'MAX'
 FOLD_GRAPH      = True
 
@@ -150,12 +149,11 @@ print(' NB_TEST          = ', NB_TEST)
 print(' NB_CALIB         = ', NB_CALIB)
 print(' NB_BITS          = ', NB_BITS)
 print(' OPTIM_SIGN       = ', OPTIM_SIGN)
-print(' NO_QUANTIZATION  = ', NO_QUANTIZATION)
+print(' NO_QUANT         = ', NO_QUANT)
 print(' CLIPPING         = ', CLIPPING)
 print(' SINGLE_SHIFT     = ', SINGLE_SHIFT)
 print(' USE_CUDA         = ', USE_CUDA)
 print(' DEV_MODE         = ', DEV_MODE)
-print(' ROUNDING         = ', ROUNDING)
 
 torch.manual_seed(RNG_SEED)
 random.seed(RNG_SEED)
@@ -207,11 +205,13 @@ for input, label in test_set:
 Load the .onnx model and perform some usual graph modifications :
     - Remove the flatten nodes;
     - Fuse the batchnorm nodes into the biases producers. 
+    - Expand the metaOperators to perform the desired fusions.
 """
 
 model = aidge_onnx.load_onnx(MODEL_NAME + ".onnx", verbose=False)
 aidge_core.remove_flatten(model)
 aidge_core.fuse_batchnorm(model)
+aidge_core.expand_metaops(model)
 model.save("imported_model")
 
 # --------------------------------------------------------------
@@ -269,15 +269,24 @@ if quantize_model:
     aidge_quantization.quantize_network(
         network = model, 
         nb_bits = NB_BITS, 
-        input_dataset = tensors[0:NB_CALIB], 
+        calibration_set = tensors[0:NB_CALIB], 
         clipping_mode = CLIPPING,
         target_type = TARGET_TYPE,
-        no_quantization = NO_QUANTIZATION,
+        no_quant = NO_QUANT,
         optimize_signs = OPTIM_SIGN, 
         single_shift = SINGLE_SHIFT, 
         use_cuda = USE_CUDA,
-        fold_graph = FOLD_GRAPH,
-        bitshift_rounding = ROUNDING)
+        fold_graph = FOLD_GRAPH)
+
+# Tag the scaling producers
+for node in model.get_nodes():
+    if node.type() == "Quantizer":
+        for SNode in get_node_from_metaop(node, "BitShift"):
+            SNode.get_parent(1).attributes().shift_prod = True
+        for CNode in get_node_from_metaop(node, "Mul"):
+            CNode.get_parent(1).attributes().coef_prod = True
+
+model.save("post_ptq_model")
 
 # --------------------------------------------------------------
 # RESCALE THE INPUT SAMPLES
@@ -358,13 +367,6 @@ In this step, we use graph regex techniques to find the desired patterns
 within the graph in order to match the export implementation of the kernels. 
 """
 
-# Expand meta ops
-"""
-We first need to expand the graph to break all the metaops that may already
-exist. For instance, PaddedConv will become Pad -> Conv. 
-"""
-aidge_core.expand_metaops(model)
-
 # Exclude unwanted producers 
 """
 Before fusing the nodes, we set a tag on the Producers in order to exclude
diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 354e474..3ba1ff4 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -27,7 +27,7 @@ from aidge_export_cpp.export_utils import (
     set_nodes_datatypes,
     normalize)
 
-from aidge_core.export_utils import remove_optional_inputs
+from aidge_core.export_utils import remove_optional_inputs, get_node_from_metaop
 
 # Torch (Dataset)
 import torch
@@ -94,8 +94,7 @@ Export configuration details :
 - OPTIM_SIGN :      Quantization optional optimization based on data sign. 
 - SINGLE_SHIFT :    Quantization option specifying if inserted scaling nodes should be
                         single shift or floating point.
-- ROUNDING :        Apply rounding on the data after the single shift step. 
-- NO_QUANTIZATION : Skip the quantization step. Should be set to False. 
+- NO_QUANT : Skip the quantization step. Should be set to False. 
 - CLIPPING :        Clipping method during quantization. 
 - FOLD_GRAPH :      The quantization step adds cast nodes to cast the graph into the given TARGET_TYPE.
                         Enabling the FOLD_GRAPH will automatically fold these nodes into the following
@@ -161,14 +160,13 @@ def print_cfg():
     print(' NB_CALIB         = ', NB_CALIB)
     print(' NB_BITS          = ', NB_BITS)
     print(' OPTIM_SIGN       = ', OPTIM_SIGN)
-    print(' NO_QUANTIZATION  = ', NO_QUANTIZATION)
+    print(' NO_QUANT         = ', NO_QUANT)
     print(' CLIPPING         = ', CLIPPING)
     print(' SINGLE_SHIFT     = ', SINGLE_SHIFT)
     print(' TARGET_TYPE      = ', TARGET_TYPE)
     print(' FOLD_GRAPH       = ', FOLD_GRAPH)
     print(' USE_CUDA         = ', USE_CUDA)
     print(' DEV_MODE         = ', DEV_MODE)
-    print(' ROUNDING         = ', ROUNDING)
 
 print_cfg()
 
@@ -239,12 +237,14 @@ for tensor in tensors:
 Load the .onnx model and perform some usual graph modifications :
     - Remove the flatten nodes;
     - Fuse the batchnorm nodes into the biases producers. 
+    - Expand the metaOperators to perform the desired fusions.
 """
 
 model = aidge_onnx.load_onnx(MODEL_NAME + ".onnx", verbose=False)
 model.save("imported_model")
 aidge_core.remove_flatten(model)
 aidge_core.fuse_batchnorm(model)
+aidge_core.expand_metaops(model)
 model.save("imported_model_fused_bn")
 
 # --------------------------------------------------------------
@@ -301,15 +301,24 @@ if quantize_model:
     aidge_quantization.quantize_network(
         network = model, 
         nb_bits = NB_BITS, 
-        input_dataset = aidge_tensors[0:NB_CALIB], 
+        calibration_set = aidge_tensors[0:NB_CALIB], 
         clipping_mode = CLIPPING,
         target_type = TARGET_TYPE,
-        no_quantization = NO_QUANTIZATION,
+        no_quant = NO_QUANT,
         optimize_signs = OPTIM_SIGN, 
         single_shift = SINGLE_SHIFT, 
         use_cuda = USE_CUDA,
-        fold_graph = FOLD_GRAPH,
-        bitshift_rounding = ROUNDING)
+        fold_graph = FOLD_GRAPH)
+
+# Tag the scaling producers
+for node in model.get_nodes():
+    if node.type() == "Quantizer":
+        for SNode in get_node_from_metaop(node, "BitShift"):
+            SNode.get_parent(1).attributes().shift_prod = True
+        for CNode in get_node_from_metaop(node, "Mul"):
+            CNode.get_parent(1).attributes().coef_prod = True
+
+model.save("post_ptq_model")
 
 # --------------------------------------------------------------
 # RESCALE THE INPUT SAMPLES 
@@ -382,16 +391,6 @@ In this step, we use graph regex techniques to find the desired patterns
 within the graph in order to match the export implementation of the kernels. 
 """
 
-# Expand meta ops
-"""
-We first need to expand the graph to break all the metaops that may already
-exist. For instance, PaddedConv will become Pad -> Conv. 
-"""
-aidge_core.expand_metaops(model)
-
-
-model.save("after_expand")
-
 # Exclude unwanted producers 
 """
 Before fusing the nodes, we set a tag on the Producers in order to exclude
-- 
GitLab


From 7a2c6f682942955b106565880289de0758dee062 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:42:42 +0200
Subject: [PATCH 72/93] [Chore] file name change

---
 aidge_export_cpp/operators/{Activation.py => ReLU.py} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename aidge_export_cpp/operators/{Activation.py => ReLU.py} (100%)

diff --git a/aidge_export_cpp/operators/Activation.py b/aidge_export_cpp/operators/ReLU.py
similarity index 100%
rename from aidge_export_cpp/operators/Activation.py
rename to aidge_export_cpp/operators/ReLU.py
-- 
GitLab


From 353df84000424d10a36c910139fce3eed115eed3 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:44:16 +0200
Subject: [PATCH 73/93] [Refactor] get_node_from_metaop now returns a list of
 all operators found

---
 aidge_export_cpp/operators/Conv.py      | 15 ++++++------
 aidge_export_cpp/operators/ElemWise.py  | 31 +++++++++----------------
 aidge_export_cpp/operators/Fc.py        | 12 ++++------
 aidge_export_cpp/operators/Pool.py      | 17 +++++++-------
 aidge_export_cpp/operators/Quantizer.py | 14 ++++++-----
 5 files changed, 39 insertions(+), 50 deletions(-)

diff --git a/aidge_export_cpp/operators/Conv.py b/aidge_export_cpp/operators/Conv.py
index e1d17bc..c8137c5 100644
--- a/aidge_export_cpp/operators/Conv.py
+++ b/aidge_export_cpp/operators/Conv.py
@@ -18,9 +18,9 @@ class Conv(ExportNodeCpp):
 
         # Browse the metaop to update kernel attributes
         ConvNode = get_node_from_metaop(node, "Conv2D") 
-        self.attributes["kernel_dims"] = ConvNode.get_operator().attr.kernel_dims
-        self.attributes["stride_dims"] = ConvNode.get_operator().attr.stride_dims
-        self.attributes["dilation_dims"] = ConvNode.get_operator().attr.dilation_dims
+        self.attributes["kernel_dims"] = ConvNode[0].get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = ConvNode[0].get_operator().attr.stride_dims
+        self.attributes["dilation_dims"] = ConvNode[0].get_operator().attr.dilation_dims
 
         # Template for layer configutation file generation
         self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
@@ -61,7 +61,7 @@ class PadConv(QConv):
 
         # Browse the metaop to update kernel attributes
         PadNode = get_node_from_metaop(node, "Pad2D")
-        self.attributes["padding"] = PadNode.get_operator().attr.begin_end_borders
+        self.attributes["padding"] = PadNode[0].get_operator().attr.begin_end_borders
 
 
 @ExportLibCpp.register_metaop("ConvAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -70,11 +70,10 @@ class ConvAct(QConv):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node_from_metaop(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU"):
             self.attributes["activation"] = "Rectifier"
-        elif get_node_from_metaop(node, "LeakyReLU") is not None:
-            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
-            # TODO : Should not be checked manually for each activation     
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.")
 
 @ExportLibCpp.register_metaop("PadConvAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class PadConvAct(PadConv, ConvAct):
diff --git a/aidge_export_cpp/operators/ElemWise.py b/aidge_export_cpp/operators/ElemWise.py
index 4603509..7d073ca 100644
--- a/aidge_export_cpp/operators/ElemWise.py
+++ b/aidge_export_cpp/operators/ElemWise.py
@@ -66,11 +66,10 @@ class AddAct(QAdd):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node_from_metaop(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU"):
             self.attributes["activation"] = "Rectifier"
-        elif get_node_from_metaop(node, "LeakyReLU") is not None:
-            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
-            # TODO : Should not be checked manually for each activation    
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.") 
 
 
 @ExportLibCpp.register("Sub", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -92,34 +91,26 @@ class SubAct(QSub):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node_from_metaop(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU"):
             self.attributes["activation"] = "Rectifier"
-        elif get_node_from_metaop(node, "LeakyReLU") is not None:
-            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
-            # TODO : Should not be checked manually for each activation    
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.") 
 
 
 @ExportLibCpp.register("Mul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class Mul(ElemWise):
+class Mul(QElemWise):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
         self.attributes["elemwise_op"] = "Mul"
 
 
-@ExportLibCpp.register_metaop("QMul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class QMul(QElemWise, Mul):
-    def __init__(self, node, mem_info):
-        super().__init__(node, mem_info)
-
-
 @ExportLibCpp.register_metaop("MulAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class MulAct(QMul):
+class MulAct(Mul):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node_from_metaop(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU"):
             self.attributes["activation"] = "Rectifier"
-        elif get_node_from_metaop(node, "LeakyReLU") is not None:
-            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
-            # TODO : Should not be checked manually for each activation    
\ No newline at end of file
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.")
\ No newline at end of file
diff --git a/aidge_export_cpp/operators/Fc.py b/aidge_export_cpp/operators/Fc.py
index b83754b..d32d20e 100644
--- a/aidge_export_cpp/operators/Fc.py
+++ b/aidge_export_cpp/operators/Fc.py
@@ -1,5 +1,5 @@
 import aidge_core
-from aidge_core.export_utils import ExportNodeCpp
+from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register("FC", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -53,9 +53,7 @@ class FCAct(QFC):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        for n in node.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "ReLU":
-                self.attributes["activation"] = "Rectifier"
-            elif n.type() == "LeakyReLU":
-                aidge_core.Log.fatal(f"{n.type()} activation is not yet supported.")
-                # TODO : Should not be checked manually for each activation
+        if get_node_from_metaop(node, "ReLU"):
+            self.attributes["activation"] = "Rectifier"
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.")
diff --git a/aidge_export_cpp/operators/Pool.py b/aidge_export_cpp/operators/Pool.py
index bf7e7eb..10d595e 100644
--- a/aidge_export_cpp/operators/Pool.py
+++ b/aidge_export_cpp/operators/Pool.py
@@ -38,7 +38,7 @@ class PadPool(Pool):
 
         # Browse the metaop to update kernel attributes
         PadNode = get_node_from_metaop(node, "Pad2D")
-        self.attributes["padding"] = PadNode.get_operator().attr.begin_end_borders
+        self.attributes["padding"] = PadNode[0].get_operator().attr.begin_end_borders
 
 
 class PoolAct(Pool):
@@ -46,11 +46,10 @@ class PoolAct(Pool):
         super().__init__(node, mem_info)
 
         # Browse the metaop to update kernel attributes
-        if get_node_from_metaop(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU"):
             self.attributes["activation"] = "Rectifier"
-        elif get_node_from_metaop(node, "LeakyReLU") is not None:
-            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
-            # TODO : Should not be checked manually for each activation    
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.")  
 
 
 @ExportLibCpp.register("MaxPooling2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -61,8 +60,8 @@ class MaxPool(Pool):
         # Browse the metaop to update kernel attributes
         PoolNode = get_node_from_metaop(node, "MaxPooling2D")
         self.attributes["pool_type"] = "Max"
-        self.attributes["kernel_dims"] = PoolNode.get_operator().attr.kernel_dims
-        self.attributes["stride_dims"] = PoolNode.get_operator().attr.stride_dims
+        self.attributes["kernel_dims"] = PoolNode[0].get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = PoolNode[0].get_operator().attr.stride_dims
 
 
 @ExportLibCpp.register_metaop("PadMaxPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
@@ -91,8 +90,8 @@ class AvgPool(Pool):
         # Browse the metaop to update kernel attributes
         PoolNode = get_node_from_metaop(node, "AvgPooling2D")
         self.attributes["pool_type"] = "Average"
-        self.attributes["kernel_dims"] = PoolNode.get_operator().attr.kernel_dims
-        self.attributes["stride_dims"] = PoolNode.get_operator().attr.stride_dims
+        self.attributes["kernel_dims"] = PoolNode[0].get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = PoolNode[0].get_operator().attr.stride_dims
 
 
 @ExportLibCpp.register_metaop("PadAvgPool", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
diff --git a/aidge_export_cpp/operators/Quantizer.py b/aidge_export_cpp/operators/Quantizer.py
index a33bc14..51f5c23 100644
--- a/aidge_export_cpp/operators/Quantizer.py
+++ b/aidge_export_cpp/operators/Quantizer.py
@@ -3,7 +3,7 @@ from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
 @ExportLibCpp.register_metaop("Quantizer", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
-class CppRescaling(ExportNodeCpp):
+class Quantizer(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
 
@@ -15,11 +15,8 @@ class CppRescaling(ExportNodeCpp):
         self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
 
         # Browse the metaop to update kernel attributes
-        if get_node_from_metaop(node, "ReLU") is not None:
+        if get_node_from_metaop(node, "ReLU"):
             self.attributes["activation"] = "Rectifier"
-        elif get_node_from_metaop(node, "LeakyReLU") is not None:
-            aidge_core.Log.fatal(f"{node.type()} activation is not yet supported.")
-            # TODO : Should not be checked manually for each activation     
         
         # Set scaling attributes
         set_scaling_attributes(self, node)
@@ -45,4 +42,9 @@ class CppRescaling(ExportNodeCpp):
         # Include aidge outputs within the fwd file
         if self.attributes["aidge_cmp"]:
             self.include_list.append("network/utils.hpp")   # aidge_cmp function
-            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
\ No newline at end of file
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp")
+
+@ExportLibCpp.register_metaop("QMul", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QMul(Quantizer):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
-- 
GitLab


From 0af171cf5ac4a302e352fcdb97f6c84ed99c7667 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:45:49 +0200
Subject: [PATCH 74/93] [Refactor] Adapt the way to set the scaling attributes,
 datatypes and node names

---
 aidge_export_cpp/export_utils.py | 55 +++++++++++++++++++-------------
 1 file changed, 33 insertions(+), 22 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 441d21b..ac59017 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -85,10 +85,12 @@ def set_nodes_names(scheduler):
 
     node_ids = {}   # Dict holding the node type along with a counter
     node_it = 0     # Node Iterator
+
+    ## MetaOps
     for node in scheduler.get_sequential_static_scheduling():
         node_type = node.type()
 
-        if node_type != "Producer":     # Producers are 
+        if node_type != "Producer":
             if node.type() not in node_ids:
                 node_ids[node_type] = 0
 
@@ -99,23 +101,27 @@ def set_nodes_names(scheduler):
             node_it += 1
 
             # Set producers names
-            if node_type in ["QConv", "PadConv", "ConvAct", "PadConvAct", "QFC", "FCAct"]:
-                # nb_parents = len(node.get_parents())
+            ## Weights & Biases producers
+            if get_node_from_metaop(node, "FC") or \
+               get_node_from_metaop(node, "Conv2D") or \
+               get_node_from_metaop(node, "ConvDepthWise2D"):
+                
                 node.get_parent(1).set_name(node.name() + "_weights")
                 if node.get_parent(2) is not None:
                     node.get_parent(2).set_name(node.name() + "_biases")  
 
-            for parent_node in node.get_parents():
-                if parent_node is not None:
-                    # [TODO] Does not work yet
-                    # if parent_node.attributes().has_attr("quantization.ptq.CompensationCoeff"):
-                    #     parent_node.set_name(node.name() + "_coeff")
-                    if parent_node.attributes().has_attr("quantization.ptq.ShiftAmount"):
-                        parent_node.set_name(node.name() + "_shift")
-                    # [Fix] Add scaling/add coeff nodes manually
-                    elif node.type() in ["CppElemWise", ""] and parent_node.type() == "Producer":
-                        parent_node.set_name(node.name() + "_coeff")
-                    # [End Fix]
+    ## Scaling Producers
+    for node in scheduler.get_sequential_static_scheduling():
+        """
+        TODO: If multiple quantizer nodes are found, the producers will
+        all have the same name and this will not work properly. 
+        """
+        if node.type() == "Producer":
+            child_node = node.output(0)[0][0]
+            if node.attributes().has_attr("shift_prod"):
+                node.set_name(child_node.name() + "_shift")
+            if node.attributes().has_attr("coef_prod"):
+                node.set_name(child_node.name() + "_coef")
 
 
 
@@ -124,13 +130,17 @@ def set_nodes_datatypes(graph_view: aidge_core.GraphView):
 
     The set_datatype function can't be used on Conv2D and FC nodes directly
     as the biases datatype is different from the other inputs. 
+    TODO: Should be using forward_datatype()
 
     :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
                        ordered input/output data within the computational graph.
     """
     for node in graph_view.get_nodes():
         if node.type() != "Producer":
-            if node.type() in ["QConv", "PadConv", "ConvAct", "PadConvAct", "QFC", "FCAct"]:
+            if get_node_from_metaop(node, "FC") or \
+               get_node_from_metaop(node, "Conv2D") or \
+               get_node_from_metaop(node, "ConvDepthWise2D"):
+
                 node.get_operator().get_input(0).set_datatype(aidge_core.dtype.int8)    # Input
                 node.get_operator().get_input(1).set_datatype(aidge_core.dtype.int8)    # Weights
                 if node.get_parent(2) is not None:
@@ -199,13 +209,14 @@ def set_scaling_attributes(export_node: aidge_core.export_utils.ExportNode, node
     """
 
     QNode = get_node_from_metaop(node, "Quantizer")
-
-    if QNode is not None:
-        for n in QNode.get_operator().get_micro_graph().get_nodes():
-            if n.type() == "BitShift":
-                export_node.attributes["shift_value"] = n.get_operator().get_input(1)[0]
-            elif n.type() == "Mul":
-                export_node.attributes["coef_value"] = n.get_operator().get_input(1)[0]
+    if QNode:
+        BNode = get_node_from_metaop(QNode[0], "BitShift")
+        export_node.attributes["shift_value"] = BNode[0].get_operator().get_input(1)[0]
+
+    QMulNode = get_node_from_metaop(node, "QMul")
+    if QMulNode:
+        CNode = get_node_from_metaop(QMulNode[0], "Mul")
+        export_node.attributes["coef_value"] = CNode[0].get_operator().get_input(1)[0]
 
       
             
-- 
GitLab


From 8ae970f2bef7b12b8e2ddcde4620fa562ae4c1ce Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:46:30 +0200
Subject: [PATCH 75/93] [Chore] Remove the LeakyReLU from the recipes as it is
 not yet supported by the export

---
 aidge_export_cpp/export_utils.py | 49 +++++++++++++-------------------
 1 file changed, 20 insertions(+), 29 deletions(-)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index ac59017..7830800 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -7,60 +7,51 @@ from aidge_core.export_utils import get_node_from_metaop
 def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
     """ 
     Fuse nodes into metaops adapted for the CPP Export
+    TODO: These recipes should be into aidge_core
 
     :param graph_view: An instance of :py:class:`aidge_core.GraphView`, providing access to nodes and
                        ordered input/output data within the computational graph.
     """
 
-    # cpp_recipes = OrderedDict({
-    #     "Quantizer":      "BitShift#->Clip; BitShift#<-Mul?",    # Scaling node created by the quantization
-    #     "CppFc":          "FC->Quantizer?->ReLU?",
-    #     "CppConv":        "Conv2D#->Quantizer?->ReLU?; Conv2D#<-Pad2D?",
-    #     "CppPool":        "(MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)->ReLU?; (MaxPooling2D#|AvgPooling2D#|GlobalAveragePooling#)<-Pad2D?",
-    #     "CppElemWise":    "(Add|Mul|Sub)->Quantizer?->ReLU?",
-    #     "CppActivation":  "ReLU"
-    # })
-
     cpp_recipes = OrderedDict({
         # Quantization
-        "Quantizer":      "BitShift#->Clip; BitShift#<-Mul?",
+        "QMul":           "Mul->Quantizer",     # Fixed Point Scaling
 
         # FC
-        "QFC":            "FC->Quantizer",
-        "FCAct":          "(FC|QFC)->(ReLU|LeakyReLU)",
+        "QFC":            "FC->(Quantizer|QMul)",
+        "FCAct":          "(FC|QFC)->ReLU",
 
         # Conv
-        "QConv":          "Conv2D->Quantizer",
+        "QConv":          "Conv2D->(Quantizer|QMul)",
         "PadConv":        "(QConv|Conv2D)<-Pad2D",
-        "ConvAct":        "(QConv|Conv2D)->(ReLU|LeakyReLU)",
-        "PadConvAct":     "PadConv->(ReLU|LeakyReLU)",
+        "ConvAct":        "(QConv|Conv2D)->ReLU",
+        "PadConvAct":     "PadConv->ReLU",
 
         # Max Pooling
         "PadMaxPool":     "MaxPooling2D<-Pad2D",
-        "MaxPoolAct":     "MaxPooling2D->(ReLU|LeakyReLU)",
-        "PadMaxPoolAct":  "PadMaxPool->(ReLU|LeakyReLU)",
+        "MaxPoolAct":     "MaxPooling2D->ReLU",
+        "PadMaxPoolAct":  "PadMaxPool->ReLU",
 
         # Average Pooling
         "PadAvgPool":     "AvgPooling2D<-Pad2D",
-        "AvgPoolAct":     "AvgPooling2D->(ReLU|LeakyReLU)",
-        "PadAvgPoolAct":  "PadAvgPool->(ReLU|LeakyReLU)",
+        "AvgPoolAct":     "AvgPooling2D->ReLU",
+        "PadAvgPoolAct":  "PadAvgPool->ReLU",
 
         # Global Average Pooling
         "PadGlobalAvgPool":     "GlobalAveragePooling2D<-Pad2D",
-        "GlobalAvgPoolAct":     "GlobalAveragePooling2D->(ReLU|LeakyReLU)",
-        "PadGlobalAvgPoolAct":  "PadGlobalAveragePool->(ReLU|LeakyReLU)",
+        "GlobalAvgPoolAct":     "GlobalAveragePooling2D->ReLU",
+        "PadGlobalAvgPoolAct":  "PadGlobalAveragePool->ReLU",
 
         # ElemWise
-        "QAdd":      "Add->Quantizer",
-        "QSub":      "Sub->Quantizer",
-        "QMul":      "Mul->Quantizer",
-        "AddAct":    "(QAdd|Add)->(ReLU|LeakyReLU)",
-        "SubAct":    "(QSub|Sub)->(ReLU|LeakyReLU)",
-        "MulAct":    "(QMul|Mul)->(ReLU|LeakyReLU)",
+        "QAdd":      "Add->(Quantizer|QMul)",
+        "QSub":      "Sub->(Quantizer|QMul)",
+        # "QMul":    "Mul->Quantizer",      # Already defined
+        "AddAct":    "(QAdd|Add)->ReLU",
+        "SubAct":    "(QSub|Sub)->ReLU",
+        "MulAct":    "(QMul|Mul)->ReLU",
 
         # Activation
-        "QReLU":        "ReLU->Quantizer",
-        "QLeakyReLU":   "LeakyReLU->Quantizer",
+        "QReLU":        "ReLU->(Quantizer|QMul)",
     })
 
     for node, recipe in cpp_recipes.items():
-- 
GitLab


From ed816aa0c58bc8e2fc9c58da129984f4fdfd1ed9 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:47:16 +0200
Subject: [PATCH 76/93] [Fix] The normalize function was dividing by 0 when min
 == max

---
 aidge_export_cpp/export_utils.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 7830800..4cbc38d 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -215,6 +215,8 @@ def normalize(array):
     """
     Normalize an input image between -1 and 1
     """
+    if array.max() == array.min():
+        return array/array.max()
     array = (array - array.min()) / (array.max() - array.min())
     return 2 * array - 1
 
-- 
GitLab


From 3a0f11abb650d7d13a230d118ebc76159b79757b Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:48:21 +0200
Subject: [PATCH 77/93] [Fix] Manually set the input after the quantzation step

---
 examples/export_ResNet18/resnet18.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 3ba1ff4..935cf96 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -140,7 +140,7 @@ DO_EXAMPLES     = True
 OPTIM_SIGN      = False   
 SINGLE_SHIFT    = True    
 ROUNDING        = True 
-NO_QUANTIZATION = False
+NO_QUANT        = False
 CLIPPING        = aidge_quantization.Clipping.MSE  # 'MAX'
 FOLD_GRAPH      = True
 
@@ -345,7 +345,13 @@ Each time the graph has been change, it has to be reset.
 Here some Quantizer and Cast nodes have been added. 
 """
 
+""" [Issue]
+We need first to manually add an input tensor with the correct datatype, 
+as it is not automatically done in PTQ. 
+"""
 if quantize_model:
+    input_node = model.get_ordered_inputs()[0]
+    input_node[0].get_operator().set_input(0, aidge_tensors[0])
     scheduler.reset_scheduling()
 
 # --------------------------------------------------------------
-- 
GitLab


From 5dfb53e6d59e68e558e0127d1d49e49347899308 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:48:44 +0200
Subject: [PATCH 78/93] [Chore](R18) Round the tensor after rescaling

---
 examples/export_ResNet18/resnet18.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 935cf96..e39daf8 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -333,6 +333,7 @@ if quantize_model:
     rescaling = 2**(NB_BITS-1)-1
     for i in range(max(NB_TEST, NB_CALIB)):
         array = np.array(aidge_tensors[i]) * rescaling 
+        array = np.round(array).astype(int)
         aidge_tensors[i] = aidge_core.Tensor(array)
         aidge_tensors[i].set_datatype(TARGET_TYPE)
 
-- 
GitLab


From 55dbb02dfcb1f94cd5256d9e6d9eebcbe369d007 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:49:18 +0200
Subject: [PATCH 79/93] [Chore](R18) Perform an inference to set the
 intermediate tensors to export

---
 examples/export_ResNet18/resnet18.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index e39daf8..f60e68a 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -376,6 +376,8 @@ if (DO_EXAMPLES and quantize_model):
     print('\n MODEL ACCURACY = ', accuracy * 100, '%')
     print('\n QUANTIZED ACCURACY = ', quant_accuracy * 100, '%')
 
+    output_array = propagate(model, scheduler, aidge_tensors[0])
+
 if USE_CUDA:    
     model.set_backend("cpu")
     for aidge_tensor in aidge_tensors:
-- 
GitLab


From 6d4d7fb0b58a72563849e0c3bfcd88d2454ee3dc Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 13:49:42 +0200
Subject: [PATCH 80/93] [Refactor](R18) Adapt to new aidge_cmp flag system

---
 examples/export_ResNet18/resnet18.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index f60e68a..64b3a9b 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -502,8 +502,9 @@ This option has to be passed to each node in order to be used within the Export
 (JConv, JPad, ...) that you can find in the "export_gen/operator_export" folder. 
 """
 
-for node in model.get_nodes():
-    node.attributes().aidge_cmp = AIDGE_CMP
+if AIDGE_CMP:
+    for node in model.get_nodes():
+        node.attributes().aidge_cmp = True
 
 # --------------------------------------------------------------
 # EXPORT THE MODEL
-- 
GitLab


From 691bd39deeee891d77d2b45940b1fe4b35bd76e0 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 14:20:01 +0200
Subject: [PATCH 81/93] [Refactor] Adapt ConvDw to the new export system

---
 aidge_export_cpp/export_utils.py              |   6 +
 aidge_export_cpp/kernels/convolution.hpp      | 154 ----------------
 .../kernels/convolution_depthwise.hpp         | 164 ++++++++++++++++++
 aidge_export_cpp/operators/ConvDw.py          |  82 +++++++++
 .../configuration/convolution_config.jinja    |   3 +-
 5 files changed, 254 insertions(+), 155 deletions(-)
 create mode 100644 aidge_export_cpp/kernels/convolution_depthwise.hpp
 create mode 100644 aidge_export_cpp/operators/ConvDw.py

diff --git a/aidge_export_cpp/export_utils.py b/aidge_export_cpp/export_utils.py
index 4cbc38d..e22524f 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -27,6 +27,12 @@ def cpp_fuse_to_metaops(graph_view: aidge_core.GraphView):
         "ConvAct":        "(QConv|Conv2D)->ReLU",
         "PadConvAct":     "PadConv->ReLU",
 
+        # ConvDw
+        "QConvDw":          "ConvDepthWise2D->(Quantizer|QMul)",
+        "ConvDwPad":        "(QConvDw|ConvDepthWise2D)->Pad2D",
+        "ConvDwAct":        "(QConvDw|ConvConvDepthWise2D2D)->ReLU",
+        "ConvDwActPad":     "ConvDwAct->Pad2D",
+
         # Max Pooling
         "PadMaxPool":     "MaxPooling2D<-Pad2D",
         "MaxPoolAct":     "MaxPooling2D->ReLU",
diff --git a/aidge_export_cpp/kernels/convolution.hpp b/aidge_export_cpp/kernels/convolution.hpp
index 0648d80..ed62401 100644
--- a/aidge_export_cpp/kernels/convolution.hpp
+++ b/aidge_export_cpp/kernels/convolution.hpp
@@ -159,158 +159,4 @@ void convolution_forward(
                         (inputs, outputs, weights, b, rescaling);
 }
 
-template<int NB_CHANNELS, 
-         int CHANNELS_HEIGHT, int CHANNELS_WIDTH,
-         int NB_OUTPUTS,
-         int OUTPUTS_HEIGHT, int OUTPUTS_WIDTH,
-         int PADDING_Y, int PADDING_X,
-         int STRIDE_Y, int STRIDE_X,
-         int DILATION_Y, int DILATION_X,
-         int KERNEL_HEIGHT, int KERNEL_WIDTH,
-         ActivationFunction_T ACTIVATION,
-         typename Input_T, typename Output_T,
-         typename Weight_T, typename Bias_T,
-         typename Rescaling_T>
-__attribute__((always_inline)) inline
-void convolution_depthwise_forward(
-    const Input_T* __restrict inputs,
-    Output_T* __restrict outputs,
-    const Weight_T* __restrict weights,
-    const Bias_T* __restrict biases,
-    const Rescaling_T& __restrict rescaling)
-{
-    static_assert(NB_OUTPUTS % NB_CHANNELS == 0,
-        "NB_OUTPUTS should be a multiple of NB_CHANNELS.");
-
-    constexpr int DILATED_KERNEL_HEIGHT 
-            = KERNEL_HEIGHT + (DILATION_Y - 1) * (KERNEL_HEIGHT - 1);
-
-    constexpr int DILATED_KERNEL_WIDTH 
-            = KERNEL_WIDTH + (DILATION_X - 1) * (KERNEL_WIDTH - 1);
-
-    constexpr int OUTPUTS_HEIGHT_NOPAD
-        = (CHANNELS_HEIGHT - DILATION_Y * (KERNEL_HEIGHT - 1) - 1 + STRIDE_Y) / STRIDE_Y;
-    constexpr int OUTPUTS_WIDTH_NOPAD
-        = (CHANNELS_WIDTH - DILATION_X * (KERNEL_WIDTH - 1) - 1 + STRIDE_X) / STRIDE_X;
-
-    for (int oy = 0; oy < OUTPUTS_HEIGHT; ++oy) {
-        const int syMin = (PADDING_Y == 0) ? 0
-            : max(PADDING_Y - (oy * STRIDE_Y), 0);
-        const int syMax = (PADDING_Y == 0
-                && OUTPUTS_HEIGHT == OUTPUTS_HEIGHT_NOPAD) ? DILATED_KERNEL_HEIGHT
-            : clamp(CHANNELS_HEIGHT + PADDING_Y - (oy * STRIDE_Y), 
-                    0, DILATED_KERNEL_HEIGHT);
-        const int iy = (oy * STRIDE_Y) - PADDING_Y;
-
-#ifdef _OPENMP
-#pragma omp parallel for collapse(2)
-#endif
-        for (int ox = 0; ox < OUTPUTS_WIDTH; ++ox) {
-            for (int output = 0; output < NB_OUTPUTS; ++output) {
-                // moved to inner loop for collapsing -->
-                const int sxMin = (PADDING_X == 0) ? 0
-                    : max(PADDING_X - (ox * STRIDE_X), 0);
-                const int sxMax = (PADDING_X == 0
-                        && OUTPUTS_WIDTH == OUTPUTS_WIDTH_NOPAD)
-                            ? DILATED_KERNEL_WIDTH
-                    : clamp(CHANNELS_WIDTH + PADDING_X - (ox * STRIDE_X), 
-                            0, DILATED_KERNEL_WIDTH);
-                const int ix = (ox * STRIDE_X) - PADDING_X;
-
-                const int oPos = (ox + OUTPUTS_WIDTH * oy);
-                const int oOffset = NB_OUTPUTS * oPos;
-                // <--
-
-                const int channel = (output * NB_CHANNELS) / NB_OUTPUTS;
-
-                Bias_T weightedSum = biases ? biases[output] : 0;
-
-                for (int sy = 0; sy < KERNEL_HEIGHT; ++sy) {
-                    if ((PADDING_Y != 0
-                            || OUTPUTS_HEIGHT != OUTPUTS_HEIGHT_NOPAD)
-                        && ((sy*DILATION_Y < syMin) || (sy*DILATION_Y >= syMax)))
-                    {
-                        continue;
-                    }
-
-                    const int iPos = ix + CHANNELS_WIDTH * (iy + sy*DILATION_Y);
-                    const int iOffset = NB_CHANNELS * iPos;
-
-                    const int wOffset = (output*KERNEL_HEIGHT + sy) 
-                                        * KERNEL_WIDTH;
-
-                    if (DILATION_X == 1 && ((PADDING_X == 0
-                            && OUTPUTS_WIDTH == OUTPUTS_WIDTH_NOPAD)
-                        || sxMax - sxMin == KERNEL_WIDTH))
-                    {
-                        macsOnRange<KERNEL_WIDTH, NB_CHANNELS>(
-                            inputs + iOffset + channel, 
-                            weights + wOffset, 
-                            weightedSum);
-                    }
-                    else {
-                        for (int sx = 0; sx < KERNEL_WIDTH; ++sx) {
-                            if ((PADDING_X != 0
-                                    || OUTPUTS_WIDTH != OUTPUTS_WIDTH_NOPAD)
-                                && ((sx*DILATION_X < sxMin) || (sx*DILATION_X >= sxMax)))
-                            {
-                                continue;
-                            }
-
-                            const int iOffsetInRange = iOffset
-                                + sx * DILATION_X * NB_CHANNELS;
-
-                            weightedSum += inputs[iOffsetInRange + channel]
-                                * weights[wOffset + sx];
-                        }
-                    }
-                }
-
-                outputs[oOffset + output] = activation_forward_value<Output_T>(weightedSum, output, ACTIVATION, rescaling);
-            }
-        }
-    }
-}
-
-// Template specialization when biases are not given to the convolution
-template<int NB_CHANNELS,
-         int CHANNELS_HEIGHT, int CHANNELS_WIDTH,
-         int NB_OUTPUTS,
-         int OUTPUTS_HEIGHT, int OUTPUTS_WIDTH,
-         int PADDING_Y, int PADDING_X,
-         int STRIDE_Y, int STRIDE_X,
-         int DILATION_Y, int DILATION_X,
-         int KERNEL_HEIGHT, int KERNEL_WIDTH,
-         ActivationFunction_T ACTIVATION,
-         typename Input_T, typename Output_T,
-         typename Weight_T,
-         typename Rescaling_T>
-__attribute__((always_inline)) inline
-void convolution_depthwise_forward(
-    const Input_T* __restrict inputs,
-    Output_T* __restrict outputs,
-    const Weight_T* __restrict weights,
-    std::nullptr_t __restrict,
-    const Rescaling_T& __restrict rescaling)
-{
-    const float* b = nullptr;
-
-    convolution_depthwise_forward<NB_CHANNELS,
-                        CHANNELS_HEIGHT,
-                        CHANNELS_WIDTH,
-                        NB_OUTPUTS,
-                        OUTPUTS_HEIGHT,
-                        OUTPUTS_WIDTH,
-                        PADDING_Y,
-                        PADDING_X,
-                        STRIDE_Y,
-                        STRIDE_X,
-                        DILATION_Y,
-                        DILATION_X,
-                        KERNEL_HEIGHT,
-                        KERNEL_WIDTH,
-                        ACTIVATION>
-                        (inputs, outputs, weights, b, rescaling);
-}
-
 #endif  // __AIDGE_EXPORT_CPP_KERNELS_CONVOLUTION__
diff --git a/aidge_export_cpp/kernels/convolution_depthwise.hpp b/aidge_export_cpp/kernels/convolution_depthwise.hpp
new file mode 100644
index 0000000..244dd86
--- /dev/null
+++ b/aidge_export_cpp/kernels/convolution_depthwise.hpp
@@ -0,0 +1,164 @@
+#ifndef __AIDGE_EXPORT_CPP_KERNELS_CONVOLUTION_DEPTHWISE__
+#define __AIDGE_EXPORT_CPP_KERNELS_CONVOLUTION_DEPTHWISE__
+
+#include "network/typedefs.hpp"
+#include "network/rescaling_utils.hpp"
+#include "network/utils.hpp"
+#include "network/macs.hpp"
+#include "network/activation_utils.hpp"
+
+template<int NB_CHANNELS, 
+         int CHANNELS_HEIGHT, int CHANNELS_WIDTH,
+         int NB_OUTPUTS,
+         int OUTPUTS_HEIGHT, int OUTPUTS_WIDTH,
+         int PADDING_Y, int PADDING_X,
+         int STRIDE_Y, int STRIDE_X,
+         int DILATION_Y, int DILATION_X,
+         int KERNEL_HEIGHT, int KERNEL_WIDTH,
+         ActivationFunction_T ACTIVATION,
+         typename Input_T, typename Output_T,
+         typename Weight_T, typename Bias_T,
+         typename Rescaling_T>
+__attribute__((always_inline)) inline
+void convolution_depthwise_forward(
+    const Input_T* __restrict inputs,
+    Output_T* __restrict outputs,
+    const Weight_T* __restrict weights,
+    const Bias_T* __restrict biases,
+    const Rescaling_T& __restrict rescaling)
+{
+    static_assert(NB_OUTPUTS % NB_CHANNELS == 0,
+        "NB_OUTPUTS should be a multiple of NB_CHANNELS.");
+
+    constexpr int DILATED_KERNEL_HEIGHT 
+            = KERNEL_HEIGHT + (DILATION_Y - 1) * (KERNEL_HEIGHT - 1);
+
+    constexpr int DILATED_KERNEL_WIDTH 
+            = KERNEL_WIDTH + (DILATION_X - 1) * (KERNEL_WIDTH - 1);
+
+    constexpr int OUTPUTS_HEIGHT_NOPAD
+        = (CHANNELS_HEIGHT - DILATION_Y * (KERNEL_HEIGHT - 1) - 1 + STRIDE_Y) / STRIDE_Y;
+    constexpr int OUTPUTS_WIDTH_NOPAD
+        = (CHANNELS_WIDTH - DILATION_X * (KERNEL_WIDTH - 1) - 1 + STRIDE_X) / STRIDE_X;
+
+    for (int oy = 0; oy < OUTPUTS_HEIGHT; ++oy) {
+        const int syMin = (PADDING_Y == 0) ? 0
+            : max(PADDING_Y - (oy * STRIDE_Y), 0);
+        const int syMax = (PADDING_Y == 0
+                && OUTPUTS_HEIGHT == OUTPUTS_HEIGHT_NOPAD) ? DILATED_KERNEL_HEIGHT
+            : clamp(CHANNELS_HEIGHT + PADDING_Y - (oy * STRIDE_Y), 
+                    0, DILATED_KERNEL_HEIGHT);
+        const int iy = (oy * STRIDE_Y) - PADDING_Y;
+
+#ifdef _OPENMP
+#pragma omp parallel for collapse(2)
+#endif
+        for (int ox = 0; ox < OUTPUTS_WIDTH; ++ox) {
+            for (int output = 0; output < NB_OUTPUTS; ++output) {
+                // moved to inner loop for collapsing -->
+                const int sxMin = (PADDING_X == 0) ? 0
+                    : max(PADDING_X - (ox * STRIDE_X), 0);
+                const int sxMax = (PADDING_X == 0
+                        && OUTPUTS_WIDTH == OUTPUTS_WIDTH_NOPAD)
+                            ? DILATED_KERNEL_WIDTH
+                    : clamp(CHANNELS_WIDTH + PADDING_X - (ox * STRIDE_X), 
+                            0, DILATED_KERNEL_WIDTH);
+                const int ix = (ox * STRIDE_X) - PADDING_X;
+
+                const int oPos = (ox + OUTPUTS_WIDTH * oy);
+                const int oOffset = NB_OUTPUTS * oPos;
+                // <--
+
+                const int channel = (output * NB_CHANNELS) / NB_OUTPUTS;
+
+                Bias_T weightedSum = biases ? biases[output] : 0;
+
+                for (int sy = 0; sy < KERNEL_HEIGHT; ++sy) {
+                    if ((PADDING_Y != 0
+                            || OUTPUTS_HEIGHT != OUTPUTS_HEIGHT_NOPAD)
+                        && ((sy*DILATION_Y < syMin) || (sy*DILATION_Y >= syMax)))
+                    {
+                        continue;
+                    }
+
+                    const int iPos = ix + CHANNELS_WIDTH * (iy + sy*DILATION_Y);
+                    const int iOffset = NB_CHANNELS * iPos;
+
+                    const int wOffset = (output*KERNEL_HEIGHT + sy) 
+                                        * KERNEL_WIDTH;
+
+                    if (DILATION_X == 1 && ((PADDING_X == 0
+                            && OUTPUTS_WIDTH == OUTPUTS_WIDTH_NOPAD)
+                        || sxMax - sxMin == KERNEL_WIDTH))
+                    {
+                        macsOnRange<KERNEL_WIDTH, NB_CHANNELS>(
+                            inputs + iOffset + channel, 
+                            weights + wOffset, 
+                            weightedSum);
+                    }
+                    else {
+                        for (int sx = 0; sx < KERNEL_WIDTH; ++sx) {
+                            if ((PADDING_X != 0
+                                    || OUTPUTS_WIDTH != OUTPUTS_WIDTH_NOPAD)
+                                && ((sx*DILATION_X < sxMin) || (sx*DILATION_X >= sxMax)))
+                            {
+                                continue;
+                            }
+
+                            const int iOffsetInRange = iOffset
+                                + sx * DILATION_X * NB_CHANNELS;
+
+                            weightedSum += inputs[iOffsetInRange + channel]
+                                * weights[wOffset + sx];
+                        }
+                    }
+                }
+
+                outputs[oOffset + output] = activation_forward_value<Output_T>(weightedSum, output, ACTIVATION, rescaling);
+            }
+        }
+    }
+}
+
+// Template specialization when biases are not given to the convolution
+template<int NB_CHANNELS,
+         int CHANNELS_HEIGHT, int CHANNELS_WIDTH,
+         int NB_OUTPUTS,
+         int OUTPUTS_HEIGHT, int OUTPUTS_WIDTH,
+         int PADDING_Y, int PADDING_X,
+         int STRIDE_Y, int STRIDE_X,
+         int DILATION_Y, int DILATION_X,
+         int KERNEL_HEIGHT, int KERNEL_WIDTH,
+         ActivationFunction_T ACTIVATION,
+         typename Input_T, typename Output_T,
+         typename Weight_T,
+         typename Rescaling_T>
+__attribute__((always_inline)) inline
+void convolution_depthwise_forward(
+    const Input_T* __restrict inputs,
+    Output_T* __restrict outputs,
+    const Weight_T* __restrict weights,
+    std::nullptr_t __restrict,
+    const Rescaling_T& __restrict rescaling)
+{
+    const float* b = nullptr;
+
+    convolution_depthwise_forward<NB_CHANNELS,
+                        CHANNELS_HEIGHT,
+                        CHANNELS_WIDTH,
+                        NB_OUTPUTS,
+                        OUTPUTS_HEIGHT,
+                        OUTPUTS_WIDTH,
+                        PADDING_Y,
+                        PADDING_X,
+                        STRIDE_Y,
+                        STRIDE_X,
+                        DILATION_Y,
+                        DILATION_X,
+                        KERNEL_HEIGHT,
+                        KERNEL_WIDTH,
+                        ACTIVATION>
+                        (inputs, outputs, weights, b, rescaling);
+}
+
+#endif  // __AIDGE_EXPORT_CPP_KERNELS_CONVOLUTION_DEPTHWISE__
diff --git a/aidge_export_cpp/operators/ConvDw.py b/aidge_export_cpp/operators/ConvDw.py
new file mode 100644
index 0000000..41e4fdf
--- /dev/null
+++ b/aidge_export_cpp/operators/ConvDw.py
@@ -0,0 +1,82 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
+from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
+
+@ExportLibCpp.register("ConvDw2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class ConvDw(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Initialize kernel attributes
+        self.attributes["padding"] = [0, 0, 0, 0]
+        self.attributes["activation"] = "Linear"
+        self.attributes["depthwise"] = True
+        self.attributes["aidge_cmp"] = node.attributes().has_attr("aidge_cmp")
+
+        ## Scaling
+        self.attributes["rescaling"] = "NoScaling"
+        self.attributes["shift_value"] = 0
+
+        # Browse the metaop to update kernel attributes
+        ConvDwNode = get_node_from_metaop(node, "ConvDw2D") 
+        self.attributes["kernel_dims"] = ConvDwNode[0].get_operator().attr.kernel_dims
+        self.attributes["stride_dims"] = ConvDwNode[0].get_operator().attr.stride_dims
+        self.attributes["dilation_dims"] = ConvDwNode[0].get_operator().attr.dilation_dims
+
+        # Template for layer configutation file generation
+        self.config_template = str(ROOT / "templates" / "configuration" / "convolution_config.jinja")
+        
+        # Template layer call function generation within the forward file
+        self.forward_template = str(ROOT / "templates" / "kernel_forward" / "convolution_forward.jinja")
+        
+        # Files to include within the generated forward.cpp file
+        self.include_list = []
+        
+        # Path to the kernel(s) files to copy
+        self.add_kernel_to_copy(ROOT / "kernels" / "convolution_depthwise.hpp")
+        self.add_kernel_to_copy(ROOT / "static" / "macs.hpp", "include/network", fwd_include=False)
+        
+        # Include aidge outputs within the fwd file
+        if self.attributes["aidge_cmp"]:
+            self.include_list.append("network/utils.hpp")   # aidge_cmp function
+            self.include_list.append("data/aidge_outputs/" + node.name() + ".hpp") 
+
+
+@ExportLibCpp.register_metaop("QConvDw", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class QConvDw(ConvDw):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Look for Quantizer node and set shift and coef export node attributes
+        set_scaling_attributes(self, node)
+
+        ## Set the scaling type
+        if self.attributes["shift_value"] != 0:
+            self.attributes["rescaling"] = "SingleShiftScaling"
+
+
+@ExportLibCpp.register_metaop("PadConvDw", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadConvDw(QConvDw):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        PadNode = get_node_from_metaop(node, "Pad2D")
+        self.attributes["padding"] = PadNode[0].get_operator().attr.begin_end_borders
+
+
+@ExportLibCpp.register_metaop("ConvDwAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class ConvDwAct(QConvDw):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+
+        # Browse the metaop to update kernel attributes
+        if get_node_from_metaop(node, "ReLU"):
+            self.attributes["activation"] = "Rectifier"
+        else:
+            aidge_core.Log.error(f"{node.type()} activation is not yet supported.")
+
+@ExportLibCpp.register_metaop("PadConvDwAct", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+class PadConvDwAct(PadConvDw, ConvDwAct):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
diff --git a/aidge_export_cpp/templates/configuration/convolution_config.jinja b/aidge_export_cpp/templates/configuration/convolution_config.jinja
index f1a57db..b72df4d 100644
--- a/aidge_export_cpp/templates/configuration/convolution_config.jinja
+++ b/aidge_export_cpp/templates/configuration/convolution_config.jinja
@@ -17,7 +17,8 @@
 {% include "./_rescaling.jinja" %}
 
 {#- Calculate sizes #}
-{%- set weights_size = out_chan[0] * in_chan[0] * kernel_dims[1] * kernel_dims[0] %}
+{%- set weights_size = out_chan[0] * kernel_dims[1] * kernel_dims[0] if depthwise is defined
+    else out_chan[0] * in_chan[0] * kernel_dims[1] * kernel_dims[0] %}
 #define {{ name|upper }}_WEIGHTS_SIZE {{ weights_size }}
 #define {{ name|upper }}_BIASES_SIZE {{ out_chan[0] }}
 
-- 
GitLab


From 2bd9378d499a4bd8a27f619b70489648d82a39d9 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 14:25:05 +0200
Subject: [PATCH 82/93] [Fix] Wrong operator name

---
 aidge_export_cpp/operators/ConvDw.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/operators/ConvDw.py b/aidge_export_cpp/operators/ConvDw.py
index 41e4fdf..f34dbb5 100644
--- a/aidge_export_cpp/operators/ConvDw.py
+++ b/aidge_export_cpp/operators/ConvDw.py
@@ -2,7 +2,7 @@ import aidge_core
 from aidge_core.export_utils import ExportNodeCpp, get_node_from_metaop
 from aidge_export_cpp import ROOT, ExportLibCpp, set_scaling_attributes
 
-@ExportLibCpp.register("ConvDw2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
+@ExportLibCpp.register("ConvDepthWise2D", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.any)))
 class ConvDw(ExportNodeCpp):
     def __init__(self, node, mem_info):
         super().__init__(node, mem_info)
-- 
GitLab


From f203af0d5c3b8c0d3655fe3e1c59c73c7a6066b2 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 2 May 2025 14:28:04 +0200
Subject: [PATCH 83/93] [Fix] Wrong operator name 2

---
 aidge_export_cpp/operators/ConvDw.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aidge_export_cpp/operators/ConvDw.py b/aidge_export_cpp/operators/ConvDw.py
index f34dbb5..936c3b6 100644
--- a/aidge_export_cpp/operators/ConvDw.py
+++ b/aidge_export_cpp/operators/ConvDw.py
@@ -18,7 +18,7 @@ class ConvDw(ExportNodeCpp):
         self.attributes["shift_value"] = 0
 
         # Browse the metaop to update kernel attributes
-        ConvDwNode = get_node_from_metaop(node, "ConvDw2D") 
+        ConvDwNode = get_node_from_metaop(node, "ConvDepthWise2D") 
         self.attributes["kernel_dims"] = ConvDwNode[0].get_operator().attr.kernel_dims
         self.attributes["stride_dims"] = ConvDwNode[0].get_operator().attr.stride_dims
         self.attributes["dilation_dims"] = ConvDwNode[0].get_operator().attr.dilation_dims
-- 
GitLab


From 44ae1d27a65e68bcaeb2fe42d2ad485fbbe00c9e Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Mon, 12 May 2025 12:37:20 +0000
Subject: [PATCH 84/93] Fix Hardmax export with new export style.

---
 aidge_export_cpp/operators/Hardmax.py | 43 +++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 aidge_export_cpp/operators/Hardmax.py

diff --git a/aidge_export_cpp/operators/Hardmax.py b/aidge_export_cpp/operators/Hardmax.py
new file mode 100644
index 0000000..1dabf00
--- /dev/null
+++ b/aidge_export_cpp/operators/Hardmax.py
@@ -0,0 +1,43 @@
+import aidge_core
+from aidge_core.export_utils import ExportNodeCpp
+from aidge_export_cpp import ROOT, ExportLibCpp
+
+@ExportLibCpp.register("Hardmax", aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.float32)))
+class HardmaxCPP(ExportNodeCpp):
+    def __init__(self, node, mem_info):
+        super().__init__(node, mem_info)
+        assert self.node.get_nb_inputs() == 1, (
+            f"export hardmax: nb_inputs == {self.node.get_nb_inputs()} not implemented"
+        )
+
+        tensor = self.operator.get_input(0)
+        nbDims = len(tensor.dims())
+        axis = node.get_operator().attr.axis if node.get_operator().attr.axis >= 0 else node.get_operator().attr.axis + nbDims
+
+        assert axis >= -nbDims and axis < nbDims, (
+            f"export hardmax: attribute axis == {node.get_operator().attr.axis} should be comprised within [-{nbDims},{nbDims}]."
+        )
+
+        post_axis_elems = 1
+        for i in range(axis + 1, nbDims):
+            post_axis_elems *= tensor.dims()[i]
+
+        preaxis_elems = 1
+        for i in range(axis):
+            preaxis_elems *= tensor.dims()[i]
+
+        axis_elems = post_axis_elems * tensor.dims()[axis]
+        nb_elems = preaxis_elems * axis_elems
+
+        self.attributes["axis_dim_size"] = tensor.dims()[axis]
+        self.attributes["preaxis_stride"] = preaxis_elems
+        self.attributes["axis_stride"] = axis_elems
+        self.attributes["postaxis_stride"] = post_axis_elems
+        self.attributes["out_nb_elts"] = nb_elems
+
+        self.config_template = str(
+            ROOT / "templates" / "configuration" / "hardmax_config.jinja")
+        self.forward_template = str(
+            ROOT / "templates" / "kernel_forward" / "hardmax_forward.jinja")
+        self.include_list = []
+        self.add_kernel_to_copy(ROOT / "kernels" / "hardmax.hpp")
-- 
GitLab


From dd36553b690715054639923efcc4db15a0cdea0e Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 08:50:38 +0000
Subject: [PATCH 85/93] Update gitignore to ignore GDB files.

---
 .gitignore | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.gitignore b/.gitignore
index 876473d..a02270f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ xml*/
 
 # ONNX
 *.onnx
+
+# GDB
+.gdb_history
\ No newline at end of file
-- 
GitLab


From f961aeeb7c2b81ddeef3bcd142bf831f1cc192cb Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 09:43:03 +0000
Subject: [PATCH 86/93] Fix ResNet18 int8 export

* Add code to download model from Hugging Face
* Fix set_backend that was wrongly placed
* Update code to compile and execute the export inside the script
---
 examples/export_ResNet18/resnet18.py | 188 +++++++++++++++++----------
 1 file changed, 116 insertions(+), 72 deletions(-)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 64b3a9b..0712736 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -8,11 +8,14 @@ In order for this file to work properly, you should first download the imagenet
 """
 
 import random
+import aidge_core.utils
 import numpy as np
 import os
 import shutil
 from PIL import Image
-
+import requests
+from pathlib import Path
+import subprocess
 # Aidge Modules
 import aidge_core
 import aidge_onnx
@@ -21,9 +24,9 @@ import aidge_quantization
 import aidge_export_cpp
 
 from aidge_export_cpp.export_utils import (
-    cpp_fuse_to_metaops, 
-    exclude_unwanted_producers, 
-    set_nodes_names, 
+    cpp_fuse_to_metaops,
+    exclude_unwanted_producers,
+    set_nodes_names,
     set_nodes_datatypes,
     normalize)
 
@@ -60,6 +63,20 @@ parser.add_argument(
         "DEBUG < INFO < NOTICE < WARN < ERROR < FATAL."
     )
 )
+parser.add_argument(
+    "--imagenet_path",
+    type=str,
+    default="/database/ILSVRC2012/val",
+    help="Path to the ImageNet validation images folder (default: /database/ILSVRC2012/val)"
+)
+
+parser.add_argument(
+    "--imagenet_labels",
+    type=str,
+    default="/database/ILSVRC2012/val.txt",
+    help="Path to the file containing validation image labels (default: /database/ILSVRC2012/val.txt)"
+)
+
 args = parser.parse_args()
 
 USE_CUDA = not args.no_cuda
@@ -72,7 +89,7 @@ elif args.verbose == 1:
 elif args.verbose == 2:
     aidge_core.Log.set_console_level(aidge_core.Level.Info)
 elif args.verbose >= 3:
-    aidge_core.Log.set_console_level(aidge_core.Level.Debug) 
+    aidge_core.Log.set_console_level(aidge_core.Level.Debug)
 
 if USE_CUDA:
     import aidge_backend_cuda
@@ -84,31 +101,31 @@ if USE_CUDA:
 """
 Export configuration details :
 - RNG_SEED :        Fix a random seed for torch to always get the same images from the dataset,
-                        therefore always getting the same output. 
+                        therefore always getting the same output.
 - NB_TEST :         Number of example inferences to perform (used to get an accuracy approximation).
-- NB_CALIB :        Number of samples used for the calibration step of quantization. 
-- MODEL_NAME :      Should be the same name as the onnx file you want to load and export. 
+- NB_CALIB :        Number of samples used for the calibration step of quantization.
+- MODEL_NAME :      Should be the same name as the onnx file you want to load and export.
 - DO_EXAMPLES :     Perform example inferences (and allow to get accuracy approximation)
-- NB_BITS :         Quantization output precision. Should be 8 to work with this export. 
+- NB_BITS :         Quantization output precision. Should be 8 to work with this export.
 - TARGET_TYPE :     The aidge datatype for tensors to be casted after the quantization step.
-- OPTIM_SIGN :      Quantization optional optimization based on data sign. 
+- OPTIM_SIGN :      Quantization optional optimization based on data sign.
 - SINGLE_SHIFT :    Quantization option specifying if inserted scaling nodes should be
                         single shift or floating point.
-- NO_QUANT : Skip the quantization step. Should be set to False. 
-- CLIPPING :        Clipping method during quantization. 
+- NO_QUANT : Skip the quantization step. Should be set to False.
+- CLIPPING :        Clipping method during quantization.
 - FOLD_GRAPH :      The quantization step adds cast nodes to cast the graph into the given TARGET_TYPE.
                         Enabling the FOLD_GRAPH will automatically fold these nodes into the following
-                        ones at the end of quantization step. 
+                        ones at the end of quantization step.
 - USE_CUDA :        Determine if the quantization step uses the GPU. It is generally recommended
                         to enable this option if you have access to GPUs as the quantization step
                         may take a while to complete.
-- DEV_MODE :        The dev mode allows to identify errors more easily export the model with 
+- DEV_MODE :        The dev mode allows to identify errors more easily export the model with
                         symbolic links enabling to modify the source files directly in the
                         generated export (make sure you installed the export plugin running
-                        `pip install -e .`). 
-                        Enabled running this python file, adding the --test argument. 
+                        `pip install -e .`).
+                        Enabled running this python file, adding the --test argument.
 - AIDGE_MODE :      Saves and export the outputs generated by the aidge inferences in order
-                        to compare it with the export outputs. 
+                        to compare it with the export outputs.
                         Enabled running this python file, adding the --aidge_cmp argument.
 """
 
@@ -116,7 +133,7 @@ print(" Available backends : ", aidge_core.Tensor.get_available_backends())
 
 quantize_model = False
 NB_BITS = 32
-TARGET_TYPE = aidge_core.dtype.float32 
+TARGET_TYPE = aidge_core.dtype.float32
 
 if args.dtype == "float32":
     quantize_model = False
@@ -129,17 +146,17 @@ else:
     print(f"[ERROR] Supported datatypes : {supported_types}.")
     exit(1)
 
-RNG_SEED        = 1234 
+RNG_SEED        = 1234
 NB_TEST         = 20 # Test set
 NB_CALIB        = 20 # Calibration set
 MODEL_NAME      = 'resnet18'
 EXPORT_FOLDER   = f"export_{MODEL_NAME}_int8"
 DO_EXAMPLES     = True
- 
+
 # Quantization params
-OPTIM_SIGN      = False   
-SINGLE_SHIFT    = True    
-ROUNDING        = True 
+OPTIM_SIGN      = False
+SINGLE_SHIFT    = True
+ROUNDING        = True
 NO_QUANT        = False
 CLIPPING        = aidge_quantization.Clipping.MSE  # 'MAX'
 FOLD_GRAPH      = True
@@ -148,13 +165,14 @@ FOLD_GRAPH      = True
 DEV_MODE      = args.dev
 AIDGE_CMP     = args.aidge_cmp
 
-### Add your paths here ###
-IMAGENET_PATH = "/database/ILSVRC2012/val"  # Look for ILSVRC2012/val
-VAL_PATH = "/database/ILSVRC2012/val.txt"   # File containing labels of image of val folder (Look for val.txt)
+# Path to databases
+IMAGENET_PATH = args.imagenet_path # Path to ImageNet database
+LABEL_PATH = args.imagenet_labels           # File containing labels of image of val folder (Look for val.txt)
 ###########################
 
 def print_cfg():
-    print('\n RNG_SEED         = ', RNG_SEED)
+    print("")
+    print(' RNG_SEED         = ', RNG_SEED)
     print(' MODEL_NAME       = ', MODEL_NAME)
     print(' NB_TEST          = ', NB_TEST)
     print(' NB_CALIB         = ', NB_CALIB)
@@ -167,6 +185,8 @@ def print_cfg():
     print(' FOLD_GRAPH       = ', FOLD_GRAPH)
     print(' USE_CUDA         = ', USE_CUDA)
     print(' DEV_MODE         = ', DEV_MODE)
+    print(' IMAGENET_PATH    = ', IMAGENET_PATH)
+    print(' LABEL_PATH       = ', LABEL_PATH)
 
 print_cfg()
 
@@ -177,7 +197,7 @@ np.random.seed(RNG_SEED)
 backend = "cuda" if USE_CUDA else "cpu"
 
 image_label_pairs = []
-with open(VAL_PATH, 'r') as f:
+with open(LABEL_PATH, 'r') as f:
     for line in f:
         parts = line.strip().split()
         if len(parts) == 2:
@@ -195,13 +215,13 @@ selected_pairs = image_label_pairs[:NB_SELECT]
 transform_val = transforms.Compose([transforms.Resize(256),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor(),
-                                    transforms.Normalize(mean=[0.485,0.456, 0.406], std=[0.229, 0.224, 0.225])
+                                    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
                                     ])
 
 tensors = []
 labels  = []
 paths   = []
-index = 0
+index   = 0
 
 for image_name, label in selected_pairs:
     image_path = os.path.join(IMAGENET_PATH, image_name)
@@ -236,11 +256,19 @@ for tensor in tensors:
 """
 Load the .onnx model and perform some usual graph modifications :
     - Remove the flatten nodes;
-    - Fuse the batchnorm nodes into the biases producers. 
+    - Fuse the batchnorm nodes into the biases producers.
     - Expand the metaOperators to perform the desired fusions.
 """
 
-model = aidge_onnx.load_onnx(MODEL_NAME + ".onnx", verbose=False)
+
+# Define the target path and filename
+file_url = "https://huggingface.co/EclipseAidge/resnet18/resolve/main/resnet18_imagenet_1k.onnx?download=true"
+file_path = Path(MODEL_NAME + "_imagenet_1k.onnx")
+
+aidge_core.utils.download_file(file_path, file_url)
+
+model = aidge_onnx.load_onnx(file_path, verbose=False)
+
 model.save("imported_model")
 aidge_core.remove_flatten(model)
 aidge_core.fuse_batchnorm(model)
@@ -253,14 +281,14 @@ model.save("imported_model_fused_bn")
 
 """
 The scheduler is an ordered version of the model, allowing to schedule
-nodes to be able to run inferences, for instance. 
+nodes to be able to run inferences, for instance.
 """
 
 # Set up the backend
 model.set_datatype(aidge_core.dtype.float32)
 model.set_backend(backend)
 
-# Create the Scheduler 
+# Create the Scheduler
 scheduler = aidge_core.SequentialScheduler(model)
 
 # --------------------------------------------------------------
@@ -270,8 +298,8 @@ scheduler = aidge_core.SequentialScheduler(model)
 def propagate(model, scheduler, aidge_tensor):
     """ Propagate the given tensor into the model
     """
-    # Run the inference 
-    scheduler.forward(True, [aidge_tensor])    
+    # Run the inference
+    scheduler.forward(True, [aidge_tensor])
     # Gather the results
     output_node = model.get_output_nodes().pop()
     output_tensor = output_node.get_operator().get_output(0).clone()
@@ -299,14 +327,14 @@ if (DO_EXAMPLES):
 
 if quantize_model:
     aidge_quantization.quantize_network(
-        network = model, 
-        nb_bits = NB_BITS, 
-        calibration_set = aidge_tensors[0:NB_CALIB], 
+        network = model,
+        nb_bits = NB_BITS,
+        calibration_set = aidge_tensors[0:NB_CALIB],
         clipping_mode = CLIPPING,
         target_type = TARGET_TYPE,
         no_quant = NO_QUANT,
-        optimize_signs = OPTIM_SIGN, 
-        single_shift = SINGLE_SHIFT, 
+        optimize_signs = OPTIM_SIGN,
+        single_shift = SINGLE_SHIFT,
         use_cuda = USE_CUDA,
         fold_graph = FOLD_GRAPH)
 
@@ -321,34 +349,37 @@ for node in model.get_nodes():
 model.save("post_ptq_model")
 
 # --------------------------------------------------------------
-# RESCALE THE INPUT SAMPLES 
+# RESCALE THE INPUT SAMPLES
 # --------------------------------------------------------------
 
 """
-Once the quantization is done, the graph now only accepts integer inputs. 
+Once the quantization is done, the graph now only accepts integer inputs.
 So we need to rescale the dataset for the data to be within [0, 255].
-Also, tensors should be casted to be the same type as TARGET_TYPE. 
+Also, tensors should be casted to be the same type as TARGET_TYPE.
 """
 if quantize_model:
     rescaling = 2**(NB_BITS-1)-1
     for i in range(max(NB_TEST, NB_CALIB)):
-        array = np.array(aidge_tensors[i]) * rescaling 
+        array = np.array(aidge_tensors[i]) * rescaling
         array = np.round(array).astype(int)
         aidge_tensors[i] = aidge_core.Tensor(array)
         aidge_tensors[i].set_datatype(TARGET_TYPE)
+        aidge_tensors[i].set_backend("cpu")
+    # Setting modele to CPU for export
+    model.set_backend("cpu")
 
 # --------------------------------------------------------------
 # GENERATE NEW SCHEDULER
 # --------------------------------------------------------------
 
 """
-Each time the graph has been change, it has to be reset. 
-Here some Quantizer and Cast nodes have been added. 
+Each time the graph has been change, it has to be reset.
+Here some Quantizer and Cast nodes have been added.
 """
 
 """ [Issue]
-We need first to manually add an input tensor with the correct datatype, 
-as it is not automatically done in PTQ. 
+We need first to manually add an input tensor with the correct datatype,
+as it is not automatically done in PTQ.
 """
 if quantize_model:
     input_node = model.get_ordered_inputs()[0]
@@ -378,7 +409,7 @@ if (DO_EXAMPLES and quantize_model):
 
     output_array = propagate(model, scheduler, aidge_tensors[0])
 
-if USE_CUDA:    
+if USE_CUDA:
     model.set_backend("cpu")
     for aidge_tensor in aidge_tensors:
         aidge_tensor.set_backend("cpu")
@@ -394,28 +425,28 @@ are performed separately (Pad -> Conv -> Quantizer -> ReLU -> ...).
 
 However within the CPP export, some core operators are merged
 in meta operators. For instance, the padding, scaling and ReLU are
-performed within the Conv kernel. 
+performed within the Conv kernel.
 
 In this step, we use graph regex techniques to find the desired patterns
-within the graph in order to match the export implementation of the kernels. 
+within the graph in order to match the export implementation of the kernels.
 """
 
-# Exclude unwanted producers 
+# Exclude unwanted producers
 """
 Before fusing the nodes, we set a tag on the Producers in order to exclude
 from the export the ones holding coefficients, as they are directly handled
-within the layers parameters. 
+within the layers parameters.
 """
 exclude_unwanted_producers(model)
 
 # Fuse nodes
 cpp_fuse_to_metaops(model)
 
-# Remove optional inputs 
+# Remove optional inputs
 """
 Some optional inputs may be added by the quantization step (for instance with the clipping nodes).
 Here we make sure that they will not be considered as actual graph inputs by the export, by
-excluding them from the ordered_inputs list of the model. 
+excluding them from the ordered_inputs list of the model.
 """
 remove_optional_inputs(model)
 
@@ -428,7 +459,7 @@ scheduler.reset_scheduling()
 # Name newly created MetaOps
 """
 As names are optional in Aidge, the fuse_to_metaops function will not automatically
-give a name to the newly created metaOps. However, in an export context, we need 
+give a name to the newly created metaOps. However, in an export context, we need
 our operators to be named, as this will be used to name the corresponding files.
 """
 scheduler.generate_scheduling() # Scheduler needs to be generated as it has just been reset
@@ -439,11 +470,11 @@ set_nodes_names(scheduler)
 # --------------------------------------------------------------
 
 """
-Here a final inference is made on the input we want to export and run. 
+Here a final inference is made on the input we want to export and run.
 This will ensure that all the feature maps tensors (between the layers)
-hold the data corresponding to this specific input. 
+hold the data corresponding to this specific input.
 Then, the "log_outputs()" function (called later) will store these tensors
-into log files that may be exported as well for comparison purpose. 
+into log files that may be exported as well for comparison purpose.
 """
 
 output_array = propagate(model, scheduler, aidge_tensors[0])
@@ -467,7 +498,7 @@ if quantize_model:
 # Store tensors values into log files
 """
 Once the tensors has been casted, the log_outputs() function can be
-called to store their values into log files. 
+called to store their values into log files.
 """
 
 if os.path.isdir("log_outputs"):
@@ -479,11 +510,11 @@ model.log_outputs("log_outputs")
 # --------------------------------------------------------------
 
 """
-The test mode is mainly used for validation and benchmark. The model will be 
-exported in a way that each layer's result will be compared with the CPU implementation. 
-The timings for each layer will be displayed. 
-In case of error, you will be able to enter debug mode, showing in-layer data or 
-changing the inputs of the layer, to isolate the source of the issue. 
+The test mode is mainly used for validation and benchmark. The model will be
+exported in a way that each layer's result will be compared with the CPU implementation.
+The timings for each layer will be displayed.
+In case of error, you will be able to enter debug mode, showing in-layer data or
+changing the inputs of the layer, to isolate the source of the issue.
 """
 
 for node in model.get_nodes():
@@ -494,12 +525,12 @@ for node in model.get_nodes():
 # --------------------------------------------------------------
 
 """
-If the --aidge_cmp option is enabled, the feature maps generated by aidge with the 
+If the --aidge_cmp option is enabled, the feature maps generated by aidge with the
 backend cpu will be exported in the generated export. It will be used as reference
 to verify that the results with the optimized kernels are correct for the exported
-model. 
+model.
 This option has to be passed to each node in order to be used within the Export Nodes.
-(JConv, JPad, ...) that you can find in the "export_gen/operator_export" folder. 
+(JConv, JPad, ...) that you can find in the "export_gen/operator_export" folder.
 """
 
 if AIDGE_CMP:
@@ -517,10 +548,23 @@ inputs_tensor.set_data_format(aidge_core.dformat.nhwc)  # Transpose the data  (n
 if args.dtype == "int8":
     inputs_tensor.set_datatype(aidge_core.dtype.int8)
 
-aidge_export_cpp.export(EXPORT_FOLDER, 
-                        model, 
-                        scheduler, 
+aidge_export_cpp.export(EXPORT_FOLDER,
+                        model,
+                        scheduler,
                         labels = aidge_core.Tensor(labels[0]),
                         inputs_tensor=inputs_tensor,
                         dev_mode = DEV_MODE,
                         aidge_cmp = AIDGE_CMP)
+
+print("\n### Compiling the export ###")
+try:
+    for std_line in aidge_core.utils.run_command(["make"], cwd=EXPORT_FOLDER):
+        print(std_line, end="")
+except subprocess.CalledProcessError as e:
+            raise RuntimeError(0, f"An error occurred, failed to build export.") from e
+print("\n### Running the export ###")
+try:
+    for std_line in aidge_core.utils.run_command(["./bin/run_export"], cwd=EXPORT_FOLDER):
+        print(std_line, end="")
+except subprocess.CalledProcessError as e:
+    raise RuntimeError(0, f"An error occurred, failed to run export.") from e
\ No newline at end of file
-- 
GitLab


From 6754e159c814021c79cea54e9579a0f946c25a47 Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 13:34:00 +0000
Subject: [PATCH 87/93]  Fix LeNet int8 export

* Add code to download model from Hugging Face
 * Fix set_backend that was wrongly placed
 * Update code to compile and execute the export inside the script
---
 examples/export_LeNet/create_lenet.py | 106 ----------------
 examples/export_LeNet/lenet.py        | 172 ++++++++++++++------------
 2 files changed, 93 insertions(+), 185 deletions(-)
 delete mode 100644 examples/export_LeNet/create_lenet.py

diff --git a/examples/export_LeNet/create_lenet.py b/examples/export_LeNet/create_lenet.py
deleted file mode 100644
index d135296..0000000
--- a/examples/export_LeNet/create_lenet.py
+++ /dev/null
@@ -1,106 +0,0 @@
-"""
-create_lenet.py
-
-This file creates a simple lenet network using the MNIST dataset. 
-It is meant to be used by the lenet.py file. 
-"""
-
-import random
-
-import torch
-from torchvision import datasets, transforms
-import torch.nn as nn
-import torch.nn.functional as F
-
-# Download the MNIST Dataset
-
-def get_mnist_dataset():
-    transform = transforms.ToTensor()
-    train_set = datasets.MNIST(root='./data', train=True,  transform=transform, download=True)
-    test_set  = datasets.MNIST(root='./data', train=False, transform=transform, download=True)
-    return train_set, test_set
-
-# Create the lenet model
-
-class Classifier(torch.nn.Module):     
-    def __init__(self):
-        super().__init__()
-        self.network = nn.Sequential(
-            nn.Conv2d(1, 32, 5),  # 28 -> 24
-            nn.ReLU(),
-            nn.MaxPool2d(2, 2),   # 24 -> 12
-            nn.Conv2d(32, 32, 5), # 12 ->  8
-            nn.ReLU(),
-            nn.MaxPool2d(2, 2),   #  8 ->  4           
-            nn.Flatten(),
-            nn.Linear(32*4*4, 100),
-            nn.ReLU(),
-            nn.Linear(100, 100),
-            nn.ReLU(),
-            nn.Linear(100, 10)
-        )
-
-    def forward(self, x):
-        return self.network(x)
-
-# Compute accuracy function
-        
-def compute_accuracy(model, data_set, nb_samples):
-    nb_valid = 0
-    for it in range(nb_samples):
-        # get a sample
-        sample_idx = torch.randint(len(data_set), size=(1,)).item()
-        img, label = data_set[sample_idx]
-        # compute the output
-        x = torch.reshape(img, (1,1,28,28))
-        y_h = model.forward(x)
-        pred_label = torch.argmax(y_h).item()
-        if label == pred_label :
-            nb_valid = nb_valid + 1
-    return nb_valid / nb_samples
-
-# Train the model
-
-def train_model(NB_ITERATION, CHECK_PERIOD, train_set, test_set, classifier):
-    accuracy_history = []
-    for it in range(NB_ITERATION):
-        sample_idx = random.randint(0, len(train_set)-1)
-        img, label = train_set[sample_idx]
-        x = torch.flatten(img)
-        x = torch.reshape(x, (1,1,28,28))
-        y = torch.zeros(1,10)
-        y[0][label] = 1
-        y_h = classifier.forward(x)
-        #print(y_h.shape, 'test')
-        l = F.mse_loss(y, y_h)
-        l.backward()
-        for p in classifier.parameters():
-            with torch.no_grad():
-                p -= 0.01 * p.grad
-            p.grad.zero_()
-
-        if it % CHECK_PERIOD == 0:
-            accuracy = compute_accuracy(classifier, test_set, CHECK_PERIOD)
-            accuracy_history.append(accuracy)
-            print(f'it {it}: accuracy = {accuracy:.8f} ')
-
-
-def create_lenet():
-    
-    # Get Dataset
-    train_set, test_set = get_mnist_dataset()
-
-    # Create model
-    classifier = Classifier()
-
-    # Train model
-    NB_ITERATION = 50000
-    CHECK_PERIOD = 3000
-    print("NB_ITERATIONS = ", NB_ITERATION)
-    print("CHECK_PERIOD  = ", CHECK_PERIOD)
-    print("\nTraining LeNet...")
-    train_model(NB_ITERATION, CHECK_PERIOD, train_set, test_set, classifier)
-
-    # Export as ONNX
-    x = torch.Tensor(1,1,28,28)
-    torch.onnx.export(classifier.network, x, 'lenet.onnx', verbose=False, input_names=[ "input" ], output_names=[ "output" ])
\ No newline at end of file
diff --git a/examples/export_LeNet/lenet.py b/examples/export_LeNet/lenet.py
index c282655..1cda87b 100644
--- a/examples/export_LeNet/lenet.py
+++ b/examples/export_LeNet/lenet.py
@@ -1,13 +1,15 @@
 """
 lenet.py
 
-Run this file to export a LeNet using the Aidge CPP Export module. 
+Run this file to export a LeNet using the Aidge CPP Export module.
 """
 
 import os
 import shutil
 import random
+import aidge_core.utils
 import numpy as np
+import subprocess
 
 # Aidge Modules
 import aidge_core
@@ -67,7 +69,7 @@ elif args.verbose == 1:
 elif args.verbose == 2:
     aidge_core.Log.set_console_level(aidge_core.Level.Info)
 elif args.verbose >= 3:
-    aidge_core.Log.set_console_level(aidge_core.Level.Debug) 
+    aidge_core.Log.set_console_level(aidge_core.Level.Debug)
 
 if USE_CUDA:
     import aidge_backend_cuda
@@ -79,39 +81,39 @@ if USE_CUDA:
 """
 Export configuration details :
 - RNG_SEED :        Fix a random seed for torch to always get the same images from the dataset,
-                        therefore always getting the same output. 
+                        therefore always getting the same output.
 - NB_TEST :         Number of example inferences to perform (used to get an accuracy approximation).
-- NB_CALIB :        Number of samples used for the calibration step of quantization. 
-- MODEL_NAME :      Should be the same name as the onnx file you want to load and export. 
+- NB_CALIB :        Number of samples used for the calibration step of quantization.
+- MODEL_NAME :      Should be the same name as the onnx file you want to load and export.
 - DO_EXAMPLES :     Perform example inferences (and allow to get accuracy approximation)
-- NB_BITS :         Quantization output precision. Should be 8 to work with this export. 
+- NB_BITS :         Quantization output precision. Should be 8 to work with this export.
 - TARGET_TYPE :     The aidge datatype for tensors to be casted after the quantization step [float64, float32, int32].
-- OPTIM_SIGN :      Quantization optional optimization based on data sign. 
+- OPTIM_SIGN :      Quantization optional optimization based on data sign.
 - SINGLE_SHIFT :    Quantization option specifying if inserted scaling nodes should be
                         single shift or floating point.
 - NO_QUANT :        Skip the quantization step.
-- CLIPPING :        Clipping method during quantization. 
+- CLIPPING :        Clipping method during quantization.
 - FOLD_GRAPH :      The quantization step adds cast nodes to cast the graph into the given TARGET_TYPE.
                         Enabling the FOLD_GRAPH will automatically fold these nodes into the following
-                        ones at the end of quantization step. 
+                        ones at the end of quantization step.
 - USE_CUDA :        Determine if the quantization step uses the GPU. It is generally recommended
                         to enable this option if you have access to GPUs as the quantization step
-                        may take a while to complete. 
-- DEV_MODE :        The dev mode allows to identify errors more easily exporting the model with 
+                        may take a while to complete.
+- DEV_MODE :        The dev mode allows to identify errors more easily exporting the model with
                         symbolic links enabling to modify the source files directly in the
                         generated export (make sure you installed the export plugin running
-                        `pip install -e .`). 
-                        Enabled running this python file, adding the --dev argument. 
+                        `pip install -e .`).
+                        Enabled running this python file, adding the --dev argument.
 - AIDGE_CMP :       Saves and export the outputs generated by the aidge inferences in order
-                        to compare it with the export outputs. 
-                        Enabled running this python file, adding the --aidge_cmp argument. 
+                        to compare it with the export outputs.
+                        Enabled running this python file, adding the --aidge_cmp argument.
 """
 
 print(" Available backends : ", aidge_core.Tensor.get_available_backends())
 
 quantize_model = False
 NB_BITS = 32
-TARGET_TYPE = aidge_core.dtype.float32 
+TARGET_TYPE = aidge_core.dtype.float32
 
 if args.dtype == "float32":
     quantize_model = False
@@ -124,7 +126,7 @@ else:
     print(f"[ERROR] Supported datatypes : {supported_types}.")
     exit(1)
 
-RNG_SEED      = 1234 
+RNG_SEED      = 1234
 NB_TEST       = 10 # Example inferences
 NB_CALIB      = 20 # Calibration set
 MODEL_NAME    = 'lenet'
@@ -135,7 +137,7 @@ DO_EXAMPLES   = True
 OPTIM_SIGN      = False
 SINGLE_SHIFT    = True
 ROUNDING        = True
-NO_QUANT = False  
+NO_QUANT = False
 CLIPPING        = aidge_quantization.Clipping.MSE  # 'MAX'
 FOLD_GRAPH      = True
 
@@ -164,16 +166,14 @@ backend = "cuda" if USE_CUDA else "cpu"
 # CREATE THE LENET MODEL
 # ------------------------------------------------------------
 """
-The LeNet model is created and trained using the create_lenet file. 
-If a lenet.onnx file is already present in the current folder, this step will be skiped. 
-The generated network is not yet quantized. 
+The LeNet model is created and trained using the create_lenet file.
+If a lenet.onnx file is already present in the current folder, this step will be skiped.
+The generated network is not yet quantized.
 """
-
-from create_lenet import create_lenet
-
-if not os.path.isfile("./lenet.onnx"):
-    print("\nTraining LeNet...")
-    create_lenet()
+# Define the target path and filename
+file_url = "https://huggingface.co/EclipseAidge/LeNet/resolve/main/lenet_mnist.onnx?download=true"
+file_path = MODEL_NAME + "_mnist.onnx"
+aidge_core.utils.download_file(file_path, file_url)
 
 # --------------------------------------------------------------
 # CREATE THE SAMPLES
@@ -185,8 +185,8 @@ test_set  = datasets.MNIST(root='./data', train=False, transform=transform, down
 tensors = []
 labels  = []
 index = 0
-for input, label in test_set:
-    array = np.array(input)
+for in_tensor, label in test_set:
+    array = np.array(in_tensor)
     array = np.reshape(array, (1, 1, 28, 28))
     tensor = aidge_core.Tensor(array)
     tensor.set_backend(backend)
@@ -204,11 +204,11 @@ for input, label in test_set:
 """
 Load the .onnx model and perform some usual graph modifications :
     - Remove the flatten nodes;
-    - Fuse the batchnorm nodes into the biases producers. 
+    - Fuse the batchnorm nodes into the biases producers.
     - Expand the metaOperators to perform the desired fusions.
 """
 
-model = aidge_onnx.load_onnx(MODEL_NAME + ".onnx", verbose=False)
+model = aidge_onnx.load_onnx(file_path, verbose=False)
 aidge_core.remove_flatten(model)
 aidge_core.fuse_batchnorm(model)
 aidge_core.expand_metaops(model)
@@ -220,14 +220,14 @@ model.save("imported_model")
 
 """
 The scheduler is an ordered version of the model, allowing to schedule
-nodes to be able to run inferences, for instance. 
+nodes to be able to run inferences, for instance.
 """
 
 # Set up the backend
 model.set_datatype(aidge_core.dtype.float32)
 model.set_backend(backend)
 
-# Create the Scheduler 
+# Create the Scheduler
 scheduler = aidge_core.SequentialScheduler(model)
 
 # --------------------------------------------------------------
@@ -235,11 +235,12 @@ scheduler = aidge_core.SequentialScheduler(model)
 # --------------------------------------------------------------
 
 def propagate(model, scheduler, tensor):
-    """ 
+    """
     Propagate the given tensor into the model and return the
-    output tensor. 
+    output tensor.
     """
-    # Run the inference 
+    print(f"Propagate: {tensor.backend()}")
+    # Run the inference
     scheduler.forward(True, [tensor])
     # Gather the results
     output_node = model.get_output_nodes().pop()
@@ -253,6 +254,7 @@ if (DO_EXAMPLES):
     nb_valid = 0
     base_values = []
     for i in range(NB_TEST):
+        print(f"Inférence: {tensors[i].backend()}")
         output_array = propagate(model, scheduler, tensors[i])
         print(labels[i], ' VS ', np.argmax(output_array), ' -> ', np.max(output_array))
         base_values.append(np.max(output_array))
@@ -263,18 +265,18 @@ if (DO_EXAMPLES):
 
 # --------------------------------------------------------------
 # PERFORM THE QUANTIZATION
-# -------------------------------------------------------------- 
+# --------------------------------------------------------------
 
 if quantize_model:
     aidge_quantization.quantize_network(
-        network = model, 
-        nb_bits = NB_BITS, 
-        calibration_set = tensors[0:NB_CALIB], 
+        network = model,
+        nb_bits = NB_BITS,
+        calibration_set = tensors[0:NB_CALIB],
         clipping_mode = CLIPPING,
         target_type = TARGET_TYPE,
         no_quant = NO_QUANT,
-        optimize_signs = OPTIM_SIGN, 
-        single_shift = SINGLE_SHIFT, 
+        optimize_signs = OPTIM_SIGN,
+        single_shift = SINGLE_SHIFT,
         use_cuda = USE_CUDA,
         fold_graph = FOLD_GRAPH)
 
@@ -293,31 +295,36 @@ model.save("post_ptq_model")
 # --------------------------------------------------------------
 
 """
-Once the quantization is done, the graph now only accepts integer inputs. 
+Once the quantization is done, the graph now only accepts integer inputs.
 So we need to rescale the dataset for the data to be within [0, 255].
-Also, tensors should be casted to be the same type as TARGET_TYPE. 
+Also, tensors should be casted to be the same type as TARGET_TYPE.
 """
 
 if quantize_model:
     rescaling = 2**(NB_BITS-1)-1
     for i in range(NB_TEST):
-        array = np.array(tensors[i]) * rescaling 
+        tensors[i].set_backend("cpu")
+        array = np.array(tensors[i]) * rescaling
         array = np.round(array).astype(int)
         tensors[i] = aidge_core.Tensor(array)
         tensors[i].set_datatype(TARGET_TYPE)
+        tensors[i].set_backend("cpu")
+    # Setting modele to CPU for export
+    model.set_backend("cpu")
+
 
 # --------------------------------------------------------------
 # GENERATE NEW SCHEDULER
 # --------------------------------------------------------------
 
 """
-Each time the graph has been change, it has to be reset. 
-Here some Quantizer and Cast nodes have been added. 
+Each time the graph has been change, it has to be reset.
+Here some Quantizer and Cast nodes have been added.
 """
 
 """ [Issue]
-We need first to manually add an input tensor with the correct datatype, 
-as it is not automatically done in PTQ. 
+We need first to manually add an input tensor with the correct datatype,
+as it is not automatically done in PTQ.
 """
 if quantize_model:
     input_node = model.get_ordered_inputs()[0]
@@ -329,10 +336,11 @@ if quantize_model:
 # --------------------------------------------------------------
 
 if (DO_EXAMPLES and quantize_model):
-    print('\n QUANTIZED EXAMPLE INFERENCES :')
+    print('\n QUANTIZED EXAMPLE INFERENCES:')
     nb_valid = 0
     post_values = []
     for i in range(NB_TEST):
+        print(f"QEI: {tensors[i].backend()}")
         output_array = propagate(model, scheduler, tensors[i])
         print(labels[i], ' VS ', np.argmax(output_array), ' -> ', np.max(output_array))
         post_values.append(np.max(output_array))
@@ -343,12 +351,6 @@ if (DO_EXAMPLES and quantize_model):
     print('\n MODEL ACCURACY = ', accuracy * 100, '%')
     print('\n QUANTIZED ACCURACY = ', quant_accuracy * 100, '%')
 
-    output_array = propagate(model, scheduler, tensors[0])
-
-if USE_CUDA:
-    model.set_backend("cpu")
-    for tensor in tensors:
-        tensor.set_backend("cpu")
 
 # --------------------------------------------------------------
 # FUSE NODES INTO METAOPS
@@ -361,28 +363,28 @@ are performed separately (Pad -> Conv -> Quantizer -> ReLU -> ...).
 
 However within the CPP export, some core operators are merged
 in meta operators. For instance, the padding, scaling and ReLU are
-performed within the Conv kernel. 
+performed within the Conv kernel.
 
 In this step, we use graph regex techniques to find the desired patterns
-within the graph in order to match the export implementation of the kernels. 
+within the graph in order to match the export implementation of the kernels.
 """
 
-# Exclude unwanted producers 
+# Exclude unwanted producers
 """
 Before fusing the nodes, we set a tag on the Producers in order to exclude
 from the export the ones holding coefficients, as they are directly handled
-within the layers parameters. 
+within the layers parameters.
 """
 exclude_unwanted_producers(model)
 
 # Fuse nodes
 cpp_fuse_to_metaops(model)
 
-# Remove optional inputs 
+# Remove optional inputs
 """
 Some optional inputs may be added by the quantization step (for instance with the clipping nodes).
 Here we make sure that they will not be considered as actual graph inputs by the export, by
-excluding them from the ordered_inputs list of the model. 
+excluding them from the ordered_inputs list of the model.
 """
 remove_optional_inputs(model)
 
@@ -395,7 +397,7 @@ scheduler.reset_scheduling()
 # Name newly created MetaOps
 """
 As names are optional in Aidge, the fuse_to_metaops function will not automatically
-give a name to the newly created metaOps. However, in an export context, we need 
+give a name to the newly created metaOps. However, in an export context, we need
 our operators to be named, as this will be used to name the corresponding files.
 """
 
@@ -407,11 +409,11 @@ set_nodes_names(scheduler)
 # --------------------------------------------------------------
 
 """
-Here a final inference is made on the input we want to export and run. 
+Here a final inference is made on the input we want to export and run.
 This will ensure that all the feature maps tensors (between the layers)
-hold the data corresponding to this specific input. 
+hold the data corresponding to this specific input.
 Then, the "log_outputs()" function (called later) will store these tensors
-into log files that may be exported as well for comparison purpose. 
+into log files that may be exported as well for comparison purpose.
 """
 
 output_array = propagate(model, scheduler, tensors[0])
@@ -436,7 +438,7 @@ if quantize_model:
 # Store tensors values into log files
 """
 Once the tensors have been casted, the log_outputs() function can be
-called to store their values into log files. 
+called to store their values into log files.
 """
 
 if os.path.isdir("log_outputs"):
@@ -448,11 +450,11 @@ model.log_outputs("log_outputs")
 # --------------------------------------------------------------
 
 """
-The test mode is mainly used for validation and benchmark. The model will be 
-exported in a way that each layer's result will be compared with the CPU implementation. 
-The timings for each layer will be displayed. 
-In case of error, you will be able to enter debug mode, showing in-layer data or 
-changing the inputs of the layer, to isolate the source of the issue. 
+The test mode is mainly used for validation and benchmark. The model will be
+exported in a way that each layer's result will be compared with the CPU implementation.
+The timings for each layer will be displayed.
+In case of error, you will be able to enter debug mode, showing in-layer data or
+changing the inputs of the layer, to isolate the source of the issue.
 """
 
 for node in model.get_nodes():
@@ -463,12 +465,12 @@ for node in model.get_nodes():
 # --------------------------------------------------------------
 
 """
-If the --aidge_cmp option is enabled, the feature maps generated by aidge with the 
+If the --aidge_cmp option is enabled, the feature maps generated by aidge with the
 backend cpu will be exported in the generated export. It will be used as reference
 to verify that the results with the optimized kernels are correct for the exported
-model. 
+model.
 This option has to be passed to each node in order to be used within the Export Nodes.
-(JConv, JPad, ...) that you can find in the "operators" folder. 
+(JConv, JPad, ...) that you can find in the "operators" folder.
 """
 
 if AIDGE_CMP:
@@ -481,10 +483,22 @@ if AIDGE_CMP:
 
 model.save("exported_model")
 
-aidge_export_cpp.export(EXPORT_FOLDER, 
-                        model, 
-                        scheduler, 
+aidge_export_cpp.export(EXPORT_FOLDER,
+                        model,
+                        scheduler,
                         # tensors[0],
-                        labels = aidge_core.Tensor(labels[0]), 
+                        labels = aidge_core.Tensor(labels[0]),
                         dev_mode = DEV_MODE,
                         aidge_cmp = AIDGE_CMP)
+print("\n### Compiling the export ###")
+try:
+    for std_line in aidge_core.utils.run_command(["make"], cwd=EXPORT_FOLDER):
+        print(std_line, end="")
+except subprocess.CalledProcessError as e:
+            raise RuntimeError(0, f"An error occurred, failed to build export.") from e
+print("\n### Running the export ###")
+try:
+    for std_line in aidge_core.utils.run_command(["./bin/run_export"], cwd=EXPORT_FOLDER):
+        print(std_line, end="")
+except subprocess.CalledProcessError as e:
+    raise RuntimeError(0, f"An error occurred, failed to run export.") from e
\ No newline at end of file
-- 
GitLab


From 30f520a935d7d4c3695c964d75eee70860faed4c Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 14:22:25 +0000
Subject: [PATCH 88/93] Add unit test on example scripts.

---
 aidge_export_cpp/unit_tests/test_examples.py |  46 ++++++++
 examples/export_ResNet18/resnet18.py         | 118 +++++++++++--------
 2 files changed, 112 insertions(+), 52 deletions(-)
 create mode 100644 aidge_export_cpp/unit_tests/test_examples.py

diff --git a/aidge_export_cpp/unit_tests/test_examples.py b/aidge_export_cpp/unit_tests/test_examples.py
new file mode 100644
index 0000000..1ddf659
--- /dev/null
+++ b/aidge_export_cpp/unit_tests/test_examples.py
@@ -0,0 +1,46 @@
+import subprocess
+import sys
+import os
+import pytest
+from pathlib import Path
+
+CURRENT_DIR = Path(__file__).parent
+EXAMPLES_DIR = CURRENT_DIR / "../../examples"
+
+# Dictionary of test cases: {id: (script_name, script_args)}
+TEST_CASES = {
+    "lenet-int8-no-args": ("export_LeNet/lenet.py", []),
+    "resnet18-int8-no-args": ("export_ResNet18/resnet18.py", ["--mock_db"])
+}
+
+def generate_test_cases():
+    """Parse TEST_CASES to provide valid pytest params.
+    """
+    for test_id, (script, args) in TEST_CASES.items():
+        yield pytest.param(script, args, id=test_id)
+
+@pytest.mark.parametrize(("script_name", "script_args"), generate_test_cases())
+def test_example_scripts_run_without_error(script_name, script_args):
+    """Basic test to verify that examples script run withoput raising an Error.
+    This test DO NOT check that the examples are working only that they are not broken.
+    """
+    script_path = os.path.join(EXAMPLES_DIR, script_name)
+    result = subprocess.run(
+        [sys.executable, script_path] + script_args,  # Or any lightweight args
+        capture_output=True,
+        text=True
+    )
+    assert result.returncode == 0, f"{script_name} failed with error: {result.stderr}"
+
+
+def main():
+    import sys
+
+    print(
+        f"{sys.argv[0]}: Warning: skipped: run with: pytest {sys.argv[0]}",
+        file=sys.stderr,
+)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 0712736..948a60f 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -16,6 +16,7 @@ from PIL import Image
 import requests
 from pathlib import Path
 import subprocess
+from random import randint
 # Aidge Modules
 import aidge_core
 import aidge_onnx
@@ -63,6 +64,10 @@ parser.add_argument(
         "DEBUG < INFO < NOTICE < WARN < ERROR < FATAL."
     )
 )
+
+parser.add_argument("--mock_db", action="store_true", help="Use a mock database instead of real one (TEST ONLY).")
+
+
 parser.add_argument(
     "--imagenet_path",
     type=str,
@@ -187,6 +192,7 @@ def print_cfg():
     print(' DEV_MODE         = ', DEV_MODE)
     print(' IMAGENET_PATH    = ', IMAGENET_PATH)
     print(' LABEL_PATH       = ', LABEL_PATH)
+    print(' MOCK_DB          = ', args.mock_db)
 
 print_cfg()
 
@@ -194,59 +200,68 @@ torch.manual_seed(RNG_SEED)
 random.seed(RNG_SEED)
 np.random.seed(RNG_SEED)
 
-backend = "cuda" if USE_CUDA else "cpu"
-
-image_label_pairs = []
-with open(LABEL_PATH, 'r') as f:
-    for line in f:
-        parts = line.strip().split()
-        if len(parts) == 2:
-            image_name, label = parts
-            image_label_pairs.append((image_name, int(label)))
-
-np.random.seed(RNG_SEED)
-NB_SELECT = max(NB_TEST, NB_CALIB)  # Check that NB_TEST and NB_CALIB are fixed
-selected_pairs = image_label_pairs[:NB_SELECT]
-
-# --------------------------------------------------------------
-# CREATE THE SAMPLES
-# --------------------------------------------------------------
-
-transform_val = transforms.Compose([transforms.Resize(256),
-                                    transforms.CenterCrop(224),
-                                    transforms.ToTensor(),
-                                    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
-                                    ])
-
-tensors = []
-labels  = []
-paths   = []
-index   = 0
-
-for image_name, label in selected_pairs:
-    image_path = os.path.join(IMAGENET_PATH, image_name)
-    if os.path.exists(image_path):
-        try:
-            image = Image.open(image_path)
-            if image.mode != 'RGB':
-                image = image.convert('RGB')
-            tensor = transform_val(image)
-            tensors.append(tensor)
-            labels.append(label)
-            paths.append(image_path)
-        except Exception as e:
-            print(f"Error with image {image_path}: {e}")
-
 backend = "cuda" if USE_CUDA else "cpu"
 aidge_tensors = []
-for tensor in tensors:
-    array = tensor.numpy()
-    array = np.reshape(array, (1, 3, 224, 224))
-    array = normalize(array)
-    aidge_tensor = aidge_core.Tensor(array)
-    aidge_tensor.set_backend(backend)
-    aidge_tensor.set_datatype(aidge_core.dtype.float32)
-    aidge_tensors.append(aidge_tensor)
+labels  = []
+if args.mock_db:
+    for i in range(NB_TEST):
+        aidge_tensor = aidge_core.Tensor(dims=(1, 3, 224, 224))
+        aidge_tensor.set_backend(backend)
+        aidge_tensor.set_datatype(aidge_core.dtype.float32)
+        aidge_core.uniform_filler(aidge_tensor, -1.0, 1.0)
+        aidge_tensors.append(aidge_tensor)
+        labels.append(randint(1, 1000))
+else:
+    image_label_pairs = []
+    with open(LABEL_PATH, 'r') as f:
+        for line in f:
+            parts = line.strip().split()
+            if len(parts) == 2:
+                image_name, label = parts
+                image_label_pairs.append((image_name, int(label)))
+
+    np.random.seed(RNG_SEED)
+    NB_SELECT = max(NB_TEST, NB_CALIB)  # Check that NB_TEST and NB_CALIB are fixed
+    selected_pairs = image_label_pairs[:NB_SELECT]
+
+    # --------------------------------------------------------------
+    # CREATE THE SAMPLES
+    # --------------------------------------------------------------
+
+    transform_val = transforms.Compose([transforms.Resize(256),
+                                        transforms.CenterCrop(224),
+                                        transforms.ToTensor(),
+                                        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+                                        ])
+
+    tensors = []
+    labels  = []
+    paths   = []
+    index   = 0
+
+    for image_name, label in selected_pairs:
+        image_path = os.path.join(IMAGENET_PATH, image_name)
+        if os.path.exists(image_path):
+            try:
+                image = Image.open(image_path)
+                if image.mode != 'RGB':
+                    image = image.convert('RGB')
+                tensor = transform_val(image)
+                tensors.append(tensor)
+                labels.append(label)
+                paths.append(image_path)
+            except Exception as e:
+                print(f"Error with image {image_path}: {e}")
+
+
+    for tensor in tensors:
+        array = tensor.numpy()
+        array = np.reshape(array, (1, 3, 224, 224))
+        array = normalize(array)
+        aidge_tensor = aidge_core.Tensor(array)
+        aidge_tensor.set_backend(backend)
+        aidge_tensor.set_datatype(aidge_core.dtype.float32)
+        aidge_tensors.append(aidge_tensor)
 
 
 # --------------------------------------------------------------
@@ -260,7 +275,6 @@ Load the .onnx model and perform some usual graph modifications :
     - Expand the metaOperators to perform the desired fusions.
 """
 
-
 # Define the target path and filename
 file_url = "https://huggingface.co/EclipseAidge/resnet18/resolve/main/resnet18_imagenet_1k.onnx?download=true"
 file_path = Path(MODEL_NAME + "_imagenet_1k.onnx")
-- 
GitLab


From b7e0504c2f1a5e2979b890b1c117aca145fe6ab9 Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 16:49:41 +0000
Subject: [PATCH 89/93] Add aidge_onnx dependencie for tests.

---
 .gitlab-ci.yml | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ba0bf4b..f67743f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,12 +12,26 @@ stages:
   - deploy
 
 include:
-  - project: 'eclipse/aidge/gitlab_shared_files' 
+  - project: 'eclipse/aidge/gitlab_shared_files'
     ref: 'main'
-    file: 
+    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' 
+      # - '.gitlab/ci/windows_python.gitlab-ci.yml'
+
+test:ubuntu_python:
+  before_script:
+    - !reference [.setup:test:ubuntu_python, before_script]
+    - DEPS_NAMES=("aidge_onnx")
+    - DEPENDENCY_JOB="build:ubuntu_python"
+    - !reference [.ubuntu:download:artifacts, script]
+
+coverage:ubuntu_python:
+  before_script:
+    - !reference [.setup:coverage:ubuntu_python, before_script]
+    - DEPS_NAMES=("aidge_onnx")
+    - DEPENDENCY_JOB="build:ubuntu_python"
+    - !reference [.ubuntu:download:artifacts, script]
\ No newline at end of file
-- 
GitLab


From 782effd017bff0eaa3449f7bfdd86e5def602ed8 Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 17:17:06 +0000
Subject: [PATCH 90/93] Add aidge_quantization dependencie for tests.

---
 .gitlab-ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f67743f..8566525 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,13 +25,13 @@ include:
 test:ubuntu_python:
   before_script:
     - !reference [.setup:test:ubuntu_python, before_script]
-    - DEPS_NAMES=("aidge_onnx")
+    - DEPS_NAMES=("aidge_onnx" "aidge_quantization")
     - DEPENDENCY_JOB="build:ubuntu_python"
     - !reference [.ubuntu:download:artifacts, script]
 
 coverage:ubuntu_python:
   before_script:
     - !reference [.setup:coverage:ubuntu_python, before_script]
-    - DEPS_NAMES=("aidge_onnx")
+    - DEPS_NAMES=("aidge_onnx" "aidge_quantization")
     - DEPENDENCY_JOB="build:ubuntu_python"
     - !reference [.ubuntu:download:artifacts, script]
\ No newline at end of file
-- 
GitLab


From de2931551f9aa2b707e16627bedf37e1a3672f71 Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Tue, 13 May 2025 17:58:20 +0000
Subject: [PATCH 91/93] Add torch dep for tests.

---
 .gitlab-ci.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8566525..4a443fc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -28,7 +28,8 @@ test:ubuntu_python:
     - DEPS_NAMES=("aidge_onnx" "aidge_quantization")
     - DEPENDENCY_JOB="build:ubuntu_python"
     - !reference [.ubuntu:download:artifacts, script]
-
+    # Need to install extra dependence for tests:
+    - python -m pip install torch torchvision
 coverage:ubuntu_python:
   before_script:
     - !reference [.setup:coverage:ubuntu_python, before_script]
-- 
GitLab


From 626a07fed87cdfff1b47b327a2ce93ae0e845090 Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Wed, 14 May 2025 08:00:34 +0000
Subject: [PATCH 92/93] Clean import in resnet18 script.

---
 examples/export_ResNet18/resnet18.py | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/examples/export_ResNet18/resnet18.py b/examples/export_ResNet18/resnet18.py
index 948a60f..81e3355 100644
--- a/examples/export_ResNet18/resnet18.py
+++ b/examples/export_ResNet18/resnet18.py
@@ -8,7 +8,6 @@ In order for this file to work properly, you should first download the imagenet
 """
 
 import random
-import aidge_core.utils
 import numpy as np
 import os
 import shutil
@@ -35,10 +34,7 @@ from aidge_core.export_utils import remove_optional_inputs, get_node_from_metaop
 
 # Torch (Dataset)
 import torch
-import torch.nn.functional as F
-from torch import nn
-from torchvision import transforms, datasets
-
+from torchvision import transforms
 # Arguments
 import argparse
 
-- 
GitLab


From 773e5565b4d2960bb892b356e680d838a9d19e90 Mon Sep 17 00:00:00 2001
From: cmoineau <cyril.moineau@cea.fr>
Date: Wed, 14 May 2025 09:24:53 +0000
Subject: [PATCH 93/93] Improve example script test to show stdout in failing
 cases.

---
 aidge_export_cpp/unit_tests/test_examples.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/aidge_export_cpp/unit_tests/test_examples.py b/aidge_export_cpp/unit_tests/test_examples.py
index 1ddf659..d3bb406 100644
--- a/aidge_export_cpp/unit_tests/test_examples.py
+++ b/aidge_export_cpp/unit_tests/test_examples.py
@@ -9,8 +9,10 @@ EXAMPLES_DIR = CURRENT_DIR / "../../examples"
 
 # Dictionary of test cases: {id: (script_name, script_args)}
 TEST_CASES = {
-    "lenet-int8-no-args": ("export_LeNet/lenet.py", []),
-    "resnet18-int8-no-args": ("export_ResNet18/resnet18.py", ["--mock_db"])
+    "lenet-no-args": ("export_LeNet/lenet.py", []),
+    "lenet-int8": ("export_LeNet/lenet.py", ["--dtype=int8"]),
+    "resnet18-no-args": ("export_ResNet18/resnet18.py", ["--mock_db"]),
+    "resnet18-int8": ("export_ResNet18/resnet18.py", ["--mock_db", "--dtype=int8"])
 }
 
 def generate_test_cases():
@@ -30,7 +32,7 @@ def test_example_scripts_run_without_error(script_name, script_args):
         capture_output=True,
         text=True
     )
-    assert result.returncode == 0, f"{script_name} failed with error: {result.stderr}"
+    assert result.returncode == 0, f"{script_name} failed with error:\n{result.stderr}\n\nTraceback:\n{result.stdout}"
 
 
 def main():
-- 
GitLab