diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt index 3262b0135c..f1e8132daa 100644 --- a/modules/dnn/CMakeLists.txt +++ b/modules/dnn/CMakeLists.txt @@ -13,6 +13,7 @@ ocv_add_dispatched_file_force_all("layers/cpu_kernels/fast_gemm_kernels" AVX AVX ocv_add_dispatched_file("layers/cpu_kernels/conv2_depthwise" AVX AVX2 NEON NEON_FP16) ocv_add_dispatched_file("layers/cpu_kernels/conv2_kernels" AVX AVX2 NEON NEON_FP16) ocv_add_dispatched_file_force_all("int8layers/conv2_int8_kernels" AVX2) +ocv_add_dispatched_file("layers/cpu_kernels/activation_kernels" AVX AVX2 NEON NEON_FP16) ocv_add_module(dnn opencv_core opencv_imgproc WRAP python java objc js) diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index 7e00e2c0b7..773f7a301f 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -918,6 +918,36 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams& params); }; + /* Activation function pointer type. + Used for fast, platform-optimized activation implementations. + @param input pointer to input data + @param output pointer to output data (can be same as input for in-place) + @param len number of elements + @param params activation-specific parameters (e.g., alpha, beta) + */ + typedef void (*ActivationFunc)(const void* input, void* output, + size_t len, const float* params); + + /** Activation type enumeration for dispatched activation function retrieval. */ + enum ActivationType { + ACTIV_NONE = 0, + ACTIV_MISH, + ACTIV_SWISH, + ACTIV_SIGMOID, + ACTIV_TANH, + ACTIV_ELU, + ACTIV_HARDSWISH, + ACTIV_HARDSIGMOID, + ACTIV_GELU, + ACTIV_GELU_APPROX, + ACTIV_RELU, + ACTIV_CLIP + }; + + /** Returns a platform-optimized activation function pointer for the given type. + The returned function is selected via CPU dispatch for the best available ISA. */ + CV_EXPORTS ActivationFunc getActivationFunc(int activationType); + /* Activations */ class CV_EXPORTS ActivationLayer : public Layer { @@ -932,6 +962,13 @@ CV__DNN_INLINE_NS_BEGIN size_t /*outPlaneSize*/, int /*cn0*/, int /*cn1*/) const {} virtual void forwardSlice(const int8_t* /*src*/, const int8_t* /*lut*/, int8_t* /*dst*/, int /*len*/, size_t /*outPlaneSize*/, int /*cn0*/, int /*cn1*/) const {} + + /** Returns a platform-optimized activation function pointer for this layer. + @return function pointer, or nullptr if not available for the given depth + */ + virtual ActivationFunc getActivationFunc(int /*depth*/, + std::vector& /*activParams*/) const + { return nullptr; } }; class CV_EXPORTS ReLULayer : public ActivationLayer diff --git a/modules/dnn/src/int8layers/conv2_int8_layer.cpp b/modules/dnn/src/int8layers/conv2_int8_layer.cpp index 2f66292294..dad01b6474 100644 --- a/modules/dnn/src/int8layers/conv2_int8_layer.cpp +++ b/modules/dnn/src/int8layers/conv2_int8_layer.cpp @@ -340,7 +340,7 @@ public: if (inpshape != prevInpshape) { cs.initConv(inpshape, wshape0, outshape, ngroups, strides, dilations, pads, auto_pad, ceil_mode, - FAST_ACTIV_NONE, {}); + FAST_ACTIV_NONE, nullptr, {}); prevInpshape = inpshape; } diff --git a/modules/dnn/src/layers/conv2_common.cpp b/modules/dnn/src/layers/conv2_common.cpp index 44c35e64a1..c0eaf3a493 100644 --- a/modules/dnn/src/layers/conv2_common.cpp +++ b/modules/dnn/src/layers/conv2_common.cpp @@ -13,11 +13,14 @@ CV__DNN_INLINE_NS_BEGIN std::string fastActivationToString(FastActivation fastActivation) { - return fastActivation == FAST_ACTIV_RELU ? "ReLU" : - fastActivation == FAST_ACTIV_LEAKY_RELU ? "LeakyReLU" : - fastActivation == FAST_ACTIV_PRELU ? "PReLU" : - fastActivation == FAST_ACTIV_CLIP ? "Clip" : - fastActivation == FAST_ACTIV_NONE ? "None" : format("unknown(%d)", int(fastActivation)); + switch (fastActivation) { + case FAST_ACTIV_NONE: return "None"; + case FAST_ACTIV_RELU: return "ReLU"; + case FAST_ACTIV_LEAKY_RELU: return "LeakyReLU"; + case FAST_ACTIV_PRELU: return "PReLU"; + case FAST_ACTIV_CLIP: return "Clip"; + default: return format("unknown(%d)", int(fastActivation)); + } } AutoPadding getAutoPadding(const LayerParams& params) @@ -175,6 +178,7 @@ void ConvState::initConv(const MatShape& inpshape_, const std::vector& pads_, AutoPadding autoPad, bool ceilMode, FastActivation fastActivation_, + ActivationFunc activationFunc_, const std::vector& activParams_) { nspatialdims = wshape_.dims - 2; @@ -201,7 +205,7 @@ void ConvState::initConv(const MatShape& inpshape_, } fastActivation = fastActivation_; - activation = nullptr; + activation = activationFunc_; activParams = activParams_; CV_Assert(wshape_[0] > 0 && wshape_[1] > 0); diff --git a/modules/dnn/src/layers/conv2_common.hpp b/modules/dnn/src/layers/conv2_common.hpp index bdf8c18d22..4bf8bbe29a 100644 --- a/modules/dnn/src/layers/conv2_common.hpp +++ b/modules/dnn/src/layers/conv2_common.hpp @@ -5,7 +5,7 @@ #ifndef __OPENCV_DNN_LAYERS_CONV2_COMMON_HPP__ #define __OPENCV_DNN_LAYERS_CONV2_COMMON_HPP__ -#include +#include #include namespace cv @@ -33,9 +33,6 @@ enum FastActivation { std::string fastActivationToString(FastActivation fastActivation); -typedef void (*ActivationFunc)(const void* input, void* output, - size_t len, const float* params); - struct ConvState { enum { MAX_CONV_DIMS = 3 }; @@ -67,6 +64,7 @@ struct ConvState const std::vector& pads, AutoPadding autoPad, bool ceilMode, FastActivation fastActivation, + ActivationFunc activationFunc, const std::vector& activParams); // initializes the structure of parameters for 1D/2D/3D diff --git a/modules/dnn/src/layers/conv2_layer.cpp b/modules/dnn/src/layers/conv2_layer.cpp index f206367498..7c9a3fad46 100644 --- a/modules/dnn/src/layers/conv2_layer.cpp +++ b/modules/dnn/src/layers/conv2_layer.cpp @@ -34,6 +34,7 @@ public: ngroups = params.get("group", 1); fusedBatchNorm = false; fastActivation = FAST_ACTIV_NONE; + activationFunc = nullptr; addResidual = false; } @@ -73,10 +74,11 @@ public: strm << "batch_norm: true,\n"; } - if (fastActivation != FAST_ACTIV_NONE || !activ.empty()) { + if (fastActivation != FAST_ACTIV_NONE || activationFunc != nullptr || !activ.empty()) { prindent(strm, indent); strm << "fused_activation: " << (fastActivation != FAST_ACTIV_NONE ? fastActivationToString(fastActivation) : + activationFunc != nullptr ? "ActivationFunc" : activ->type) << ",\n"; } @@ -186,8 +188,10 @@ public: virtual bool fuseActivation(const Ptr& activlayer) override { ActivationLayer* activ_ptr = dynamic_cast(activlayer.get()); - if (!activ_ptr || fastActivation != FAST_ACTIV_NONE || !activ.empty()) + if (!activ_ptr || fastActivation != FAST_ACTIV_NONE || + activationFunc != nullptr || !activ.empty()) return false; + ReLULayer* activRelu = dynamic_cast(activ_ptr); ReLU6Layer* activClip = dynamic_cast(activ_ptr); ChannelsPReLULayer* activPRelu = dynamic_cast(activ_ptr); @@ -211,15 +215,17 @@ public: int nslopes = int(slopes.total()); Mat(1, &nslopes, slopesType, (void*)slopes.data).convertTo(activParams, CV_32F); } else { - //activ = activlayer; - return false; + activationFunc = activ_ptr->getActivationFunc(CV_32F, activParams); + if (!activationFunc) + return false; } return true; } virtual bool fuseAddResidual(Arg residual) CV_OVERRIDE { - if (activ.empty() && fastActivation == FAST_ACTIV_NONE && !addResidual && residual.idx >= 0) { + if (activ.empty() && fastActivation == FAST_ACTIV_NONE && + activationFunc == nullptr && !addResidual && residual.idx >= 0) { addResidual = true; inputs.push_back(residual); return true; @@ -345,7 +351,7 @@ public: if (inpshape != prevInpshape) { cs.initConv(inpshape, wshape0, outshape, ngroups, strides, dilations, pads, auto_pad, ceil_mode, - fastActivation, activParams); + fastActivation, activationFunc, activParams); prevInpshape = inpshape; } @@ -400,6 +406,7 @@ public: ConvState cs; bool fusedBatchNorm; FastActivation fastActivation; + ActivationFunc activationFunc; std::vector activParams; bool addResidual; }; diff --git a/modules/dnn/src/layers/cpu_kernels/activation_kernels.cpp b/modules/dnn/src/layers/cpu_kernels/activation_kernels.cpp new file mode 100644 index 0000000000..e522ac614d --- /dev/null +++ b/modules/dnn/src/layers/cpu_kernels/activation_kernels.cpp @@ -0,0 +1,19 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "../../precomp.hpp" +#include +#include "activation_kernels.simd.hpp" +#include "layers/cpu_kernels/activation_kernels.simd_declarations.hpp" + +namespace cv { namespace dnn { +CV__DNN_INLINE_NS_BEGIN + +ActivationFunc getActivationFunc(int type) +{ + CV_CPU_DISPATCH(getActivationFunc_, (type), CV_CPU_DISPATCH_MODES_ALL); +} + +CV__DNN_INLINE_NS_END +}} diff --git a/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp b/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp new file mode 100644 index 0000000000..161c1e0479 --- /dev/null +++ b/modules/dnn/src/layers/cpu_kernels/activation_kernels.simd.hpp @@ -0,0 +1,321 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// Copyright (C) 2026, BigVision LLC, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include +#include "opencv2/core/hal/intrin.hpp" +#include + +namespace cv { +namespace dnn { +CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN + +cv::dnn::ActivationFunc getActivationFunc_(int type); + +CV_CPU_OPTIMIZATION_NAMESPACE_END +}} + +#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +namespace cv { +namespace dnn { +CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN + +// Mish: x * tanh(softplus(x)) +// Uses numerically stable form: x * (1 + 2*y) / (1 + 2*y + 2*y*y) where y = exp(-x) for large x +static void activationMish(const void* input, void* output, + size_t len, const float* /*params*/) +{ + const float* inp = (const float*)input; + float* out = (float*)output; + const float MISH_THRESHOLD = -36.73f; + size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 v_threshold = vx_setall_f32(MISH_THRESHOLD); + 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); + x = v_select(v_le(x, v_threshold), z, x); + v_float32 y = v_exp(v_sub(z, x)); + v_float32 _2y = v_add(y, y); + v_float32 _2ya1 = v_add(_2y, one); + x = v_div(v_mul(x, _2ya1), v_add(_2ya1, v_mul(_2y, y))); + vx_store(out + i, x); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + if (x <= MISH_THRESHOLD) { out[i] = 0.f; continue; } + float y = expf(-x); + float _2y = 2.f * y; + out[i] = x * (1.f + _2y) / (1.f + _2y + _2y * y); + } +} + +// Swish/SiLU: x / (1 + exp(-x)) +static void activationSwish(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; +#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); + v_float32 t = v_exp(v_sub(z, x)); + t = v_div(x, v_add(one, t)); + vx_store(out + i, t); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + out[i] = x / (1.f + expf(-x)); + } +} + +// Sigmoid: 1 / (1 + exp(-x)) +static void activationSigmoid(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; +#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); + v_float32 t = v_exp(v_sub(z, x)); + t = v_div(one, v_add(one, t)); + vx_store(out + i, t); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + out[i] = 1.f / (1.f + expf(-x)); + } +} + +// TanH: uses v_exp SIMD for (exp(2x)-1)/(exp(2x)+1) +static void activationTanH(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; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), two = vx_setall_f32(2.f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + v_float32 e2x = v_exp(v_mul(two, x)); + v_float32 t = v_div(v_sub(e2x, one), v_add(e2x, one)); + vx_store(out + i, t); + } +#endif + for (; i < len; i++) { + out[i] = tanhf(inp[i]); + } +} + +// ELU: x >= 0 ? x : alpha*(exp(x)-1) +static void activationELU(const void* input, void* output, + size_t len, const float* params) +{ + const float* inp = (const float*)input; + float* out = (float*)output; + float alpha = params[0]; + size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 v_alpha = vx_setall_f32(alpha); + 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); + v_float32 t = v_mul(v_alpha, v_sub(v_exp(x), one)); + x = v_select(v_ge(x, z), x, t); + vx_store(out + i, x); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + out[i] = x >= 0.f ? x : alpha * (expf(x) - 1.f); + } +} + +// HardSwish: x * clip(x/6 + 0.5, 0, 1) +static void activationHardSwish(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; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 zero = vx_setzero_f32(), one = vx_setall_f32(1.f); + v_float32 half = vx_setall_f32(0.5f), sixth = vx_setall_f32(1.f / 6.f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + v_float32 t = v_min(one, v_max(zero, v_add(v_mul(x, sixth), half))); + vx_store(out + i, v_mul(x, t)); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + out[i] = x * std::min(std::max(x / 6.f + 0.5f, 0.f), 1.f); + } +} + +// HardSigmoid: clip(alpha*x + beta, 0, 1) +static void activationHardSigmoid(const void* input, void* output, + size_t len, const float* params) +{ + const float* inp = (const float*)input; + float* out = (float*)output; + float alpha = params[0]; + float beta = params[1]; + size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 v_alpha = vx_setall_f32(alpha), v_beta = vx_setall_f32(beta); + v_float32 zero = vx_setzero_f32(), one = vx_setall_f32(1.f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + x = v_min(one, v_max(zero, v_add(v_mul(v_alpha, x), v_beta))); + vx_store(out + i, x); + } +#endif + for (; i < len; i++) { + out[i] = std::min(std::max(alpha * inp[i] + beta, 0.f), 1.f); + } +} + +// GELU exact: 0.5 * x * (1 + erf(x / sqrt(2))) +static void activationGELU(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; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 half = vx_setall_f32(0.5f), one = vx_setall_f32(1.f); + v_float32 rsqrt2 = vx_setall_f32((float)M_SQRT1_2); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + v_float32 t = v_add(one, v_erf(v_mul(rsqrt2, x))); + vx_store(out + i, v_mul(v_mul(half, x), t)); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + out[i] = 0.5f * x * (1.f + erff(x * (float)M_SQRT1_2)); + } +} + +// GELU approximate: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3))) +static void activationGELUApprox(const void* input, void* output, + size_t len, const float* /*params*/) +{ + const float* inp = (const float*)input; + float* out = (float*)output; + const float sqrt2_pi = 0.7978845834732056f; // sqrt(2/pi) + const float coeff = 0.044715f * sqrt2_pi; + size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 half = vx_setall_f32(0.5f), one = vx_setall_f32(1.f); + v_float32 v_s2pi = vx_setall_f32(sqrt2_pi), v_coeff = vx_setall_f32(coeff); + v_float32 two = vx_setall_f32(2.f); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + // inner = sqrt(2/pi) * x + coeff * x^3 = x * (sqrt(2/pi) + coeff * x^2) + v_float32 inner = v_mul(x, v_add(v_s2pi, v_mul(v_coeff, v_mul(x, x)))); + // tanh via exp: (exp(2*inner)-1)/(exp(2*inner)+1) + v_float32 e2 = v_exp(v_mul(two, inner)); + v_float32 t = v_div(v_sub(e2, one), v_add(e2, one)); + vx_store(out + i, v_mul(v_mul(half, x), v_add(one, t))); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + float inner = x * (sqrt2_pi + coeff * x * x); + out[i] = 0.5f * x * (1.f + tanhf(inner)); + } +} + +// ReLU: max(0, x) or LeakyReLU: x >= 0 ? x : alpha*x +// params[0] = negative slope (0 for plain ReLU) +static void activationReLU(const void* input, void* output, + size_t len, const float* params) +{ + const float* inp = (const float*)input; + float* out = (float*)output; + float alpha = params ? params[0] : 0.f; + size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 v_alpha = vx_setall_f32(alpha), z = vx_setzero_f32(); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + x = v_select(v_ge(x, z), x, v_mul(x, v_alpha)); + vx_store(out + i, x); + } +#endif + for (; i < len; i++) { + float x = inp[i]; + out[i] = x >= 0.f ? x : alpha * x; + } +} + +// Clip: clamp(x, minval, maxval) +// params[0] = minval, params[1] = maxval +static void activationClip(const void* input, void* output, + size_t len, const float* params) +{ + const float* inp = (const float*)input; + float* out = (float*)output; + float minval = params[0]; + float maxval = params[1]; + size_t i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 v_lo = vx_setall_f32(minval), v_hi = vx_setall_f32(maxval); + for (; i + vlanes <= len; i += vlanes) { + v_float32 x = vx_load(inp + i); + x = v_min(v_max(x, v_lo), v_hi); + vx_store(out + i, x); + } +#endif + for (; i < len; i++) { + out[i] = std::min(std::max(inp[i], minval), maxval); + } +} + +ActivationFunc getActivationFunc_(int type) +{ + switch (type) { + case ACTIV_MISH: return activationMish; + case ACTIV_SWISH: return activationSwish; + case ACTIV_SIGMOID: return activationSigmoid; + case ACTIV_TANH: return activationTanH; + case ACTIV_ELU: return activationELU; + case ACTIV_HARDSWISH: return activationHardSwish; + case ACTIV_HARDSIGMOID: return activationHardSigmoid; + case ACTIV_GELU: return activationGELU; + case ACTIV_GELU_APPROX: return activationGELUApprox; + case ACTIV_RELU: return activationReLU; + case ACTIV_CLIP: return activationClip; + default: return nullptr; + } +} + +CV_CPU_OPTIMIZATION_NAMESPACE_END +}} // cv::dnn:: + +#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY diff --git a/modules/dnn/src/layers/cpu_kernels/conv2_depthwise.simd.hpp b/modules/dnn/src/layers/cpu_kernels/conv2_depthwise.simd.hpp index 0f8792d376..e6933584f5 100644 --- a/modules/dnn/src/layers/cpu_kernels/conv2_depthwise.simd.hpp +++ b/modules/dnn/src/layers/cpu_kernels/conv2_depthwise.simd.hpp @@ -24,9 +24,6 @@ namespace cv { namespace dnn { CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN -// -// [TODO] add special branch for 3x3 depthwise convolution -// static void depthwiseConv32f(const void* inp__, const void* residual__, void* out__, const ConvState& cs, const void* weights__, const float* scale__, @@ -88,7 +85,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__, } else if (fastActivation == FAST_ACTIV_PRELU) { CV_Assert(cs.activParams.size() == size_t(C)); } else { - CV_Assert(fastActivation == FAST_ACTIV_NONE); + // FAST_ACTIV_NONE: activation (if any) is handled via function pointer defaultAlpha = 1.f; } diff --git a/modules/dnn/src/layers/cpu_kernels/conv2_kernels.simd.hpp b/modules/dnn/src/layers/cpu_kernels/conv2_kernels.simd.hpp index 6926b9f79b..9d85997a7b 100644 --- a/modules/dnn/src/layers/cpu_kernels/conv2_kernels.simd.hpp +++ b/modules/dnn/src/layers/cpu_kernels/conv2_kernels.simd.hpp @@ -424,7 +424,7 @@ static void setupActivation(const ConvState& cs, int K, } else if (fastActivation == FAST_ACTIV_PRELU) { CV_Assert(cs.activParams.size() == size_t(K)); } else { - CV_Assert(fastActivation == FAST_ACTIV_NONE); + // FAST_ACTIV_NONE: activation (if any) is handled via function pointer defaultAlpha = 1.f; } } diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index a04675dee3..a2f367a19c 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -255,6 +255,27 @@ public: if (src.type() == CV_32F && dst.type() == CV_32F) { + // Try fast activation function path first + std::vector activParams_; + ActivationFunc activFunc = func.getActivationFunc(CV_32F, activParams_); + if (activFunc) { + const float* params = activParams_.empty() ? nullptr : activParams_.data(); + size_t total = src.total(); + const float* srcptr = src.ptr(); + float* dstptr = dst.ptr(); + + const size_t BLOCK_SIZE = 1 << 16; + parallel_for_(Range(0, (int)((total + BLOCK_SIZE - 1) / BLOCK_SIZE)), + [&](const Range& r) { + for (int b = r.start; b < r.end; b++) { + size_t start = b * BLOCK_SIZE; + size_t len = std::min(BLOCK_SIZE, total - start); + activFunc(srcptr + start, dstptr + start, len, params); + } + }); + continue; + } + const int nstripes = getNumThreads(); PBody body(func, src, dst, nstripes); parallel_for_(Range(0, nstripes), body, nstripes); @@ -281,6 +302,11 @@ public: func.apply(src, dst, -1, len, planeSize, cn0, cn1); } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const CV_OVERRIDE + { + return func.getActivationFunc(depth, activParams); + } + #ifdef HAVE_CUDA Ptr initCUDA( void *context_, @@ -326,6 +352,9 @@ struct BaseFunctor bool tryFuse(Ptr&) { return false; } void getScaleShift(Mat&, Mat&) const {} + + ActivationFunc getActivationFunc(int /*depth*/, std::vector& /*activParams*/) const + { return nullptr; } }; struct ReLUFunctor : public BaseFunctor @@ -335,6 +364,13 @@ struct ReLUFunctor : public BaseFunctor explicit ReLUFunctor(float slope_=1.f) : slope(slope_) {} + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams = {slope}; + return cv::dnn::getActivationFunc(ACTIV_RELU); + } + bool supportBackend(int backendId, int) { #ifdef HAVE_DNN_NGRAPH @@ -507,6 +543,13 @@ struct ReLU6Functor : public BaseFunctor CV_Assert(minValue <= maxValue); } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams = {minValue, maxValue}; + return cv::dnn::getActivationFunc(ACTIV_CLIP); + } + bool supportBackend(int backendId, int) { #ifdef HAVE_INF_ENGINE @@ -759,6 +802,13 @@ struct GeluFunctor : public BaseFunctor { #endif } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_GELU); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || @@ -892,6 +942,13 @@ struct GeluApproximationFunctor : public BaseDefaultFunctor& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_GELU_APPROX); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV; @@ -913,6 +970,13 @@ struct TanHFunctor : public BaseDefaultFunctor { typedef TanHLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_TANH); + } + bool supportBackend(int backendId, int) { #ifdef HAVE_INF_ENGINE @@ -984,6 +1048,13 @@ struct SwishFunctor : public BaseDefaultFunctor #endif } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_SWISH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || @@ -1089,6 +1160,13 @@ struct MishFunctor : public BaseDefaultFunctor #endif } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_MISH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || @@ -1174,6 +1252,13 @@ struct SigmoidFunctor : public BaseDefaultFunctor { typedef SigmoidLayer Layer; + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_SIGMOID); + } + bool supportBackend(int backendId, int) { #ifdef HAVE_INF_ENGINE @@ -1253,6 +1338,13 @@ struct ELUFunctor : public BaseDefaultFunctor #endif } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams = {alpha}; + return cv::dnn::getActivationFunc(ACTIV_ELU); + } + bool supportBackend(int backendId, int) { #ifdef HAVE_INF_ENGINE @@ -1900,6 +1992,13 @@ struct HardSwishFunctor : public BaseDefaultFunctor #endif } + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams.clear(); + return cv::dnn::getActivationFunc(ACTIV_HARDSWISH); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || @@ -2181,6 +2280,13 @@ struct HardSigmoidFunctor : public BaseDefaultFunctor explicit HardSigmoidFunctor(float alpha_ = 0.2f, float beta_ = 0.5f) : alpha(alpha_), beta(beta_) {} + ActivationFunc getActivationFunc(int depth, std::vector& activParams) const + { + if (depth != CV_32F) return nullptr; + activParams = {alpha, beta}; + return cv::dnn::getActivationFunc(ACTIV_HARDSIGMOID); + } + bool supportBackend(int backendId, int) { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; diff --git a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp index 01103dfdd3..3e91894f18 100644 --- a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp +++ b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp @@ -1033,6 +1033,43 @@ private: int hardSigmoidId; }; +// Swish/SiLU: x * Sigmoid(x) +class SwishSubgraph : public Subgraph +{ +public: + SwishSubgraph() + { + int input = addNodeToMatch(""); + sigmoidId = addNodeToMatch("Sigmoid", input); + mulId = addNodeToMatch("Mul", input, sigmoidId); + setFusedNode("Swish", input); + } + + virtual bool match(const Ptr& net, int nodeId, + std::vector& matchedNodesIds) CV_OVERRIDE + { + if (Subgraph::match(net, nodeId, matchedNodesIds)) + { + // Verify both Mul inputs trace to the same tensor as Sigmoid's input. + Ptr mulNode = net->getNode(matchedNodesIds[mulId]); + Ptr sigmoidNode = net->getNode(matchedNodesIds[sigmoidId]); + std::string sigmoidInput = sigmoidNode->getInputName(0); + std::string sigmoidOutput = net->getOutputName(matchedNodesIds[sigmoidId], 0); + + for (int i = 0; i < mulNode->getNumInputs(); i++) + { + std::string mulInput = mulNode->getInputName(i); + if (mulInput != sigmoidOutput) + return mulInput == sigmoidInput; + } + } + return false; + } + +private: + int sigmoidId, mulId; +}; + class CeluSubgraph : public Subgraph { public: @@ -1706,6 +1743,7 @@ void simplifySubgraphs(opencv_onnx::GraphProto& net) subgraphs.push_back(makePtr()); subgraphs.push_back(makePtr()); subgraphs.push_back(makePtr()); + subgraphs.push_back(makePtr()); subgraphs.push_back(makePtr()); subgraphs.push_back(makePtr()); subgraphs.push_back(makePtr()); diff --git a/modules/dnn/test/test_layers.cpp b/modules/dnn/test/test_layers.cpp index 7ae628735b..fba5f4ad78 100644 --- a/modules/dnn/test/test_layers.cpp +++ b/modules/dnn/test/test_layers.cpp @@ -2389,6 +2389,15 @@ public: activationParams.set("scale", 0.3f); activationParams.set("shift", 0.6f); } + else if (activationParams.type == "ELU") + { + activationParams.set("alpha", 1.0f); + } + else if (activationParams.type == "HardSigmoid") + { + activationParams.set("alpha", 0.2f); + activationParams.set("beta", 0.5f); + } } static void makeDefaultTestEltwiseLayer(LayerParams& eltwiseParams, const std::string& op, bool withCoefficients) @@ -2460,7 +2469,8 @@ public: static testing::internal::ParamGenerator activationLayersList() { // TODO: automate list generation - return Values("ReLU", "ReLU6", "ChannelsPReLU", "TanH", "Swish", "Mish", "Sigmoid", "ELU", "AbsVal", "BNLL", "Power", "Exp"); + return Values("ReLU", "ReLU6", "ChannelsPReLU", "TanH", "Swish", "Mish", "Sigmoid", "ELU", + "AbsVal", "BNLL", "Power", "Exp", "HardSwish", "HardSigmoid", "Gelu", "GeluApproximation"); } static testing::internal::ParamGenerator > dnnBackendsAndTargetsForFusionTests()