From 4d839a16f079e18b88f5d853c3bb20cd8e917bb8 Mon Sep 17 00:00:00 2001 From: Tonu Samuel Date: Sat, 27 Jun 2026 09:11:09 +0300 Subject: [PATCH] dnn: SIMD for 13 transcendental activations in the 5.x engine Port of #29377 to 5.x. Wires Log, Erf, Exp, Sin, Cos, Sinh, Cosh, Tan, Softplus, BNLL, Asinh, Acosh, Atanh into the dispatched activation_kernels registry, plus a Layer_Activation perf test. 1.4-12.3x across M4/A76/Threadripper/Xeon, correct to <=5e-7 vs scalar. --- .../dnn/include/opencv2/dnn/all_layers.hpp | 15 +- modules/dnn/perf/perf_layer.cpp | 54 ++++++ .../cpu_kernels/activation_kernels.simd.hpp | 166 ++++++++++++++++++ modules/dnn/src/layers/elementwise_layers.cpp | 104 +++++++++++ 4 files changed, 338 insertions(+), 1 deletion(-) diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index e9d7215baf..64034f2610 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -1027,7 +1027,20 @@ CV__DNN_INLINE_NS_BEGIN ACTIV_GELU, ACTIV_GELU_APPROX, ACTIV_RELU, - ACTIV_CLIP + ACTIV_CLIP, + ACTIV_LOG, + ACTIV_ERF, + ACTIV_EXP, + ACTIV_SIN, + ACTIV_COS, + ACTIV_SINH, + ACTIV_COSH, + ACTIV_TAN, + ACTIV_SOFTPLUS, + ACTIV_BNLL, + ACTIV_ASINH, + ACTIV_ACOSH, + ACTIV_ATANH }; /** Returns a platform-optimized activation function pointer for the given type. diff --git a/modules/dnn/perf/perf_layer.cpp b/modules/dnn/perf/perf_layer.cpp index ad16e9c0ab..32639c1245 100644 --- a/modules/dnn/perf/perf_layer.cpp +++ b/modules/dnn/perf/perf_layer.cpp @@ -1104,4 +1104,58 @@ INSTANTIATE_TEST_CASE_P(/**/, Layer_TopK, /* withWebnn= */ false, /* withCann= */ false)); +// Single-input elementwise activation throughput (the transcendental functors +// vectorized in elementwise_layers.cpp via the activation_kernels registry). +// Inputs are drawn from each function's valid domain so the output stays finite. +struct Layer_Activation : public TestBaseWithParam > +{ + void test_activation(const String& type, float lo, float hi) + { + int backendId = get<0>(GetParam()); + int targetId = get<1>(GetParam()); + + Mat input({N, C, H, W}, CV_32F); + randu(input, lo, hi); + + Net net; + LayerParams lp; + lp.type = type; + lp.name = "testLayer"; + net.addLayerToPrev(lp.name, lp.type, lp); + + net.setPreferableBackend(backendId); + net.setPreferableTarget(targetId); + net.setInput(input); + Mat out = net.forward(); // warmup + + TEST_CYCLE() + { + Mat res = net.forward(); + } + + SANITY_CHECK_NOTHING(); + } + + int N = 8; + int C = 256; + int H = 128; + int W = 100; +}; + +PERF_TEST_P_(Layer_Activation, Log) { test_activation("Log", 0.1f, 10.f); } +PERF_TEST_P_(Layer_Activation, Erf) { test_activation("Erf", -3.f, 3.f); } +PERF_TEST_P_(Layer_Activation, Exp) { test_activation("Exp", -5.f, 5.f); } +PERF_TEST_P_(Layer_Activation, Sin) { test_activation("Sin", -3.f, 3.f); } +PERF_TEST_P_(Layer_Activation, Cos) { test_activation("Cos", -3.f, 3.f); } +PERF_TEST_P_(Layer_Activation, Sinh) { test_activation("Sinh", -5.f, 5.f); } +PERF_TEST_P_(Layer_Activation, Cosh) { test_activation("Cosh", -5.f, 5.f); } +PERF_TEST_P_(Layer_Activation, Tan) { test_activation("Tan", -1.4f, 1.4f); } +PERF_TEST_P_(Layer_Activation, Softplus) { test_activation("Softplus", -5.f, 5.f); } +PERF_TEST_P_(Layer_Activation, BNLL) { test_activation("BNLL", -5.f, 5.f); } +PERF_TEST_P_(Layer_Activation, Asinh) { test_activation("Asinh", -10.f, 10.f); } +PERF_TEST_P_(Layer_Activation, Acosh) { test_activation("Acosh", 1.f, 20.f); } +PERF_TEST_P_(Layer_Activation, Atanh) { test_activation("Atanh", -0.95f, 0.95f); } + +INSTANTIATE_TEST_CASE_P(/**/, Layer_Activation, testing::Values(std::make_tuple(DNN_BACKEND_OPENCV, DNN_TARGET_CPU))); + } // namespace diff --git a/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp b/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp index a59fe1fb56..718c264ef2 100644 --- a/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp +++ b/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp @@ -454,6 +454,159 @@ void softmax_(Mat &dst, const Mat &src, int axis, int axisBias, int axisStep, fl }, nstripes); } +static void activationLog(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i + vlanes <= len; i += vlanes) vx_store(out + i, v_log(vx_load(inp + i))); +#endif + for (; i < len; i++) out[i] = logf(inp[i]); +} +static void activationErf(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i + vlanes <= len; i += vlanes) vx_store(out + i, v_erf(vx_load(inp + i))); +#endif + for (; i < len; i++) out[i] = erff(inp[i]); +} +static void activationExp(const void* input, void* output, size_t len, const float* params) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; + const float nscale = params[0], nshift = params[1]; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 vsc = vx_setall_f32(nscale), vsh = vx_setall_f32(nshift); + for (; i + vlanes <= len; i += vlanes) vx_store(out + i, v_exp(v_fma(vx_load(inp + i), vsc, vsh))); +#endif + for (; i < len; i++) out[i] = expf(inp[i] * nscale + nshift); +} +static void activationSin(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i + vlanes <= len; i += vlanes) vx_store(out + i, v_sin(vx_load(inp + i))); +#endif + for (; i < len; i++) out[i] = sinf(inp[i]); +} +static void activationCos(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i + vlanes <= len; i += vlanes) vx_store(out + i, v_cos(vx_load(inp + i))); +#endif + for (; i < len; i++) out[i] = cosf(inp[i]); +} +static void activationSinh(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 half = vx_setall_f32(0.5f), z = vx_setzero_f32(); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_mul(half, v_sub(v_exp(x), v_exp(v_sub(z, x))))); + } +#endif + for (; i < len; i++) out[i] = sinhf(inp[i]); +} +static void activationCosh(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 half = vx_setall_f32(0.5f), z = vx_setzero_f32(); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_mul(half, v_add(v_exp(x), v_exp(v_sub(z, x))))); + } +#endif + for (; i < len; i++) out[i] = coshf(inp[i]); +} +static void activationTan(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_div(v_sin(x), v_cos(x))); + } +#endif + for (; i < len; i++) out[i] = tanf(inp[i]); +} +static void activationSoftplus(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_log(v_add(one, v_exp(x)))); + } +#endif + for (; i < len; i++) out[i] = logf(1.f + expf(inp[i])); +} +static void activationBNLL(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), z = vx_setzero_f32(); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_add(v_max(x, z), v_log(v_add(one, v_exp(v_sub(z, v_abs(x))))))); + } +#endif + for (; i < len; i++) { float a = inp[i]; out[i] = std::max(a, 0.f) + logf(1.f + expf(-std::abs(a))); } +} +static void activationAsinh(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), z = vx_setzero_f32(); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i), ax = v_abs(x); + v_float32 t = v_log(v_add(ax, v_sqrt(v_add(v_mul(x, x), one)))); + vx_store(out + i, v_select(v_lt(x, z), v_sub(z, t), t)); + } +#endif + for (; i < len; i++) out[i] = asinhf(inp[i]); +} +static void activationAcosh(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_log(v_add(x, v_sqrt(v_sub(v_mul(x, x), one))))); + } +#endif + for (; i < len; i++) out[i] = acoshf(inp[i]); +} +static void activationAtanh(const void* input, void* output, size_t len, const float*) +{ + const float* inp = (const float*)input; float* out = (float*)output; size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), half = vx_setall_f32(0.5f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + vx_store(out + i, v_mul(half, v_log(v_div(v_add(one, x), v_sub(one, x))))); + } +#endif + for (; i < len; i++) out[i] = atanhf(inp[i]); +} + + ActivationFunc getActivationFunc_(int type) { switch (type) { @@ -468,6 +621,19 @@ ActivationFunc getActivationFunc_(int type) case ACTIV_GELU_APPROX: return activationGELUApprox; case ACTIV_RELU: return activationReLU; case ACTIV_CLIP: return activationClip; + case ACTIV_LOG: return activationLog; + case ACTIV_ERF: return activationErf; + case ACTIV_EXP: return activationExp; + case ACTIV_SIN: return activationSin; + case ACTIV_COS: return activationCos; + case ACTIV_SINH: return activationSinh; + case ACTIV_COSH: return activationCosh; + case ACTIV_TAN: return activationTan; + case ACTIV_SOFTPLUS: return activationSoftplus; + case ACTIV_BNLL: return activationBNLL; + case ACTIV_ASINH: return activationAsinh; + case ACTIV_ACOSH: return activationAcosh; + case ACTIV_ATANH: return activationAtanh; default: return nullptr; } } diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 41b9fab5b5..f11fe510f2 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -1496,6 +1496,14 @@ struct BNLLFunctor : public BaseDefaultFunctor { typedef BNLLLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_BNLL); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || @@ -1645,6 +1653,14 @@ struct LogFunctor : public BaseDefaultFunctor { typedef LogLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_LOG); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -1767,6 +1783,14 @@ struct AcoshFunctor : public BaseDefaultFunctor { typedef AcoshLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_ACOSH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -1821,6 +1845,14 @@ struct AsinhFunctor : public BaseDefaultFunctor { typedef AsinhLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_ASINH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -1875,6 +1907,14 @@ struct AtanhFunctor : public BaseDefaultFunctor { typedef AtanhLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_ATANH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -1902,6 +1942,14 @@ struct CosFunctor : public BaseDefaultFunctor { typedef CosLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_COS); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -1929,6 +1977,14 @@ struct CoshFunctor : public BaseDefaultFunctor { typedef CoshLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_COSH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -1956,6 +2012,14 @@ struct ErfFunctor : public BaseDefaultFunctor { typedef ErfLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_ERF); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -2074,6 +2138,14 @@ struct SinFunctor : public BaseDefaultFunctor { typedef SinLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_SIN); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -2101,6 +2173,14 @@ struct SinhFunctor : public BaseDefaultFunctor { typedef SinhLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_SINH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -2128,6 +2208,14 @@ struct SoftplusFunctor : public BaseDefaultFunctor { typedef SoftplusLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_SOFTPLUS); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -2182,6 +2270,14 @@ struct TanFunctor : public BaseDefaultFunctor { typedef TanLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_TAN); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; @@ -2633,6 +2729,14 @@ struct ElementWiseIntDispatch struct ExpFunctor : public BaseDefaultFunctor { typedef ExpLayer Layer; + + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) + return nullptr; + activParams = {normScale, normShift}; + return cv::dnn::getActivationFunc(ACTIV_EXP); + } float base, scale, shift; float normScale, normShift;