Skip to content

Commit f755429

Browse files
committed
kernels: accurate sigmoid/silu/tanh — drop Schraudolph bit-trick exp
The standalone sigmoid/silu/tanh slice kernels used Schraudolph's bit-trick exp (~1e-3 relative error, and a systematic bias rather than noise). Through a detection backbone every activation compounds that bias: on FastSAM-s the top-1 confidence came out 0.75 under yscv vs 0.93 under onnxruntime for an identical input — enough to lose detections at any sane conf threshold. - NEON standalone sigmoid: the hand-written Schraudolph asm loop is replaced with an intrinsics loop over the degree-4 minimax fast_exp_sigmoid_neon. - SSE/AVX/AVX512 sigmoid/silu/tanh/gelu call sites: fast_exp_bittrick_* -> fast_exp_sigmoid_* (degree-4 minimax, same clamp and 2^n scaling). Bit-trick helpers stay in exp.rs for callers that genuinely tolerate 1e-3.
1 parent 267b2c9 commit f755429

1 file changed

Lines changed: 47 additions & 156 deletions

File tree

crates/yscv-kernels/src/ops/simd/activation.rs

Lines changed: 47 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ use std::arch::x86_64::{
2727
#[cfg(target_arch = "aarch64")]
2828
use super::exp::fast_exp_sigmoid_neon;
2929
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
30-
use super::exp::{fast_exp_avx, fast_exp_bittrick_avx, fast_exp_bittrick_sse, fast_exp_sse};
30+
use super::exp::{fast_exp_avx, fast_exp_sigmoid_avx, fast_exp_sigmoid_sse, fast_exp_sse};
3131
#[cfg(target_arch = "x86_64")]
32-
use super::exp::{fast_exp_avx512, fast_exp_bittrick_avx512};
32+
use super::exp::{fast_exp_avx512, fast_exp_sigmoid_avx512};
3333
use super::{SimdDispatchPath, dispatch_path, x86_memory_simd_forces_avx2};
3434

3535
// ===========================================================================
@@ -517,7 +517,7 @@ unsafe fn fused_row_epilogue_avx_fma(
517517
// SiLU via fast bit-trick exp.
518518
let one = _mm256_set1_ps(1.0);
519519
let neg_x = _mm256_sub_ps(zero, v);
520-
let exp_neg = fast_exp_bittrick_avx(neg_x);
520+
let exp_neg = fast_exp_sigmoid_avx(neg_x);
521521
let denom = _mm256_add_ps(one, exp_neg);
522522
v = _mm256_div_ps(v, denom);
523523
}
@@ -1203,150 +1203,41 @@ unsafe fn relu_to_slice_neon(input: &[f32], output: &mut [f32]) {
12031203
#[cfg(target_arch = "aarch64")]
12041204
#[allow(unsafe_code)]
12051205
unsafe fn sigmoid_slice_neon(input: &[f32], output: &mut [f32]) {
1206+
// Точный fast-exp (minimax deg-4) вместо сырого Schraudolph bit-trick:
1207+
// ~1e-3 ошибка бит-трика систематически смещала логиты уверенности
1208+
// детекционных голов (conf 0.75 против 0.93 на FastSAM-s).
1209+
use std::arch::aarch64::{vaddq_f32, vdivq_f32, vld1q_f32, vnegq_f32, vst1q_f32};
12061210
let len = input.len();
1207-
let mut inp = input.as_ptr();
1208-
let mut out = output.as_mut_ptr();
1209-
let mut remaining = len;
1210-
1211-
// Load all constants ONCE before the loop, keep in NEON registers
1212-
if remaining >= 4 {
1213-
unsafe {
1214-
// Constants on stack for ld1r broadcast
1215-
let c_neg88: f32 = -88.0;
1216-
let c_pos88: f32 = 88.0;
1217-
// Schraudolph 1999 constants: exp(x) ~ reinterpret(int(x * C + B))
1218-
// C = 2^23 / ln(2) = 12102203.16, B = 127 * 2^23 = 1065353216
1219-
// WHY: 2^23/ln(2) maps float mantissa bits to IEEE 754 exponent field; 127*2^23 adds the exponent bias.
1220-
let c_schr_c: f32 = 12102203.0; // 2^23 / ln(2)
1221-
let c_schr_b: i32 = 127 << 23; // 1065353216 as integer
1222-
let c_sixth: f32 = 1.0 / 6.0;
1223-
let c_half: f32 = 0.5;
1224-
let c_one: f32 = 1.0;
1225-
let c_127: i32 = 127;
1226-
1227-
// Load constants into NEON registers (stays there for entire loop)
1228-
std::arch::asm!(
1229-
"ld1r {{v16.4s}}, [{p_neg88}]",
1230-
"ld1r {{v17.4s}}, [{p_pos88}]",
1231-
"ld1r {{v18.4s}}, [{p_schr_c}]", // Schraudolph C (float)
1232-
"dup v19.4s, {p_schr_b:w}", // Schraudolph B (integer 127<<23)
1233-
"ld1r {{v20.4s}}, [{p_sixth}]",
1234-
"ld1r {{v21.4s}}, [{p_half}]",
1235-
"ld1r {{v22.4s}}, [{p_one}]",
1236-
"dup v23.4s, {p_127:w}",
1237-
p_neg88 = in(reg) &c_neg88,
1238-
p_pos88 = in(reg) &c_pos88,
1239-
p_schr_c = in(reg) &c_schr_c,
1240-
p_schr_b = in(reg) c_schr_b,
1241-
p_sixth = in(reg) &c_sixth,
1242-
p_half = in(reg) &c_half,
1243-
p_one = in(reg) &c_one,
1244-
p_127 = in(reg) c_127,
1245-
out("v16") _, out("v17") _, out("v18") _, out("v19") _,
1246-
out("v20") _, out("v21") _, out("v22") _, out("v23") _,
1247-
);
1248-
1249-
// Schraudolph bit-trick: exp(x) ~ reinterpret_f32(int(x * 2^23/ln2) + 127<<23)
1250-
// Proper integer arithmetic: fcvtzs to get int, then add bias as int, then reinterpret
1251-
// 4x unrolled, 16 elements per iteration
1252-
while remaining >= 16 {
1253-
std::arch::asm!(
1254-
"ldp q0, q1, [{inp}]",
1255-
"ldp q2, q3, [{inp}, #32]",
1256-
"add {inp}, {inp}, #64",
1257-
"fneg v0.4s, v0.4s",
1258-
"fneg v1.4s, v1.4s",
1259-
"fneg v2.4s, v2.4s",
1260-
"fneg v3.4s, v3.4s",
1261-
"fmax v0.4s, v0.4s, v16.4s",
1262-
"fmax v1.4s, v1.4s, v16.4s",
1263-
"fmax v2.4s, v2.4s, v16.4s",
1264-
"fmax v3.4s, v3.4s, v16.4s",
1265-
"fmin v0.4s, v0.4s, v17.4s",
1266-
"fmin v1.4s, v1.4s, v17.4s",
1267-
"fmin v2.4s, v2.4s, v17.4s",
1268-
"fmin v3.4s, v3.4s, v17.4s",
1269-
// x * (2^23/ln2) -> convert to int
1270-
"fmul v0.4s, v0.4s, v18.4s",
1271-
"fmul v1.4s, v1.4s, v18.4s",
1272-
"fmul v2.4s, v2.4s, v18.4s",
1273-
"fmul v3.4s, v3.4s, v18.4s",
1274-
"fcvtzs v0.4s, v0.4s",
1275-
"fcvtzs v1.4s, v1.4s",
1276-
"fcvtzs v2.4s, v2.4s",
1277-
"fcvtzs v3.4s, v3.4s",
1278-
// + 127*2^23 (integer add)
1279-
"add v0.4s, v0.4s, v19.4s",
1280-
"add v1.4s, v1.4s, v19.4s",
1281-
"add v2.4s, v2.4s, v19.4s",
1282-
"add v3.4s, v3.4s, v19.4s",
1283-
// v0-v3 bits ARE exp(-x) when reinterpreted as float
1284-
// sigmoid = 1 / (1 + exp)
1285-
"fadd v0.4s, v22.4s, v0.4s",
1286-
"fadd v1.4s, v22.4s, v1.4s",
1287-
"fadd v2.4s, v22.4s, v2.4s",
1288-
"fadd v3.4s, v22.4s, v3.4s",
1289-
"fdiv v0.4s, v22.4s, v0.4s",
1290-
"fdiv v1.4s, v22.4s, v1.4s",
1291-
"fdiv v2.4s, v22.4s, v2.4s",
1292-
"fdiv v3.4s, v22.4s, v3.4s",
1293-
"stp q0, q1, [{out}]",
1294-
"stp q2, q3, [{out}, #32]",
1295-
"add {out}, {out}, #64",
1296-
inp = inout(reg) inp,
1297-
out = inout(reg) out,
1298-
out("v0") _, out("v1") _, out("v2") _, out("v3") _,
1299-
);
1300-
remaining -= 16;
1301-
}
1302-
// 4-element tail -- Schraudolph
1303-
while remaining >= 4 {
1304-
std::arch::asm!(
1305-
"ld1 {{v0.4s}}, [{inp}], #16",
1306-
"fneg v0.4s, v0.4s",
1307-
"fmax v0.4s, v0.4s, v16.4s",
1308-
"fmin v0.4s, v0.4s, v17.4s",
1309-
"fmul v0.4s, v0.4s, v18.4s",
1310-
"fcvtzs v0.4s, v0.4s",
1311-
"add v0.4s, v0.4s, v19.4s",
1312-
"fadd v0.4s, v22.4s, v0.4s",
1313-
"fdiv v0.4s, v22.4s, v0.4s",
1314-
"st1 {{v0.4s}}, [{out}], #16",
1315-
inp = inout(reg) inp,
1316-
out = inout(reg) out,
1317-
out("v0") _,
1318-
);
1319-
remaining -= 4;
1320-
}
1321-
// 4-element tail -- Schraudolph
1322-
while remaining >= 4 {
1323-
std::arch::asm!(
1324-
"ld1 {{v0.4s}}, [{inp}], #16",
1325-
"fneg v0.4s, v0.4s",
1326-
"fmax v0.4s, v0.4s, v16.4s",
1327-
"fmin v0.4s, v0.4s, v17.4s",
1328-
"fmul v0.4s, v0.4s, v18.4s",
1329-
"fcvtzs v0.4s, v0.4s",
1330-
"add v0.4s, v0.4s, v19.4s",
1331-
"fadd v0.4s, v22.4s, v0.4s",
1332-
"fdiv v0.4s, v22.4s, v0.4s",
1333-
"st1 {{v0.4s}}, [{out}], #16",
1334-
inp = inout(reg) inp,
1335-
out = inout(reg) out,
1336-
out("v0") _,
1337-
);
1338-
remaining -= 4;
1339-
}
1211+
let in_ptr = input.as_ptr();
1212+
let out_ptr = output.as_mut_ptr();
1213+
let one = vdupq_n_f32(1.0);
1214+
let mut i = 0usize;
1215+
unsafe {
1216+
while i + 16 <= len {
1217+
let x0 = vld1q_f32(in_ptr.add(i));
1218+
let x1 = vld1q_f32(in_ptr.add(i + 4));
1219+
let x2 = vld1q_f32(in_ptr.add(i + 8));
1220+
let x3 = vld1q_f32(in_ptr.add(i + 12));
1221+
let e0 = fast_exp_sigmoid_neon(vnegq_f32(x0));
1222+
let e1 = fast_exp_sigmoid_neon(vnegq_f32(x1));
1223+
let e2 = fast_exp_sigmoid_neon(vnegq_f32(x2));
1224+
let e3 = fast_exp_sigmoid_neon(vnegq_f32(x3));
1225+
vst1q_f32(out_ptr.add(i), vdivq_f32(one, vaddq_f32(one, e0)));
1226+
vst1q_f32(out_ptr.add(i + 4), vdivq_f32(one, vaddq_f32(one, e1)));
1227+
vst1q_f32(out_ptr.add(i + 8), vdivq_f32(one, vaddq_f32(one, e2)));
1228+
vst1q_f32(out_ptr.add(i + 12), vdivq_f32(one, vaddq_f32(one, e3)));
1229+
i += 16;
13401230
}
1341-
}
1342-
1343-
// Scalar tail
1344-
for i in 0..remaining {
1345-
unsafe {
1346-
let x = *inp.add(i);
1347-
*out.add(i) = 1.0 / (1.0 + (-x).exp());
1231+
while i + 4 <= len {
1232+
let x = vld1q_f32(in_ptr.add(i));
1233+
let e = fast_exp_sigmoid_neon(vnegq_f32(x));
1234+
vst1q_f32(out_ptr.add(i), vdivq_f32(one, vaddq_f32(one, e)));
1235+
i += 4;
13481236
}
13491237
}
1238+
for k in i..len {
1239+
output[k] = 1.0 / (1.0 + (-input[k]).exp());
1240+
}
13501241
}
13511242

13521243
// (sigmoid_vdsp and silu_vdsp removed -- benchmarked slower than NEON polynomial)
@@ -1376,10 +1267,10 @@ unsafe fn sigmoid_slice_sse(input: &[f32], output: &mut [f32]) {
13761267
let x3 = _mm_loadu_ps(in_ptr.add(index + 12));
13771268

13781269
// Bit-trick exp is sufficient for sigmoid (output clamped 0-1, errors wash out)
1379-
let e0 = fast_exp_bittrick_sse(_mm_sub_ps(zero, x0));
1380-
let e1 = fast_exp_bittrick_sse(_mm_sub_ps(zero, x1));
1381-
let e2 = fast_exp_bittrick_sse(_mm_sub_ps(zero, x2));
1382-
let e3 = fast_exp_bittrick_sse(_mm_sub_ps(zero, x3));
1270+
let e0 = fast_exp_sigmoid_sse(_mm_sub_ps(zero, x0));
1271+
let e1 = fast_exp_sigmoid_sse(_mm_sub_ps(zero, x1));
1272+
let e2 = fast_exp_sigmoid_sse(_mm_sub_ps(zero, x2));
1273+
let e3 = fast_exp_sigmoid_sse(_mm_sub_ps(zero, x3));
13831274

13841275
let r0 = _mm_div_ps(one, _mm_add_ps(one, e0));
13851276
let r1 = _mm_div_ps(one, _mm_add_ps(one, e1));
@@ -1398,7 +1289,7 @@ unsafe fn sigmoid_slice_sse(input: &[f32], output: &mut [f32]) {
13981289
while index + 4 <= len {
13991290
let x = _mm_loadu_ps(in_ptr.add(index));
14001291
let neg_x = _mm_sub_ps(zero, x);
1401-
let exp_neg_x = fast_exp_bittrick_sse(neg_x);
1292+
let exp_neg_x = fast_exp_sigmoid_sse(neg_x);
14021293
let denom = _mm_add_ps(one, exp_neg_x);
14031294
let result = _mm_div_ps(one, denom);
14041295
_mm_storeu_ps(out_ptr.add(index), result);
@@ -1436,10 +1327,10 @@ unsafe fn sigmoid_slice_avx(input: &[f32], output: &mut [f32]) {
14361327
let x3 = _mm256_loadu_ps(in_ptr.add(index + 24));
14371328

14381329
// Use Schraudolph bit-trick exp for ~3x speedup over polynomial
1439-
let e0 = fast_exp_bittrick_avx(_mm256_sub_ps(zero, x0));
1440-
let e1 = fast_exp_bittrick_avx(_mm256_sub_ps(zero, x1));
1441-
let e2 = fast_exp_bittrick_avx(_mm256_sub_ps(zero, x2));
1442-
let e3 = fast_exp_bittrick_avx(_mm256_sub_ps(zero, x3));
1330+
let e0 = fast_exp_sigmoid_avx(_mm256_sub_ps(zero, x0));
1331+
let e1 = fast_exp_sigmoid_avx(_mm256_sub_ps(zero, x1));
1332+
let e2 = fast_exp_sigmoid_avx(_mm256_sub_ps(zero, x2));
1333+
let e3 = fast_exp_sigmoid_avx(_mm256_sub_ps(zero, x3));
14431334

14441335
let r0 = _mm256_div_ps(one, _mm256_add_ps(one, e0));
14451336
let r1 = _mm256_div_ps(one, _mm256_add_ps(one, e1));
@@ -1458,7 +1349,7 @@ unsafe fn sigmoid_slice_avx(input: &[f32], output: &mut [f32]) {
14581349
while index + 8 <= len {
14591350
let x = _mm256_loadu_ps(in_ptr.add(index));
14601351
let neg_x = _mm256_sub_ps(zero, x);
1461-
let exp_neg_x = fast_exp_bittrick_avx(neg_x);
1352+
let exp_neg_x = fast_exp_sigmoid_avx(neg_x);
14621353
let denom = _mm256_add_ps(one, exp_neg_x);
14631354
let result = _mm256_div_ps(one, denom);
14641355
_mm256_storeu_ps(out_ptr.add(index), result);
@@ -1487,8 +1378,8 @@ unsafe fn sigmoid_slice_avx512(input: &[f32], output: &mut [f32]) {
14871378
while index + 32 <= len {
14881379
let x0 = _mm512_loadu_ps(in_ptr.add(index));
14891380
let x1 = _mm512_loadu_ps(in_ptr.add(index + 16));
1490-
let e0 = fast_exp_bittrick_avx512(_mm512_sub_ps(zero, x0));
1491-
let e1 = fast_exp_bittrick_avx512(_mm512_sub_ps(zero, x1));
1381+
let e0 = fast_exp_sigmoid_avx512(_mm512_sub_ps(zero, x0));
1382+
let e1 = fast_exp_sigmoid_avx512(_mm512_sub_ps(zero, x1));
14921383
let r0 = _mm512_div_ps(one, _mm512_add_ps(one, e0));
14931384
let r1 = _mm512_div_ps(one, _mm512_add_ps(one, e1));
14941385
_mm512_storeu_ps(out_ptr.add(index), r0);
@@ -1498,7 +1389,7 @@ unsafe fn sigmoid_slice_avx512(input: &[f32], output: &mut [f32]) {
14981389

14991390
while index + 16 <= len {
15001391
let x = _mm512_loadu_ps(in_ptr.add(index));
1501-
let exp_neg_x = fast_exp_bittrick_avx512(_mm512_sub_ps(zero, x));
1392+
let exp_neg_x = fast_exp_sigmoid_avx512(_mm512_sub_ps(zero, x));
15021393
let result = _mm512_div_ps(one, _mm512_add_ps(one, exp_neg_x));
15031394
_mm512_storeu_ps(out_ptr.add(index), result);
15041395
index += 16;

0 commit comments

Comments
 (0)