COMPMID-3150: Remove padding from NEL2NormalizationLayerKernel

Signed-off-by: Georgios Pinitas <georgios.pinitas@arm.com>
Change-Id: I7ae0d56f1c1f55c7049509b1f80cc07bdc54c8ec
Reviewed-on: https://review.mlplatform.org/c/ml/ComputeLibrary/+/3457
Tested-by: Arm Jenkins <bsgcomp@arm.com>
Reviewed-by: Michele Di Giorgio <michele.digiorgio@arm.com>
Comments-Addressed: Arm Jenkins <bsgcomp@arm.com>
diff --git a/src/core/NEON/kernels/NEL2NormalizeLayerKernel.cpp b/src/core/NEON/kernels/NEL2NormalizeLayerKernel.cpp
index 9900446..226d6e0 100644
--- a/src/core/NEON/kernels/NEL2NormalizeLayerKernel.cpp
+++ b/src/core/NEON/kernels/NEL2NormalizeLayerKernel.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2019 ARM Limited.
+ * Copyright (c) 2017-2020 ARM Limited.
  *
  * SPDX-License-Identifier: MIT
  *
@@ -45,102 +45,87 @@
 template <typename T, int S>
 void l2_normalize_X(const ITensor *in, const ITensor *sum, ITensor *out, float epsilon, const Window &window)
 {
-    /** NEON vector tag type. */
     using ExactTagType = typename wrapper::traits::neon_vector<T, S>::tag_type;
 
-    Window window_sum(window);
-    window_sum.set(Window::DimX, Window::Dimension(0, 0, 0));
+    const int  window_step_x  = 16 / data_size_from_type(in->info()->data_type());
+    const auto window_start_x = static_cast<int>(window.x().start());
+    const auto window_end_x   = static_cast<int>(window.x().end());
 
-    Window in_slice  = window.first_slice_window_1D();
-    Window sum_slice = window_sum.first_slice_window_1D();
+    Window win_collapsed = window.collapse_if_possible(window, Window::DimZ);
+    win_collapsed.set(Window::DimX, Window::Dimension(0, 1, 1));
 
-    do
+    Iterator input_it(in, win_collapsed);
+    Iterator sum_it(sum, win_collapsed);
+    Iterator output_it(out, win_collapsed);
+
+    execute_window_loop(win_collapsed, [&](const Coordinates &)
     {
-        Iterator input_it(in, in_slice);
-        Iterator sum_it(sum, sum_slice);
-        Iterator output_it(out, in_slice);
+        const auto in_ptr  = reinterpret_cast<const T *>(input_it.ptr());
+        const auto out_ptr = reinterpret_cast<T *>(output_it.ptr());
 
-        const auto sum_value           = *reinterpret_cast<const T *>(sum_it.ptr());
-        const auto vec_normalize_value = wrapper::vdup_n(static_cast<T>(1.f / std::sqrt(std::max(sum_value, static_cast<T>(epsilon)))), ExactTagType{});
+        const T    sum_value      = *reinterpret_cast<const T *>(sum_it.ptr());
+        const T    norm_value     = static_cast<T>(1.f) / std::sqrt(std::max(sum_value, static_cast<T>(epsilon)));
+        const auto vec_norm_value = wrapper::vdup_n(norm_value, ExactTagType{});
 
-        execute_window_loop(in_slice, [&](const Coordinates &)
+        // Compute elements over vector steps
+        int x = window_start_x;
+        for(; x <= (window_end_x - window_step_x); x += window_step_x)
         {
-            const auto in_ptr  = reinterpret_cast<const T *>(input_it.ptr());
-            const auto out_ptr = reinterpret_cast<T *>(output_it.ptr());
+            wrapper::vstore(out_ptr + x, wrapper::vmul(wrapper::vloadq(in_ptr + x), vec_norm_value));
+        }
 
-            wrapper::vstore(out_ptr, wrapper::vmul(wrapper::vloadq(in_ptr), vec_normalize_value));
-        },
-        input_it, output_it);
-    }
-    while(window.slide_window_slice_1D(in_slice) && window.slide_window_slice_1D(sum_slice));
+        // Compute left-over elements
+        for(; x < window_end_x; ++x)
+        {
+            out_ptr[x] = in_ptr[x] * norm_value;
+        }
+    },
+    input_it, sum_it, output_it);
 }
 
 template <typename T, int S>
