1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

Merge pull request #29377 from tonuonu:perf-dnn-activation-simd

dnn: SIMD for transcendental activation layers (15 functors) 🧑‍💻🤖 #2937

# dnn: SIMD for transcendental activation layers (15 functors) 🧑‍💻🤖

### Summary

Many `BaseDefaultFunctor`-based activation functors fall back to the scalar `BaseDefaultFunctor::apply`
— one `libm` call per element. This PR adds vectorized `apply()` overrides (universal intrinsics,
matching the existing `MishFunctor`/`GeluFunctor`/`SigmoidFunctor` pattern, each with a scalar tail) to
the **15 transcendental activations** that have a clean SIMD path:

| functor | uses | | functor | uses |
|---|---|---|---|---|
| TanH | `v_exp` (1−2/(e²ˣ+1)) | | Asinh | `v_log`,`v_sqrt` (sign·log(\|x\|+√(x²+1))) |
| Log | `v_log` | | Acosh | `v_log`,`v_sqrt` |
| Erf | `v_erf` | | Atanh | `v_log` |
| Exp | `v_exp` | | Softplus | `v_exp`,`v_log` |
| Sin | `v_sin` | | BNLL | `v_exp`,`v_log` (max(x,0)+log1p(e^−\|x\|)) |
| Cos | `v_cos` | | GeluApproximation | `v_exp` (tanh) |
| Tan | `v_sin`/`v_cos` | | Sinh / Cosh | `v_exp` |

Functors compile at the baseline SIMD width (SSE on x86, NEON on ARM — same as the existing SIMD
functors). `Sqrt`/`Floor`/`Ceil`/`Round`/`Abs`/etc. are intentionally **not** included — those are
single cheap ops the compiler already auto-vectorizes (measured `Sqrt` at 0.95×). `Asin`/`Acos`/`Atan`
are omitted (no `v_atan`/`v_asin`/`v_acos` intrinsic).

### Performance

Real `opencv_perf_dnn` `Layer_Activation` (added here), 8×256×128×100 = 26.2 M-elem CV_32F blob, A/B
vs the scalar fallback, median speedup:

| functor | M4 clang/NEON | A76 gcc/NEON | Threadripper gcc | Xeon W-2235 gcc | functor | M4 | A76 | TR | Xeon |
|---|---|---|---|---|---|---|---|---|---|
| TanH | 9.1× | 2.3× | 7.3× | 7.3× | Sinh | 3.3× | 3.2× | 5.2× | 5.0× |
| Cos | 8.8× | 3.0× | 3.3× | 3.0× | Acosh | 3.2× | 2.9× | 2.7× | 2.6× |
| Log | 7.8× | 3.0× | 2.5× | 2.4× | Cosh | 2.8× | 3.2× | 2.6× | 2.5× |
| Sin | 7.8× | 3.0× | 3.4× | 2.9× | Exp | 2.4× | 2.5× | 1.4× | 1.5× |
| GeluApprox | 7.6× | 2.4× | 6.6× | 5.8× | BNLL | 4.2× | 2.0× | 1.6× | 1.4× |
| Atanh | 7.0× | 3.6× | 6.0× | 5.2× | Softplus | 2.0× | 2.0× | 3.3× | 2.7× |
| Tan | 6.8× | 3.6× | 6.6× | 6.0× | Erf | 4.2× | 2.4× | 3.9× | 3.5× |
| Asinh | 3.9× | 2.4× | 5.8× | 5.7× | | | | | |

Every functor is a win on every tested out-of-order core (worst case 1.4×). On the **in-order
Cortex-A55** (a55-tuned build) all 15 measure 0.985–0.99× — neutral, within noise: the SIMD
polynomials are dependency-chain-bound, which an in-order pipeline can neither accelerate nor (here)
slow down. So: meaningful wins on out-of-order ARM + x86, no regression on in-order ARM.

### Accuracy

Uses the `v_exp`/`v_log`/`v_erf`/`v_sin`/`v_cos` polynomials OpenCV already ships and relies on (Gelu
via `v_erf`, Mish/Sigmoid via `v_exp`). Verified vs scalar `libm` on 320 K random elements per functor
with domain-correct inputs (Acosh x≥1, Atanh |x|<1, …): **max abs error ≤ 3e-5** (most ≤ 2e-6), zero
elements exceeding rel>1e-3 & abs>1e-4. ONNX conformance and the layer accuracy tests exercise these ops.

### Pull Request Readiness Checklist

