From 156b2873425352e4edfb4b2c10f22be8584dd3cb 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 01/19] [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 c2fb048..71f21e7 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 13cbf0517616fd1b388ae8da525d318abec9cf70 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 02/19] [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 478b6a5..28ed5f6 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -113,7 +113,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 8c1b047789032f52c9a6e530b793f0b8244077a6 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 03/19] [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 7b7477d..da1d398 100644
--- a/aidge_export_cpp/export_utils.py
+++ b/aidge_export_cpp/export_utils.py
@@ -174,3 +174,11 @@ def exclude_unwanted_producers(model):
                 if node_type in children_nodes:
                     node.attributes().ignore = True
                     break
+
+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 a4f914cd0861436b4ad078bdbf3c2a623f558434 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 04/19] [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 539a5c99993e357d236ebc86cff8a3cc1a08ba17 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 05/19] [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 28ed5f6..123b980 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 0a57a87daa657dfcdc6ff79e54258c9f3c75e2da 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 06/19] [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 c8c74fccffb4ba23e0ba96996aec760cf11f68be Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:55:57 +0000
Subject: [PATCH 07/19] [Refactor] Change elemwise_op determination

---
 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 416ef61..a43923e 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().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 ef5b11c11149b7ee6fbb1ff9f5190b15f4c236c1 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 08/19] [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 76e61374c4ff4ae5c7ae33faba9c576c80c471a3 Mon Sep 17 00:00:00 2001
From: Mickael GUIBERT <mickael.guibert@cea.fr>
Date: Tue, 1 Apr 2025 07:58:11 +0000
Subject: [PATCH 09/19] [Refactor] Change rescaling resolution using
 rescaling.jinja

---
 aidge_export_cpp/templates/configuration/elemwise_config.jinja | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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 e4317fd73d2004ea77e115f9f82a9638ac9aac28 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 10/19] [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 7801dc6f9f7cfc055a9d84f460a7d3b1385c1530 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 11/19] [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 b063bd160f61e700131475c6d7460f3d4d3271b5 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 12/19] [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 56ecbd4148a0fff9ba2dd3913fdce54c58dd5d20 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 13/19] [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 cea362469356f3de9fabba8b1348a28024726684 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 14/19] [Fix] Add #define to enable or disable the OpenMP
 option for compilation

---
 aidge_export_cpp/kernels/convolution.hpp    | 5 +++--
 aidge_export_cpp/kernels/fullyconnected.hpp | 5 ++++-
 aidge_export_cpp/kernels/leakyrelu.hpp      | 4 +++-
 aidge_export_cpp/kernels/pooling.hpp        | 6 ++++--
 4 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/aidge_export_cpp/kernels/convolution.hpp b/aidge_export_cpp/kernels/convolution.hpp
index 6ea9f05..47f9c19 100644
--- a/aidge_export_cpp/kernels/convolution.hpp
+++ b/aidge_export_cpp/kernels/convolution.hpp
@@ -47,8 +47,9 @@ void convolution_forward(
             : clamp(CHANNELS_HEIGHT + PADDING_Y - (oy * STRIDE_Y),
                     0, DILATED_KERNEL_HEIGHT);
         const int iy = (oy * STRIDE_Y) - PADDING_Y;
-
-#pragma omp parallel for collapse(2)
+#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 -->
diff --git a/aidge_export_cpp/kernels/fullyconnected.hpp b/aidge_export_cpp/kernels/fullyconnected.hpp
index 895ed1c..3af09a6 100644
--- a/aidge_export_cpp/kernels/fullyconnected.hpp
+++ b/aidge_export_cpp/kernels/fullyconnected.hpp
@@ -28,6 +28,7 @@ void fullyconnected_forward (
     // It is only an issue if the FC was after a flatten layer.
     // Otherwise it is not an issue for the other FC because CHANNELS_WIDTH = CHANNELS_HEIGHT = 1
     // Solution: Add a system to check dataformat
+
     for (int och = 0; och < NB_OUTPUTS; och++) {
 
         Bias_T weightedSum = biases[och];
@@ -45,7 +46,9 @@ void fullyconnected_forward (
     }
 /*
 Here the kernel to use with inputs in NHWC and weights in NHWC
-#pragma omp parallel for
+#ifdef _OPENMP
+    #pragma omp parallel for collapse(2)
+#endif
     for (int och = 0; och < NB_OUTPUTS; och++) {
 
         Bias_T weightedSum = biases[och];
diff --git a/aidge_export_cpp/kernels/leakyrelu.hpp b/aidge_export_cpp/kernels/leakyrelu.hpp
index 07352cd..3aa2fd5 100644
--- a/aidge_export_cpp/kernels/leakyrelu.hpp
+++ b/aidge_export_cpp/kernels/leakyrelu.hpp
@@ -11,7 +11,9 @@ void leakyrelu_forward (
     Output_T* __restrict outputs,
     const float negative_slope)
 {
-#pragma omp parallel for
+#ifdef _OPENMP
+    #pragma omp parallel for collapse(2)
+#endif
     for (int i = 0; i < NB_DATA; ++i) {
         if (inputs[i] >= 0) {
             outputs[i] = inputs[i];
diff --git a/aidge_export_cpp/kernels/pooling.hpp b/aidge_export_cpp/kernels/pooling.hpp
index 123b980..8855dae 100644
--- a/aidge_export_cpp/kernels/pooling.hpp
+++ b/aidge_export_cpp/kernels/pooling.hpp
@@ -37,7 +37,9 @@ void pooling_forward(
                     0, POOL_HEIGHT);
         const int iy = (oy * STRIDE_Y) - PADDING_Y;
 
-#pragma omp parallel for collapse(2)
+    #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 -->
@@ -117,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 cf3509dd9a695d2f9f4fda68181c1ca2aed188c2 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 15/19] [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 af529f9..94f5ad3 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 1057b2e01d88277c49b42824ac87fc8abb825174 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 16/19] [Git] Add gitignores

---
 examples/export_LeNet/.gitignore    | 1 +
 examples/export_ResNet18/.gitignore | 6 ++++++
 2 files changed, 7 insertions(+)
 create mode 100644 examples/export_ResNet18/.gitignore

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
new file mode 100644
index 0000000..a6e4e97
--- /dev/null
+++ b/examples/export_ResNet18/.gitignore
@@ -0,0 +1,6 @@
+# Exclude export artefacts
+export_resnet18_int8/
+log_outputs/*
+assets/*
+data/*
+log.txt
-- 
GitLab


From 55b4d0969c1c27ce5e3f3a639fcaa144a2534d58 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 17/19] [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 8855dae..5340bd0 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 3f58cc0196ef98226089c7304258d6b0cffff226 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 18/19] [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 c2cfa8d15e5d4e12a8ea0452e1069c1edca264bc 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 19/19] [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 1ae54d3..138b58c 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 len(self.values.shape) == 4:  # Note: export in HWC
             self.values = np.transpose(self.values, (0, 2, 3, 1))
-- 
GitLab