-void l2_normalize_Y(const ITensor *in, const ITensor *sum, ITensor *out, float epsilon, const Window &window)
+void l2_normalize_YZ(const ITensor *in, const ITensor *sum, ITensor *out, float epsilon, const Window &window, size_t axis)
 {
-    /** NEON vector tag type. */
     using ExactTagType = typename wrapper::traits::neon_vector<T, S>::tag_type;
 
-    Window window_sum(window);
-    window_sum.set(Window::DimY, Window::Dimension(0, 0, 0));
+    const int  window_step_x  = 16 / data_size_from_type(in->info()->data_type());
+    const auto window_start_x = static_cast<int>(window.x().start());
+    const auto window_end_x   = static_cast<int>(window.x().end());
 
-    Window in_slice  = window.first_slice_window_2D();
-    Window sum_slice = window_sum.first_slice_window_2D();
+    Window win = window;
+    win.set(Window::DimX, Window::Dimension(0, 1, 1));
 
-    do
+    Window window_sum(win);
+    window_sum.set(axis, Window::Dimension(0, 0, 0));
+
+    Iterator input_it(in, win);
+    Iterator sum_it(sum, window_sum);
+    Iterator output_it(out, win);
+
+    const auto vec_eps = wrapper::vdup_n(static_cast<T>(epsilon), ExactTagType{});
+
+    execute_window_loop(win, [&](const Coordinates &)
     {
-        Iterator input_it(in, in_slice);
-        Iterator sum_it(sum, sum_slice);
-        Iterator output_it(out, in_slice);
+        const auto in_ptr  = reinterpret_cast<const T *>(input_it.ptr());
+        const auto sum_ptr = reinterpret_cast<const T *>(sum_it.ptr());
+        const auto out_ptr = reinterpret_cast<T *>(output_it.ptr());
 
-        auto eps = wrapper::vdup_n(static_cast<T>(epsilon), ExactTagType{});
-
-        execute_window_loop(in_slice, [&](const Coordinates &)
+        // Compute elements over vector steps
+        int x = window_start_x;
+        for(; x <= (window_end_x - window_step_x); x += window_step_x)
         {
-            const auto in_ptr  = reinterpret_cast<const T *>(input_it.ptr());
-            const auto sum_ptr = reinterpret_cast<const T *>(sum_it.ptr());
-            const auto out_ptr = reinterpret_cast<T *>(output_it.ptr());
+            const auto vec_norm_value = wrapper::vinvsqrt(wrapper::vmax(wrapper::vloadq(sum_ptr + x), vec_eps));
+            wrapper::vstore(out_ptr + x, wrapper::vmul(wrapper::vloadq(in_ptr + x), vec_norm_value));
+        }
 
-            const auto vec_normalize_value = wrapper::vinvsqrt(wrapper::vmax(wrapper::vloadq(sum_ptr), eps));
-            wrapper::vstore(out_ptr, wrapper::vmul(wrapper::vloadq(in_ptr), vec_normalize_value));
-        },
-        input_it, sum_it, output_it);
-    }
-    while(window.slide_window_slice_2D(in_slice) && window.slide_window_slice_2D(sum_slice));
-}
-
-template <typename T, int S>
-void l2_normalize_Z(const ITensor *in, const ITensor *sum, ITensor *out, float epsilon, const Window &window)
-{
-    /** NEON vector tag type. */
-    using ExactTagType = typename wrapper::traits::neon_vector<T, S>::tag_type;
-
-    Window window_sum(window);
-    window_sum.set(Window::DimZ, Window::Dimension(0, 0, 0));
-
-    Window in_slice  = window.first_slice_window_3D();
-    Window sum_slice = window_sum.first_slice_window_3D();
-
-    do
-    {
-        Iterator input_it(in, in_slice);
-        Iterator sum_it(sum, sum_slice);
-        Iterator output_it(out, in_slice);
-
-        auto eps = wrapper::vdup_n(static_cast<T>(epsilon), ExactTagType{});
-
-        execute_window_loop(in_slice, [&](const Coordinates &)
+        // Compute left-over elements
+        for(; x < window_end_x; ++x)
         {
-            const auto in_ptr  = reinterpret_cast<const T *>(input_it.ptr());
-            const auto sum_ptr = reinterpret_cast<const T *>(sum_it.ptr());
-            const auto out_ptr = reinterpret_cast<T *>(output_it.ptr());
-
-            const auto vec_normalize_value = wrapper::vinvsqrt(wrapper::vmax(wrapper::vloadq(sum_ptr), eps));
-            wrapper::vstore(out_ptr, wrapper::vmul(wrapper::vloadq(in_ptr), vec_normalize_value));
-        },
-        input_it, sum_it, output_it);
-    }
-    while(window.slide_window_slice_3D(in_slice) && window.slide_window_slice_3D(sum_slice));
+            const T norm_value = static_cast<T>(1.f) / std::sqrt(std::max(sum_ptr[x], static_cast<T>(epsilon)));
+            out_ptr[x]         = in_ptr[x] * norm_value;
+        }
+    },
+    input_it, sum_it, output_it);
 }
 
 Status validate_arguments(const ITensorInfo *input, const ITensorInfo *sum, const ITensorInfo *output, int axis, float epsilon)
