1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 15:23:05 +04:00

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.
This commit is contained in:
Tonu Samuel
2026-06-27 09:11:09 +03:00
parent 8b06f28e1b
commit 4d839a16f0
4 changed files with 338 additions and 1 deletions
+14 -1
View File
@@ -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.
+54
View File
@@ -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<tuple<Backend, Target> >
{
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
@@ -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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<v_float32>::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<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);
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<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), 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<v_float32>::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<v_float32>::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;
}
}
@@ -1496,6 +1496,14 @@ struct BNLLFunctor : public BaseDefaultFunctor<BNLLFunctor>
{
typedef BNLLLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<LogFunctor>
{
typedef LogLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<AcoshFunctor>
{
typedef AcoshLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<AsinhFunctor>
{
typedef AsinhLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<AtanhFunctor>
{
typedef AtanhLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<CosFunctor>
{
typedef CosLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<CoshFunctor>
{
typedef CoshLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<ErfFunctor>
{
typedef ErfLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<SinFunctor>
{
typedef SinLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<SinhFunctor>
{
typedef SinhLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<SoftplusFunctor>
{
typedef SoftplusLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<TanFunctor>
{
typedef TanLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& 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<PowerFunctor>
struct ExpFunctor : public BaseDefaultFunctor<ExpFunctor>
{
typedef ExpLayer Layer;
ActivationFunc getActivationFunc(int depth, std::vector<float>& activParams) const
{
if (depth != CV_32F)
return nullptr;
activParams = {normScale, normShift};
return cv::dnn::getActivationFunc(ACTIV_EXP);
}
float base, scale, shift;
float normScale, normShift;