From 1f60b19b179f0145a505703c07a2dfb8bb540bbd Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Mon, 5 May 2025 18:33:52 +0200
Subject: [PATCH] Added rounding mechanism

---
 include/aidge/backend/cpu/operator/AvgPoolingImpl.hpp |  1 +
 .../backend/cpu/operator/AvgPoolingImpl_kernels.hpp   |  9 +++++----
 .../backend/cpu/operator/GlobalAveragePoolingImpl.hpp |  2 +-
 .../cpu/operator/GlobalAveragePoolingImpl_kernels.hpp | 10 +++++-----
 include/aidge/backend/cpu/operator/ReduceMeanImpl.hpp |  1 +
 .../backend/cpu/operator/ReduceMeanImpl_kernels.hpp   | 11 ++++++-----
 include/aidge/backend/cpu/operator/RoundImpl.hpp      |  2 +-
 .../aidge/backend/cpu/operator/RoundImpl_kernels.hpp  |  6 +++---
 src/operator/AvgPoolingImpl.cpp                       |  1 +
 src/operator/GlobalAveragePoolingImpl.cpp             |  3 ++-
 src/operator/ReduceMeanImpl.cpp                       |  1 +
 src/operator/RoundImpl.cpp                            |  5 ++++-
 unit_tests/operator/Test_RoundImpl.cpp                |  2 +-
 13 files changed, 32 insertions(+), 22 deletions(-)

diff --git a/include/aidge/backend/cpu/operator/AvgPoolingImpl.hpp b/include/aidge/backend/cpu/operator/AvgPoolingImpl.hpp
index 7c76657f..f805f204 100644
--- a/include/aidge/backend/cpu/operator/AvgPoolingImpl.hpp
+++ b/include/aidge/backend/cpu/operator/AvgPoolingImpl.hpp
@@ -32,6 +32,7 @@ using AvgPoolingImpl2D_cpu = OperatorImpl_cpu<AvgPooling_Op<2>,
         const std::array<DimSize_t, 2>&,
         const std::array<DimSize_t, 4>&,
         bool,
+        RoundingMode,
         const void *,
         void *)>;
 
diff --git a/include/aidge/backend/cpu/operator/AvgPoolingImpl_kernels.hpp b/include/aidge/backend/cpu/operator/AvgPoolingImpl_kernels.hpp
index f9cc13b5..9980d884 100644
--- a/include/aidge/backend/cpu/operator/AvgPoolingImpl_kernels.hpp
+++ b/include/aidge/backend/cpu/operator/AvgPoolingImpl_kernels.hpp
@@ -29,14 +29,14 @@ using Acc_T = typename std::conditional<std::is_floating_point<T>::value, T, dou
 
 template <typename T>
 typename std::enable_if<std::is_floating_point<T>::value, T>::type