@@ -170,27 +155,19 @@
     return Status{};
 }
 
-std::tuple<Status, Window> validate_and_configure_window(ITensorInfo *input, ITensorInfo *sum, ITensorInfo *output, int axis)
+std::tuple<Status, Window> validate_and_configure_window(ITensorInfo *input, ITensorInfo *output)
 {
-    const uint32_t actual_axis = wrap_around(axis, max_input_tensor_dim);
-    const unsigned int num_elems_processed_per_iteration     = 16 / data_size_from_type(input->data_type());
-    const unsigned int num_elems_processed_per_iteration_sum = (actual_axis == 0) ? 1 : num_elems_processed_per_iteration;
-
-    Window win = calculate_max_window(*input, Steps(num_elems_processed_per_iteration));
+    Window win = calculate_max_window(*input, Steps());
 
     // Output auto initialization if not yet initialized
     auto_init_if_empty(*output, input->tensor_shape(), 1, input->data_type());
 
-    AccessWindowHorizontal input_access(input, 0, num_elems_processed_per_iteration);
-    AccessWindowHorizontal sum_access(sum, 0, num_elems_processed_per_iteration_sum);
-    AccessWindowHorizontal output_access(output, 0, num_elems_processed_per_iteration);
+    // NEL2NormalizeLayerKernel doesn't need padding so update_window_and_padding() can be skipped
+    Coordinates coord;
+    coord.set_num_dimensions(output->num_dimensions());
+    output->set_valid_region(ValidRegion(coord, output->tensor_shape()));
 
-    bool window_changed = update_window_and_padding(win, input_access, sum_access, output_access);
-    output_access.set_valid_region(win, input->valid_region());
-
-    Status err = (window_changed) ? ARM_COMPUTE_CREATE_ERROR(ErrorCode::RUNTIME_ERROR, "Insufficient Padding!") : Status{};
-
-    return std::make_tuple(err, win);
+    return std::make_tuple(Status{}, win);
 }
 } // namespace
 
@@ -204,14 +181,14 @@
     ARM_COMPUTE_ERROR_ON_NULLPTR(input, sum, output);
     ARM_COMPUTE_ERROR_THROW_ON(validate_arguments(input->info(), sum->info(), output->info(), axis, epsilon));
 
-    _input   = input;
-    _sum     = sum;
-    _output  = output;
-    _actual_axis    = wrap_around(axis, max_input_tensor_dim);
-    _epsilon = epsilon;
+    _input       = input;
+    _sum         = sum;
+    _output      = output;
+    _actual_axis = wrap_around(axis, max_input_tensor_dim);
+    _epsilon     = epsilon;
 
     // Configure kernel window
-    auto win_config = validate_and_configure_window(_input->info(), _sum->info(), _output->info(), axis);
+    auto win_config = validate_and_configure_window(_input->info(), _output->info());
     ARM_COMPUTE_ERROR_THROW_ON(std::get<0>(win_config));
 
     INEKernel::configure(std::get<1>(win_config));
@@ -220,7 +197,7 @@
 Status NEL2NormalizeLayerKernel::validate(const ITensorInfo *input, const ITensorInfo *sum, const ITensorInfo *output, int axis, float epsilon)
 {
     ARM_COMPUTE_RETURN_ON_ERROR(validate_arguments(input, sum, output, axis, epsilon));
-    ARM_COMPUTE_RETURN_ON_ERROR(std::get<0>(validate_and_configure_window(input->clone().get(), sum->clone().get(), output->clone().get(), axis)));
+    ARM_COMPUTE_RETURN_ON_ERROR(std::get<0>(validate_and_configure_window(input->clone().get(), output->clone().get())));
 
     return Status{};
 }
