mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 23:33:05 +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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user