mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 00:03:03 +04:00
Merge pull request #28750 from abhishek-gola:activation_fusion
Extended fusion for activation functions in new DNN engine #28750 After this fusion, we see following improvements in YOLO models: | Model | Before (`ENGINE_NEW`) | After (`ENGINE_NEW`) | `ENGINE_ORT` | % Improvement (Before v/s After) | | :--- | :--- | :--- | :--- | :--- | | **YOLOv8n** | 18.89 ms| 12.06 ms| 12.15 ms| 36.16% | | **YOLOv5n** | 17.12 ms| 9.29 ms| 9.23 ms| 45.73% | | **YOLOX-S** | 38.78 ms| 25.56 ms| 25.16 ms| 34.09% | Device details: - Model name: Intel(R) Core(TM) i9-14900KS, x86, 32 Cores, ubuntu 24.04, ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -918,6 +918,36 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<Pad2Layer> 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<float>& /*activParams*/) const
|
||||
{ return nullptr; }
|
||||
};
|
||||
|
||||
class CV_EXPORTS ReLULayer : public ActivationLayer
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<int>& pads_,
|
||||
AutoPadding autoPad, bool ceilMode,
|
||||
FastActivation fastActivation_,
|
||||
ActivationFunc activationFunc_,
|
||||
const std::vector<float>& 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);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#ifndef __OPENCV_DNN_LAYERS_CONV2_COMMON_HPP__
|
||||
#define __OPENCV_DNN_LAYERS_CONV2_COMMON_HPP__
|
||||
|
||||
#include <opencv2/dnn.hpp>
|
||||
#include <opencv2/dnn/all_layers.hpp>
|
||||
#include <array>
|
||||
|
||||
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<int>& pads,
|
||||
AutoPadding autoPad, bool ceilMode,
|
||||
FastActivation fastActivation,
|
||||
ActivationFunc activationFunc,
|
||||
const std::vector<float>& activParams);
|
||||
|
||||
// initializes the structure of parameters for 1D/2D/3D
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
ngroups = params.get<int>("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<Layer>& activlayer) override
|
||||
{
|
||||
ActivationLayer* activ_ptr = dynamic_cast<ActivationLayer*>(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<ReLULayer*>(activ_ptr);
|
||||
ReLU6Layer* activClip = dynamic_cast<ReLU6Layer*>(activ_ptr);
|
||||
ChannelsPReLULayer* activPRelu = dynamic_cast<ChannelsPReLULayer*>(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<float> activParams;
|
||||
bool addResidual;
|
||||
};
|
||||
|
||||
@@ -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 <opencv2/dnn/all_layers.hpp>
|
||||
#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
|
||||
}}
|
||||
@@ -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 <opencv2/dnn/all_layers.hpp>
|
||||
#include "opencv2/core/hal/intrin.hpp"
|
||||
#include <math.h>
|
||||
|
||||
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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +255,27 @@ public:
|
||||
|
||||
if (src.type() == CV_32F && dst.type() == CV_32F)
|
||||
{
|
||||
// Try fast activation function path first
|
||||
std::vector<float> 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>();
|
||||
float* dstptr = dst.ptr<float>();
|
||||
|
||||
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<float>& activParams) const CV_OVERRIDE
|
||||
{
|
||||
return func.getActivationFunc(depth, activParams);
|
||||
}
|
||||
|
||||
#ifdef HAVE_CUDA
|
||||
Ptr<BackendNode> initCUDA(
|
||||
void *context_,
|
||||
@@ -326,6 +352,9 @@ struct BaseFunctor
|
||||
bool tryFuse(Ptr<dnn::Layer>&) { return false; }
|
||||
|
||||
void getScaleShift(Mat&, Mat&) const {}
|
||||
|
||||
ActivationFunc getActivationFunc(int /*depth*/, std::vector<float>& /*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<float>& 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<float>& 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<float>& 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<GeluApproximationFun
|
||||
|
||||
explicit GeluApproximationFunctor() {}
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<TanHFunctor>
|
||||
{
|
||||
typedef TanHLayer Layer;
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<SwishFunctor>
|
||||
#endif
|
||||
}
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<MishFunctor>
|
||||
#endif
|
||||
}
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<SigmoidFunctor>
|
||||
{
|
||||
typedef SigmoidLayer Layer;
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<ELUFunctor>
|
||||
#endif
|
||||
}
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<HardSwishFunctor>
|
||||
#endif
|
||||
}
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<HardSigmoidFunctor>
|
||||
|
||||
explicit HardSigmoidFunctor(float alpha_ = 0.2f, float beta_ = 0.5f) : alpha(alpha_), beta(beta_) {}
|
||||
|
||||
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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;
|
||||
|
||||
@@ -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<ImportGraphWrapper>& net, int nodeId,
|
||||
std::vector<int>& matchedNodesIds) CV_OVERRIDE
|
||||
{
|
||||
if (Subgraph::match(net, nodeId, matchedNodesIds))
|
||||
{
|
||||
// Verify both Mul inputs trace to the same tensor as Sigmoid's input.
|
||||
Ptr<ImportNodeWrapper> mulNode = net->getNode(matchedNodesIds[mulId]);
|
||||
Ptr<ImportNodeWrapper> 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<SoftMaxSubgraph>());
|
||||
subgraphs.push_back(makePtr<SoftMaxSubgraph2>());
|
||||
subgraphs.push_back(makePtr<LogSoftMaxSubgraph>());
|
||||
subgraphs.push_back(makePtr<SwishSubgraph>());
|
||||
subgraphs.push_back(makePtr<HardSwishSubgraph>());
|
||||
subgraphs.push_back(makePtr<CeluSubgraph>());
|
||||
subgraphs.push_back(makePtr<NormalizeSubgraph1>());
|
||||
|
||||
@@ -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<std::string> 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<tuple<Backend, Target> > dnnBackendsAndTargetsForFusionTests()
|
||||
|
||||
Reference in New Issue
Block a user