@@ -231,55 +208,23 @@
     ARM_COMPUTE_ERROR_ON_UNCONFIGURED_KERNEL(this);
     ARM_COMPUTE_ERROR_ON_INVALID_SUBWINDOW(INEKernel::window(), window);
 
-    switch(_actual_axis)
+    if(_actual_axis > 2)
     {
-        case 0:
-            switch(_input->info()->data_type())
-            {
-                case DataType::F32:
-                    l2_normalize_X<float, 4>(_input, _sum, _output, _epsilon, window);
-                    break;
-#ifdef __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
-                case DataType::F16:
-                    l2_normalize_X<float16_t, 8>(_input, _sum, _output, _epsilon, window);
-                    break;
-#endif // __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
-                default:
-                    ARM_COMPUTE_ERROR("Not implemented");
-            }
+        ARM_COMPUTE_ERROR("Unsupported normalization axis");
+    }
+
+    switch(_input->info()->data_type())
+    {
+        case DataType::F32:
+            (_actual_axis == Window::DimX) ? l2_normalize_X<float, 4>(_input, _sum, _output, _epsilon, window) : l2_normalize_YZ<float, 4>(_input, _sum, _output, _epsilon, window, _actual_axis);
             break;
-        case 1:
-            switch(_input->info()->data_type())
-            {
-                case DataType::F32:
-                    l2_normalize_Y<float, 4>(_input, _sum, _output, _epsilon, window);
-                    break;
 #ifdef __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
-                case DataType::F16:
-                    l2_normalize_Y<float16_t, 8>(_input, _sum, _output, _epsilon, window);
-#endif // __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
-                    break;
-                default:
-                    ARM_COMPUTE_ERROR("Not implemented");
-            }
+        case DataType::F16:
+            (_actual_axis == Window::DimX) ? l2_normalize_X<float16_t, 8>(_input, _sum, _output, _epsilon, window) : l2_normalize_YZ<float16_t, 8>(_input, _sum, _output, _epsilon, window, _actual_axis);
             break;
-        case 2:
-            switch(_input->info()->data_type())
-            {
-                case DataType::F32:
-                    l2_normalize_Z<float, 4>(_input, _sum, _output, _epsilon, window);
-                    break;
-#ifdef __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
-                case DataType::F16:
-                    l2_normalize_Z<float16_t, 8>(_input, _sum, _output, _epsilon, window);
-                    break;
 #endif // __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
-                default:
-                    ARM_COMPUTE_ERROR("Not implemented");
-            }
-            break;
         default:
-            ARM_COMPUTE_ERROR("Unsupported normalization axis");
+            ARM_COMPUTE_ERROR("Not implemented");
     }
 }
 } // namespace arm_compute
diff --git a/tests/validation/NEON/L2NormalizeLayer.cpp b/tests/validation/NEON/L2NormalizeLayer.cpp
index 17147c1..0bbbf2a 100644
--- a/tests/validation/NEON/L2NormalizeLayer.cpp
+++ b/tests/validation/NEON/L2NormalizeLayer.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2019 ARM Limited.
+ * Copyright (c) 2017-2020 ARM Limited.
  *
  * SPDX-License-Identifier: MIT
  *
@@ -99,7 +99,7 @@
 TEST_SUITE(FP32)
 FIXTURE_DATA_TEST_CASE(RunSmall, NEL2NormalizeLayerFixture<float>, framework::DatasetMode::PRECOMMIT,
                        combine(combine(combine(combine(datasets::SmallShapes(), framework::dataset::make("DataType", DataType::F32)), framework::dataset::make("DataLayout", { DataLayout::NCHW, DataLayout::NHWC })),
-                                       framework::dataset::make("Axis", { -1, 0, 2 })),
+                                       framework::dataset::make("Axis", { -1, 0, 1, 2 })),
                                framework::dataset::make("Epsilon", { 1e-12 })))
 {
     // Validate output
@@ -120,7 +120,7 @@
 TEST_SUITE(FP16)
 FIXTURE_DATA_TEST_CASE(RunSmall, NEL2NormalizeLayerFixture<half>, framework::DatasetMode::PRECOMMIT,
                        combine(combine(combine(combine(datasets::SmallShapes(), framework::dataset::make("DataType", DataType::F16)), framework::dataset::make("DataLayout", { DataLayout::NCHW, DataLayout::NHWC })),
-                                       framework::dataset::make("Axis", { -1, 0, 2 })),
+                                       framework::dataset::make("Axis", { -1, 0, 1, 2 })),
                                framework::dataset::make("Epsilon", { 1e-12 })))
 {
     // Validate output