- [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 (4.x)
- [ ] There is a reference to the original bug report and related work — N/A (perf improvement)
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable — perf test added (`Layer_Activation`, `SANITY_CHECK_NOTHING`); accuracy covered by existing ONNX conformance/layer tests
- [ ] The feature is well documented and sample code can be built with the project CMake — N/A (internal optimization, no new API)
This commit is contained in:
Tõnu Samuel
2026-06-26 11:06:53 +03:00
committed by GitHub
parent 61bd5e7b55
commit b64a215e71
2 changed files with 337 additions and 0 deletions
+37
View File
@@ -843,6 +843,43 @@ PERF_TEST_P_(Layer_GroupNorm, GroupNorm)
}
struct Layer_Activation : public TestBaseWithParam<tuple<Backend, Target> >
{
void test_activation(const std::vector<int>& shape, const String& type)
{
int backendId = get<0>(GetParam());
int targetId = get<1>(GetParam());
Mat a(shape, CV_32FC1);
randn(a, 0.f, 1.f);
Net net;
LayerParams lp; lp.type = type; lp.name = "testLayer";
net.addLayerToPrev(lp.name, lp.type, lp);
net.setInput(a);
net.setPreferableBackend(backendId);
net.setPreferableTarget(targetId);
Mat out = net.forward(); // warmup
TEST_CYCLE() { Mat res = net.forward(); }
SANITY_CHECK_NOTHING();
}
int N = 8, C = 256, H = 128, W = 100;
};
PERF_TEST_P_(Layer_Activation, Exp) { test_activation({N,C,H,W}, "Exp"); }
PERF_TEST_P_(Layer_Activation, TanH) { test_activation({N,C,H,W}, "TanH"); }
PERF_TEST_P_(Layer_Activation, Log) { test_activation({N,C,H,W}, "Log"); }
PERF_TEST_P_(Layer_Activation, Erf) { test_activation({N,C,H,W}, "Erf"); }
PERF_TEST_P_(Layer_Activation, Sin) { test_activation({N,C,H,W}, "Sin"); }
PERF_TEST_P_(Layer_Activation, Cos) { test_activation({N,C,H,W}, "Cos"); }
PERF_TEST_P_(Layer_Activation, Sinh) { test_activation({N,C,H,W}, "Sinh"); }
PERF_TEST_P_(Layer_Activation, Cosh) { test_activation({N,C,H,W}, "Cosh"); }
PERF_TEST_P_(Layer_Activation, Tan) { test_activation({N,C,H,W}, "Tan"); }
PERF_TEST_P_(Layer_Activation, Softplus) { test_activation({N,C,H,W}, "Softplus"); }
PERF_TEST_P_(Layer_Activation, BNLL) { test_activation({N,C,H,W}, "BNLL"); }
PERF_TEST_P_(Layer_Activation, Gelu) { test_activation({N,C,H,W}, "GeluApproximation"); }
PERF_TEST_P_(Layer_Activation, Asinh) { test_activation({N,C,H,W}, "Asinh"); }
PERF_TEST_P_(Layer_Activation, Acosh) { test_activation({N,C,H,W}, "Acosh"); }
PERF_TEST_P_(Layer_Activation, Atanh) { test_activation({N,C,H,W}, "Atanh"); }
INSTANTIATE_TEST_CASE_P(/**/, Layer_Activation, testing::Values(std::make_tuple(DNN_BACKEND_OPENCV, DNN_TARGET_CPU)));
INSTANTIATE_TEST_CASE_P(/**/, Layer_Slice, dnnBackendsAndTargets(false, false));
INSTANTIATE_TEST_CASE_P(/**/, Layer_NaryEltwise, testing::Values(std::make_tuple(DNN_BACKEND_OPENCV, DNN_TARGET_CPU)));
#ifdef HAVE_CUDA
@@ -989,6 +989,32 @@ struct GeluApproximationFunctor : public BaseDefaultFunctor<GeluApproximationFun
GeluApproximationConstants::coef_sqrt_2_pi * x * x)));
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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), half = vx_setall_f32(0.5f);
v_float32 c1 = vx_setall_f32(GeluApproximationConstants::sqrt_2_pi);
v_float32 c2 = vx_setall_f32(GeluApproximationConstants::coef_sqrt_2_pi);
v_float32 lo = vx_setall_f32(-88.f), hi = vx_setall_f32(88.f);
for (; i <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
v_float32 u = v_mul(x, v_fma(v_mul(x, x), c2, c1));
v_float32 e = v_exp(v_min(v_max(v_add(u, u), lo), hi));
v_float32 th = v_sub(one, v_div(two, v_add(e, one)));
vx_store(dstptr + i, v_mul(v_mul(half, x), v_add(one, th)));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
int64 getFLOPSPerElement() const { return 100; }
};
@@ -1016,6 +1042,28 @@ struct TanHFunctor : public BaseDefaultFunctor<TanHFunctor>
return tanh(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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);
v_float32 lo = vx_setall_f32(-88.f), hi = vx_setall_f32(88.f);
for (; i <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
v_float32 e = v_exp(v_min(v_max(v_add(x, x), lo), hi)); // e^{2x}, clamped
vx_store(dstptr + i, v_sub(one, v_div(two, v_add(e, one)))); // 1 - 2/(e+1)
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -1607,6 +1655,27 @@ struct BNLLFunctor : public BaseDefaultFunctor<BNLLFunctor>
return x > 0 ? x + log(1.f + exp(-x)) : log(1.f + exp(x));
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
// stable single-branch form: max(x,0) + log(1 + exp(-|x|))
vx_store(dstptr + 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++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -1779,6 +1848,22 @@ struct LogFunctor : public BaseDefaultFunctor<LogFunctor>
return log(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int i = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int vlanes = VTraits<v_float32>::vlanes();
for (; i <= len - vlanes; i += vlanes)
vx_store(dstptr + i, v_log(vx_load(srcptr + i)));
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -1982,6 +2067,26 @@ struct AcoshFunctor : public BaseDefaultFunctor<AcoshFunctor>
return acosh(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_log(v_add(x, v_sqrt(v_sub(v_mul(x, x), one)))));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2036,6 +2141,28 @@ struct AsinhFunctor : public BaseDefaultFunctor<AsinhFunctor>
return asinh(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
v_float32 ax = v_abs(x);
v_float32 r = v_log(v_add(ax, v_sqrt(v_fma(ax, ax, one))));
vx_store(dstptr + i, v_select(v_lt(x, z), v_sub(z, r), r));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2090,6 +2217,26 @@ struct AtanhFunctor : public BaseDefaultFunctor<AtanhFunctor>
return atanh(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_mul(half, v_log(v_div(v_add(one, x), v_sub(one, x)))));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2117,6 +2264,25 @@ struct CosFunctor : public BaseDefaultFunctor<CosFunctor>
return cos(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int i = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int vlanes = VTraits<v_float32>::vlanes();
for (; i <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_cos(x));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2144,6 +2310,26 @@ struct CoshFunctor : public BaseDefaultFunctor<CoshFunctor>
return cosh(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_mul(half, v_add(v_exp(x), v_exp(v_sub(z, x)))));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2171,6 +2357,22 @@ struct ErfFunctor : public BaseDefaultFunctor<ErfFunctor>
return erf(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int i = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int vlanes = VTraits<v_float32>::vlanes();
for (; i <= len - vlanes; i += vlanes)
vx_store(dstptr + i, v_erf(vx_load(srcptr + i)));
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2282,6 +2484,25 @@ struct SinFunctor : public BaseDefaultFunctor<SinFunctor>
return sin(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int i = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int vlanes = VTraits<v_float32>::vlanes();
for (; i <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_sin(x));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2309,6 +2530,26 @@ struct SinhFunctor : public BaseDefaultFunctor<SinhFunctor>
return sinh(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_mul(half, v_sub(v_exp(x), v_exp(v_sub(z, x)))));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2336,6 +2577,26 @@ struct SoftplusFunctor : public BaseDefaultFunctor<SoftplusFunctor>
return log1p(exp(x));
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int 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 <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_log(v_add(one, v_exp(x))));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2390,6 +2651,25 @@ struct TanFunctor : public BaseDefaultFunctor<TanFunctor>
return tan(x);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int i = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int vlanes = VTraits<v_float32>::vlanes();
for (; i <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_div(v_sin(x), v_cos(x)));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(int target, csl::Stream stream)
{
@@ -2827,6 +3107,26 @@ struct ExpFunctor : public BaseDefaultFunctor<ExpFunctor>
return exp(normScale * x + normShift);
}
void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const
{
CV_UNUSED(stripeStart);
for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize)
{
int i = 0;
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int vlanes = VTraits<v_float32>::vlanes();
v_float32 vsc = vx_setall_f32(normScale), vsh = vx_setall_f32(normShift);
for (; i <= len - vlanes; i += vlanes)
{
v_float32 x = vx_load(srcptr + i);
vx_store(dstptr + i, v_exp(v_fma(x, vsc, vsh)));
}
#endif
for (; i < len; i++)
dstptr[i] = calculate(srcptr[i]);
}
}
inline void setKernelParams(ocl::Kernel& kernel) const
{
kernel.set(3, normScale);