From 74829b1ad22fdc5cd915bd0ec1bba5a4c20cfe08 Mon Sep 17 00:00:00 2001 From: Pawel Raasz Date: Thu, 16 May 2024 08:38:44 +0200 Subject: [PATCH] [core] Use custom functions in reference binary ops impl (#24444) ### Details: - Custom function used in binary element wise operators to reduce bin size (around 130k). ### Tickets: - [CVS-138266](https://jira.devtools.intel.com/browse/CVS-138266) --- .../include/openvino/reference/add.hpp | 11 ++- .../include/openvino/reference/and.hpp | 12 ++- .../openvino/reference/bitwise_and.hpp | 35 ++++---- .../include/openvino/reference/bitwise_or.hpp | 34 +++---- .../openvino/reference/bitwise_xor.hpp | 35 ++++---- .../include/openvino/reference/divide.hpp | 89 +++++++------------ .../include/openvino/reference/or.hpp | 11 ++- .../openvino/reference/scatter_nd_update.hpp | 28 ++---- .../include/openvino/reference/select.hpp | 29 ++---- .../openvino/reference/squared_difference.hpp | 11 ++- .../include/openvino/reference/subtract.hpp | 12 ++- 11 files changed, 131 insertions(+), 176 deletions(-) diff --git a/src/core/reference/include/openvino/reference/add.hpp b/src/core/reference/include/openvino/reference/add.hpp index 0cc1e076f1e1f5..4156c9b7265147 100644 --- a/src/core/reference/include/openvino/reference/add.hpp +++ b/src/core/reference/include/openvino/reference/add.hpp @@ -11,10 +11,17 @@ namespace ov { namespace reference { +namespace func { +// Usage of custom function instead of std::plus gives smaller binary size. +template +constexpr T add(const T a, const T b) { + return a + b; +} +} // namespace func template void add(const T* arg0, const T* arg1, T* out, const size_t count) { - std::transform(arg0, std::next(arg0, count), arg1, out, std::plus()); + std::transform(arg0, std::next(arg0, count), arg1, out, func::add); } /** @@ -34,7 +41,7 @@ void add(const T* arg0, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::plus()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::add); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/and.hpp b/src/core/reference/include/openvino/reference/and.hpp index 5dd23c8eeab6f0..8643908f91c90a 100644 --- a/src/core/reference/include/openvino/reference/and.hpp +++ b/src/core/reference/include/openvino/reference/and.hpp @@ -12,9 +12,17 @@ namespace ov { namespace reference { +namespace func { +// Usage of custom function instead of std::logical_and gives smaller binary size. +template +constexpr T logical_and(const T a, const T b) { + return static_cast(a) && static_cast(b); +} +} // namespace func + template void logical_and(const T* arg0, const T* arg1, T* out, size_t count) { - std::transform(arg0, std::next(arg0, count), arg1, out, std::logical_and()); + std::transform(arg0, std::next(arg0, count), arg1, out, func::logical_and); } /** @@ -34,7 +42,7 @@ void logical_and(const T* arg0, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::logical_and()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::logical_and); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/bitwise_and.hpp b/src/core/reference/include/openvino/reference/bitwise_and.hpp index 56a314c3aee994..b38334b99b6925 100644 --- a/src/core/reference/include/openvino/reference/bitwise_and.hpp +++ b/src/core/reference/include/openvino/reference/bitwise_and.hpp @@ -7,30 +7,24 @@ #include #include +#include "openvino/reference/and.hpp" #include "openvino/reference/autobroadcast_binop.hpp" namespace ov { namespace reference { -/** - * @brief Reference implementation of binary elementwise bitwise AND operator. - * - * @param arg0 Pointer to input 0 data. - * @param arg1 Pointer to input 1 data. - * @param out Pointer to output data. - * @param arg_shape0 Input 0 shape. - * @param arg_shape1 Input 1 shape. - * @param broadcast_spec Broadcast specification mode. - */ -template ::type, char>::value>::type* = nullptr> +namespace func { // Check for char datatype used by ov::element::boolean -void bitwise_and(const T* arg0, - const T* arg1, - T* out, - const Shape& arg0_shape, - const Shape& arg1_shape, - const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::bit_and()); +template ::type, char>::value>::type* = nullptr> +constexpr T bit_and(const T a, const T b) { + return logical_and(a, b); } + +template ::type, char>::value>::type* = nullptr> +constexpr T bit_and(const T a, const T b) { + return a & b; +} +} // namespace func + /** * @brief Reference implementation of binary elementwise bitwise AND operator. * @@ -41,14 +35,15 @@ void bitwise_and(const T* arg0, * @param arg_shape1 Input 1 shape. * @param broadcast_spec Broadcast specification mode. */ -template ::type, char>::value>::type* = nullptr> +template +// Check for char datatype used by ov::element::boolean void bitwise_and(const T* arg0, const T* arg1, T* out, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::bit_and()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::bit_and); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/bitwise_or.hpp b/src/core/reference/include/openvino/reference/bitwise_or.hpp index 45bf7857a8ac57..17c08ab1c02fcc 100644 --- a/src/core/reference/include/openvino/reference/bitwise_or.hpp +++ b/src/core/reference/include/openvino/reference/bitwise_or.hpp @@ -8,29 +8,23 @@ #include #include "openvino/reference/autobroadcast_binop.hpp" +#include "openvino/reference/or.hpp" namespace ov { namespace reference { -/** - * @brief Reference implementation of binary elementwise bitwise OR operator. - * - * @param arg0 Pointer to input 0 data. - * @param arg1 Pointer to input 1 data. - * @param out Pointer to output data. - * @param arg_shape0 Input 0 shape. - * @param arg_shape1 Input 1 shape. - * @param broadcast_spec Broadcast specification mode. - */ -template ::type, char>::value>::type* = nullptr> +namespace func { // Check for char datatype used by ov::element::boolean -void bitwise_or(const T* arg0, - const T* arg1, - T* out, - const Shape& arg0_shape, - const Shape& arg1_shape, - const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::bit_or()); +template ::type, char>::value>::type* = nullptr> +constexpr T bit_or(const T a, const T b) { + return logical_or(a, b); } + +template ::type, char>::value>::type* = nullptr> +constexpr T bit_or(const T a, const T b) { + return a | b; +} +} // namespace func + /** * @brief Reference implementation of binary elementwise bitwise OR operator. * @@ -41,14 +35,14 @@ void bitwise_or(const T* arg0, * @param arg_shape1 Input 1 shape. * @param broadcast_spec Broadcast specification mode. */ -template ::type, char>::value>::type* = nullptr> +template void bitwise_or(const T* arg0, const T* arg1, T* out, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::bit_or()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::bit_or); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/bitwise_xor.hpp b/src/core/reference/include/openvino/reference/bitwise_xor.hpp index 3fb0d3e218472c..e49c86c2cc160d 100644 --- a/src/core/reference/include/openvino/reference/bitwise_xor.hpp +++ b/src/core/reference/include/openvino/reference/bitwise_xor.hpp @@ -8,29 +8,23 @@ #include #include "openvino/reference/autobroadcast_binop.hpp" +#include "openvino/reference/xor.hpp" namespace ov { namespace reference { -/** - * @brief Reference implementation of binary elementwise bitwise XOR operator. - * - * @param arg0 Pointer to input 0 data. - * @param arg1 Pointer to input 1 data. - * @param out Pointer to output data. - * @param arg_shape0 Input 0 shape. - * @param arg_shape1 Input 1 shape. - * @param broadcast_spec Broadcast specification mode. - */ -template ::type, char>::value>::type* = nullptr> +namespace func { // Check for char datatype used by ov::element::boolean -void bitwise_xor(const T* arg0, - const T* arg1, - T* out, - const Shape& arg0_shape, - const Shape& arg1_shape, - const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::bit_xor()); +template ::type, char>::value>::type* = nullptr> +constexpr T bit_xor(const T a, const T b) { + return logical_xor(a, b); } + +template ::type, char>::value>::type* = nullptr> +constexpr T bit_xor(const T a, const T b) { + return a ^ b; +} +} // namespace func + /** * @brief Reference implementation of binary elementwise bitwise XOR operator. * @@ -41,14 +35,15 @@ void bitwise_xor(const T* arg0, * @param arg_shape1 Input 1 shape. * @param broadcast_spec Broadcast specification mode. */ -template ::type, char>::value>::type* = nullptr> +template +// Check for char datatype used by ov::element::boolean void bitwise_xor(const T* arg0, const T* arg1, T* out, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::bit_xor()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::bit_xor); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/divide.hpp b/src/core/reference/include/openvino/reference/divide.hpp index 8614799203c203..2b82b03382f21d 100644 --- a/src/core/reference/include/openvino/reference/divide.hpp +++ b/src/core/reference/include/openvino/reference/divide.hpp @@ -16,38 +16,33 @@ namespace ov { namespace reference { +namespace func { + +template +constexpr T div(const T x, const T y) { + return x / y; +} + // NOTE: Execution throws `std::domain_error` if either a non-integral value or an // out-of-bounds value is detected in the input tensor. +template +T try_div(const T x, const T y) { + if (y == 0) { + throw std::domain_error("integer division by zero"); + } + return div(x, y); +} -// In English: return type is void and T must be an integral type. -template -typename std::enable_if::value>::type divide(const T* arg0, - const T* arg1, - T* out, - size_t count, - bool pythondiv) { - if (pythondiv) { - for (size_t i = 0; i < count; i++) { - if (arg1[i] == 0) { - throw std::domain_error("integer division by zero"); - } - T quot = arg0[i] / arg1[i]; - T rem = arg0[i] % arg1[i]; - if ((rem != 0) && ((arg0[i] < 0) != (arg1[i] < 0))) { - out[i] = quot - 1; - } else { - out[i] = quot; - } - } - } else { - for (size_t i = 0; i < count; i++) { - if (arg1[i] == 0) { - throw std::domain_error("integer division by zero"); - } - out[i] = arg0[i] / arg1[i]; - } +template +T try_python_div(const T x, const T y) { + if (y == 0) { + throw std::domain_error("integer division by zero"); } + + T quot = div(x, y); + return (((x < 0) != (y < 0)) && (x % y != 0)) ? quot - T{1} : quot; } +} // namespace func template typename std::enable_if::value>::type divide(const T* arg0, @@ -57,26 +52,8 @@ typename std::enable_if::value>::type divide(const T* arg0, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec, bool pythondiv) { - auto functor = [pythondiv](T x, T y) -> T { - if (pythondiv) { - if (y == 0) { - throw std::domain_error("integer division by zero"); - } - T quot = x / y; - T rem = x % y; - if ((rem != 0) && ((x < 0) != (y < 0))) { - return quot - 1; - } else { - return quot; - } - } else { - if (y == 0) { - throw std::domain_error("integer division by zero"); - } - return x / y; - } - }; - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, functor); + auto div = pythondiv ? func::try_python_div : func::try_div; + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, div); } // In English: return type is void and T must be a standard floating point type, or @@ -84,13 +61,10 @@ typename std::enable_if::value>::type divide(const T* arg0, template typename std::enable_if::value || std::is_same::value || std::is_same::value>::type -divide(const T* arg0, const T* arg1, T* out, size_t count, bool pythondiv) { - (void)pythondiv; - for (size_t i = 0; i < count; i++) { - // TODO: Here we do not check for div by zero, so we'll get +-inf here - // if arg1[i] == 0. Is that the right thing to do? Jury's still out. - out[i] = arg0[i] / arg1[i]; - } +divide(const T* arg0, const T* arg1, T* out, size_t count, bool) { + // TODO: Here we do not check for div by zero, so we'll get +-inf here + // if arg1[i] == 0. Is that the right thing to do? Jury's still out. + std::transform(arg0, arg0 + count, arg1, out, func::div); } template @@ -102,11 +76,8 @@ divide(const T* arg0, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec, - bool pythondiv) { - (void)pythondiv; - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, [](T x, T y) -> T { - return x / y; - }); + bool) { + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::div); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/or.hpp b/src/core/reference/include/openvino/reference/or.hpp index dab6932da4873b..b676bbf4c1f547 100644 --- a/src/core/reference/include/openvino/reference/or.hpp +++ b/src/core/reference/include/openvino/reference/or.hpp @@ -12,10 +12,17 @@ namespace ov { namespace reference { +namespace func { +// Usage of custom function instead of std::logical_or gives smaller binary size. +template +constexpr T logical_or(const T a, const T b) { + return static_cast(a) || static_cast(b); +} +} // namespace func template void logical_or(const T* arg0, const T* arg1, T* out, const size_t count) { - std::transform(arg0, std::next(arg0, count), arg1, out, std::logical_or()); + std::transform(arg0, std::next(arg0, count), arg1, out, func::logical_or); } /** @@ -35,7 +42,7 @@ void logical_or(const T* arg0, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::logical_or()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::logical_or); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/scatter_nd_update.hpp b/src/core/reference/include/openvino/reference/scatter_nd_update.hpp index 9cd1d43cd7cdb2..1c11b091b6ba10 100644 --- a/src/core/reference/include/openvino/reference/scatter_nd_update.hpp +++ b/src/core/reference/include/openvino/reference/scatter_nd_update.hpp @@ -9,40 +9,22 @@ #include "openvino/core/shape.hpp" #include "openvino/op/scatter_nd_update.hpp" +#include "openvino/reference/add.hpp" +#include "openvino/reference/and.hpp" #include "openvino/reference/maximum.hpp" #include "openvino/reference/minimum.hpp" #include "openvino/reference/multiply.hpp" +#include "openvino/reference/or.hpp" +#include "openvino/reference/subtract.hpp" #include "openvino/reference/xor.hpp" #include "utils/span.hpp" namespace ov { namespace reference { -namespace func { -// TODO move this functions to other reference implementations to reduce binary size. Binary for -// ScatterElementsUpdate-12 can also be updated. Ticket: CVS-138266 -template -constexpr T add(const T a, const T b) { - return a + b; -} -template -constexpr T subtract(const T a, const T b) { - return a - b; -} - -template -constexpr T logical_and(const T a, const T b) { - return static_cast(a) && static_cast(b); -} - -template -constexpr T logical_or(const T a, const T b) { - return static_cast(a) || static_cast(b); -} - -} // namespace func namespace scatter_nd_update { template using reduction_function = T (*)(const T, const T); + template ::type, char>::value>::type* = nullptr> reduction_function reduction_functor_for(const ov::op::v15::ScatterNDUpdate::Reduction reduction_type) { diff --git a/src/core/reference/include/openvino/reference/select.hpp b/src/core/reference/include/openvino/reference/select.hpp index fc42f8ab9da63b..84ccfa2d9ebcf5 100644 --- a/src/core/reference/include/openvino/reference/select.hpp +++ b/src/core/reference/include/openvino/reference/select.hpp @@ -11,19 +11,12 @@ namespace ov { namespace reference { -template -void select(const char* arg0, - const T* arg1, - const T* arg2, - T* out, - size_t arg0_count, - size_t arg1_count, - size_t arg2_count, - size_t out_count) { - for (size_t i = 0; i < out_count; i++) { - out[i] = arg0[i % arg0_count] ? arg1[i % arg1_count] : arg2[i % arg2_count]; - } +namespace func { +template +constexpr T select(const bool s, const T x, const T y) { + return s ? x : y; } +} // namespace func template void select(const char* arg0, @@ -34,17 +27,7 @@ void select(const char* arg0, const Shape& arg1_shape, const Shape& arg2_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_select(arg0, - arg1, - arg2, - out, - arg0_shape, - arg1_shape, - arg2_shape, - broadcast_spec, - [](char s, T x, T y) -> T { - return static_cast(s ? x : y); - }); + autobroadcast_select(arg0, arg1, arg2, out, arg0_shape, arg1_shape, arg2_shape, broadcast_spec, func::select); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/squared_difference.hpp b/src/core/reference/include/openvino/reference/squared_difference.hpp index 333f38874592ad..365f935a009a00 100644 --- a/src/core/reference/include/openvino/reference/squared_difference.hpp +++ b/src/core/reference/include/openvino/reference/squared_difference.hpp @@ -11,6 +11,13 @@ namespace ov { namespace reference { +namespace func { +template +constexpr T sq_diff(const T a, const T b) { + return (a - b) * (a - b); +} +} // namespace func + template void squared_difference(const T* arg0, const T* arg1, @@ -18,9 +25,7 @@ void squared_difference(const T* arg0, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, [](T x, T y) -> T { - return (x - y) * (x - y); - }); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::sq_diff); } } // namespace reference } // namespace ov diff --git a/src/core/reference/include/openvino/reference/subtract.hpp b/src/core/reference/include/openvino/reference/subtract.hpp index 20a9cdc2f546e3..2c29579a081a58 100644 --- a/src/core/reference/include/openvino/reference/subtract.hpp +++ b/src/core/reference/include/openvino/reference/subtract.hpp @@ -13,9 +13,17 @@ namespace ov { namespace reference { +namespace func { +// Usage of custom function instead of std::minus gives smaller binary size. +template +constexpr T subtract(const T a, const T b) { + return a - b; +} +} // namespace func + template void subtract(const T* arg0, const T* arg1, T* out, size_t count) { - std::transform(arg0, std::next(arg0, count), arg1, out, std::minus()); + std::transform(arg0, std::next(arg0, count), arg1, out, func::subtract); } /** @@ -35,7 +43,7 @@ void subtract(const T* arg0, const Shape& arg0_shape, const Shape& arg1_shape, const op::AutoBroadcastSpec& broadcast_spec) { - autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, std::minus()); + autobroadcast_binop(arg0, arg1, out, arg0_shape, arg1_shape, broadcast_spec, func::subtract); } } // namespace reference } // namespace ov