-castFromFloat(T value) {
+castFromFloat(T value, RoundingMode /*roundingMode*/) {
   return value;
 }
 
 template <typename T>
 typename std::enable_if<!std::is_floating_point<T>::value, T>::type
-castFromFloat(double value) {
-  return static_cast<T>(std::nearbyint(value));
+castFromFloat(double value, RoundingMode roundingMode) {
+  return static_cast<T>(round(value, roundingMode));
 }
 
 /**
@@ -54,6 +54,7 @@ void AvgPoolingImpl2D_cpu_forward_kernel(const std::array<DimSize_t, 2>& strideD
                                         const std::array<DimSize_t, 2>& dilations,
                                         const std::array<DimSize_t, 4> &dims,
                                         bool ceilMode,
+                                        RoundingMode roundingMode,
                                         const void *input_,
                                         void *output_) {
     const I *input = static_cast<const I *>(input_);
@@ -115,7 +116,7 @@ void AvgPoolingImpl2D_cpu_forward_kernel(const std::array<DimSize_t, 2>& strideD
                         }
                     }
 
-                    output[oIndexFull] = count > 0 ? castFromFloat<O>(sum / count) : 0;
+                    output[oIndexFull] = count > 0 ? castFromFloat<O>(sum / count, roundingMode) : 0;
                 }
             }
         }
diff --git a/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl.hpp b/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl.hpp
index a71174c0..24561ed2 100644
--- a/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl.hpp
+++ b/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl.hpp
@@ -22,7 +22,7 @@
 namespace Aidge {
 // Operator implementation entry point for the backend
 using GlobalAveragePoolingImpl_cpu = OperatorImpl_cpu<GlobalAveragePooling_Op,
-    void(const std::shared_ptr<Tensor>&, void *)>;
+    void(RoundingMode, const std::shared_ptr<Tensor>&, void *)>;
 
 // Implementation entry point registration to Operator
 REGISTRAR(GlobalAveragePooling_Op, "cpu", Aidge::GlobalAveragePoolingImpl_cpu::create);
diff --git a/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl_kernels.hpp b/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl_kernels.hpp
index 3cab0ad9..8fe83370 100644
--- a/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl_kernels.hpp
+++ b/include/aidge/backend/cpu/operator/GlobalAveragePoolingImpl_kernels.hpp
@@ -45,18 +45,18 @@ static stableMean(const T* vec, std::size_t size) {
 
 template <typename T>
 typename std::enable_if_t<std::is_floating_point<T>::value, T>
-static castFromFloat(T value) {
+static castFromFloat(T value, RoundingMode /*roundingMode*/) {
     return value;
 }
 
 template <typename T>
 typename std::enable_if_t<!std::is_floating_point<T>::value, T>
-static castFromFloat(double value) {
-    return static_cast<T>(std::nearbyint(value));
+static castFromFloat(double value, RoundingMode roundingMode) {
+    return static_cast<T>(round(value, roundingMode));
 }
 
 template <DataType DT_I, DataType DT_O = DT_I>
-void GlobalAveragePoolingImpl_cpu_forward_kernel(const std::shared_ptr<Tensor>& inputTensor, void *output_) {
+void GlobalAveragePoolingImpl_cpu_forward_kernel(RoundingMode roundingMode, const std::shared_ptr<Tensor>& inputTensor, void *output_) {
 
     // computation
     using I = cpptype_t<DT_I>;
@@ -81,7 +81,7 @@ void GlobalAveragePoolingImpl_cpu_forward_kernel(const std::shared_ptr<Tensor>&
         for (int channel = 0; channel < static_cast<int>(dims[1]); ++channel) {
             const I *filter_start = std::next(
                 input, (batch * in_batch_nb_elems) + (channel * in_channel_nb_elems));
-            output[batch * out_batch_nb_elems + channel] = castFromFloat<O>(stableMean<I>(filter_start, in_channel_nb_elems));
+            output[batch * out_batch_nb_elems + channel] = castFromFloat<O>(stableMean<I>(filter_start, in_channel_nb_elems), roundingMode);
         }
     }
 }
diff --git a/include/aidge/backend/cpu/operator/ReduceMeanImpl.hpp b/include/aidge/backend/cpu/operator/ReduceMeanImpl.hpp
index d6c60c35..993e9941 100644
--- a/include/aidge/backend/cpu/operator/ReduceMeanImpl.hpp
+++ b/include/aidge/backend/cpu/operator/ReduceMeanImpl.hpp
@@ -26,6 +26,7 @@ namespace Aidge {
 using ReduceMeanImpl_cpu = OperatorImpl_cpu<ReduceMean_Op,
     void(const std::vector<std::int32_t>&,
                             DimSize_t,
+                            RoundingMode,
                             const std::vector<DimSize_t>&,
                             const void *,
                             void *)>;
diff --git a/include/aidge/backend/cpu/operator/ReduceMeanImpl_kernels.hpp b/include/aidge/backend/cpu/operator/ReduceMeanImpl_kernels.hpp
index 73aa283d..eea790d5 100644
--- a/include/aidge/backend/cpu/operator/ReduceMeanImpl_kernels.hpp
+++ b/include/aidge/backend/cpu/operator/ReduceMeanImpl_kernels.hpp
@@ -52,19 +52,20 @@ stableMean(const T* vec, std::size_t len, std::size_t stride) {
 
 template <typename T>
 typename std::enable_if_t<std::is_floating_point<T>::value, T>
-castFromFloat(T value) {
+castFromFloat(T value, RoundingMode /*roundingMode*/) {
   return value;
 }
 
 template <typename T>
 typename std::enable_if_t<!std::is_floating_point<T>::value, T>
-castFromFloat(double value) {
-  return static_cast<T>(std::nearbyint(value));
+castFromFloat(double value, RoundingMode roundingMode) {
+  return static_cast<T>(round(value, roundingMode));
 }
 
 template <class I, class O>
 void ReduceMeanImpl_cpu_forward_kernel(const std::vector<std::int32_t>& axes,
                                     DimSize_t /*keepDims*/,
+                                    RoundingMode roundingMode,
                                     const std::vector<DimSize_t>& inputDims,
                                     const void* input_,
                                     void* output_) {
@@ -87,7 +88,7 @@ void ReduceMeanImpl_cpu_forward_kernel(const std::vector<std::int32_t>& axes,
             for (std::size_t post = 0; post < stride_post; ++post) {
                 const std::size_t idx_i = pre * dim_i * stride_post + post;
                 const std::size_t idx_o = pre * stride_post + post;
-                output[idx_o]  = castFromFloat<O>(stableMean(input + idx_i, dim_i, stride_post));
+                output[idx_o]  = castFromFloat<O>(stableMean(input + idx_i, dim_i, stride_post), roundingMode);
             }
         }
     } else {
@@ -133,7 +134,7 @@ void ReduceMeanImpl_cpu_forward_kernel(const std::vector<std::int32_t>& axes,
         }
 
         std::transform(inputAccumulation, inputAccumulation + outputElements, output,
-            [](auto value) { return castFromFloat<O>(value); });
+            [roundingMode](auto value) { return castFromFloat<O>(value, roundingMode); });
         if (outputAccumulation) {
             delete[] outputAccumulation;
         }
diff --git a/include/aidge/backend/cpu/operator/RoundImpl.hpp b/include/aidge/backend/cpu/operator/RoundImpl.hpp
index c595e251..02e35f74 100644
--- a/include/aidge/backend/cpu/operator/RoundImpl.hpp
+++ b/include/aidge/backend/cpu/operator/RoundImpl.hpp
@@ -25,7 +25,7 @@
 namespace Aidge {
 // Operator implementation entry point for the backend
 using RoundImpl_cpu = OperatorImpl_cpu<Round_Op,
-    void(const std::size_t, const void*, void*)>;
+    void(RoundingMode, const std::size_t, const void*, void*)>;
 
 // Implementation entry point registration to Operator
 REGISTRAR(Round_Op, "cpu", Aidge::RoundImpl_cpu::create);
diff --git a/include/aidge/backend/cpu/operator/RoundImpl_kernels.hpp b/include/aidge/backend/cpu/operator/RoundImpl_kernels.hpp
index 7ac4319b..03304347 100644
--- a/include/aidge/backend/cpu/operator/RoundImpl_kernels.hpp
+++ b/include/aidge/backend/cpu/operator/RoundImpl_kernels.hpp
@@ -21,7 +21,8 @@
 
 namespace Aidge {
 template <class I, class O>
-void RoundImpl_cpu_forward_kernel(const std::size_t inputLength,
+void RoundImpl_cpu_forward_kernel(RoundingMode roundingMode,
+                                     const std::size_t inputLength,
                                      const void* input_,
                                      void* output_) {
 
@@ -29,8 +30,7 @@ void RoundImpl_cpu_forward_kernel(const std::size_t inputLength,
     O* output = static_cast<O*>(output_);
 
     for (std::size_t i = 0; i < inputLength; ++i) {
-        //std::round would not work since it doesn't follow the halves rules (See ONNX Round)
-        output[i] = static_cast<O>(std::nearbyint(static_cast<float>(input[i])));
+        output[i] = static_cast<O>(round(input[i], roundingMode));
     }
 }
 
diff --git a/src/operator/AvgPoolingImpl.cpp b/src/operator/AvgPoolingImpl.cpp
index eb5ef87b..53f0f799 100644
--- a/src/operator/AvgPoolingImpl.cpp
+++ b/src/operator/AvgPoolingImpl.cpp
@@ -35,6 +35,7 @@ void Aidge::AvgPoolingImpl2D_cpu::forward() {
                op_.dilations(),
                op_.getInput(0)->template dims<4>(),
                op_.ceilMode(),
+               op_.roundingMode(),
                getCPUPtr(op_.getInput(0)),
                getCPUPtr(op_.getOutput(0)));
 }
diff --git a/src/operator/GlobalAveragePoolingImpl.cpp b/src/operator/GlobalAveragePoolingImpl.cpp
index 1b6d9a06..ba613e55 100644
--- a/src/operator/GlobalAveragePoolingImpl.cpp
+++ b/src/operator/GlobalAveragePoolingImpl.cpp
@@ -38,7 +38,8 @@ void Aidge::GlobalAveragePoolingImpl_cpu::forward()
     const auto impl = Registrar<GlobalAveragePoolingImpl_cpu>::create(getBestMatch(getRequiredSpec()));
 
     // Call kernel
-    impl.forward(op_.getInput(0),
+    impl.forward(op_.roundingMode(),
+               op_.getInput(0),
                op_.getOutput(0)->getImpl()->rawPtr());
 }
 
diff --git a/src/operator/ReduceMeanImpl.cpp b/src/operator/ReduceMeanImpl.cpp
index 62267256..7ad6df0e 100644
--- a/src/operator/ReduceMeanImpl.cpp
+++ b/src/operator/ReduceMeanImpl.cpp
@@ -28,6 +28,7 @@ void Aidge::ReduceMeanImpl_cpu::forward() {
     // Call kernel
     impl.forward(op_.axes(),
                 op_.keepDims(),
+                op_.roundingMode(),
                 op_.getInput(0)->dims(),
                 op_.getInput(0)->getImpl()->rawPtr(),
                 op_.getOutput(0)->getImpl()->rawPtr());
diff --git a/src/operator/RoundImpl.cpp b/src/operator/RoundImpl.cpp
index 6f19f064..e18dbdc7 100644
--- a/src/operator/RoundImpl.cpp
+++ b/src/operator/RoundImpl.cpp
@@ -22,6 +22,8 @@
 
 template <>
 void Aidge::RoundImpl_cpu::forward() {
+    const Round_Op& op_ = dynamic_cast<const Round_Op&>(mOp);
+
     std::shared_ptr<Tensor> in0 = std::static_pointer_cast<Tensor>(mOp.getRawInput(0));
     std::shared_ptr<Tensor> out0 = std::static_pointer_cast<Tensor>(mOp.getRawOutput(0));
     AIDGE_ASSERT(in0, "missing input #0");
@@ -30,7 +32,8 @@ void Aidge::RoundImpl_cpu::forward() {
     const auto impl = Registrar<RoundImpl_cpu>::create(getBestMatch(getRequiredSpec()));
 
     // Call kernel
-    impl.forward(in0->size(),
+    impl.forward(op_.roundingMode(),
+        in0->size(),
         getCPUPtr(mOp.getRawInput(0)),
         getCPUPtr(mOp.getRawOutput(0)));
 }
diff --git a/unit_tests/operator/Test_RoundImpl.cpp b/unit_tests/operator/Test_RoundImpl.cpp
index e658b061..1c93eb5e 100644
--- a/unit_tests/operator/Test_RoundImpl.cpp
+++ b/unit_tests/operator/Test_RoundImpl.cpp
@@ -83,7 +83,7 @@ TEST_CASE("[cpu/operator] Round_Test", "[Round][CPU]") {
 
                 for (std::size_t i = 0; i < nb_elements; ++i) {
                     array0[i] = valueDist(gen);
-                    result[i] = std::nearbyint(array0[i]);
+                    result[i] = std::round(array0[i]);
 
                 }
 
-- 
GitLab