pub mod max {
pub use tract_data::internal::f16;
routine_reduce_rust!(generic;
f32,
SMax4,
4,
4,
fn run(x: &[f32], _: ()) -> f32 {
debug_assert!(x.len() % Self::nr() == 0);
debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);
*x.iter().max_by(|a, b| a.total_cmp(b)).unwrap()
},
op(Max)
);
routine_reduce_rust!(generic;
f16,
HMax8,
8,
8,
fn run(x: &[f16], _: ()) -> f16 {
debug_assert!(x.len() % Self::nr() == 0);
debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);
*x.iter().max_by(|a, b| a.total_cmp(b)).unwrap()
},
op(Max)
);
}
pub mod min {
pub use tract_data::internal::f16;
routine_reduce_rust!(generic;
f32,
SMin4,
4,
4,
fn run(x: &[f32], _: ()) -> f32 {
debug_assert!(x.len() % Self::nr() == 0);
debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);
*x.iter().min_by(|a, b| a.total_cmp(b)).unwrap()
},
op(Min)
);
}
pub mod sum {
pub use tract_data::internal::f16;
routine_reduce_rust!(generic;
f32,
SSum4,
4,
4,
fn run(x: &[f32], _: ()) -> f32 {
debug_assert!(x.len() % Self::nr() == 0);
debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);
x.iter().sum::<f32>()
},
op(Sum)
);
routine_reduce_rust!(generic;
f16,
HSum8,
8,
8,
fn run(x: &[f16], _: ()) -> f16 {
debug_assert!(x.len() % Self::nr() == 0);
debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);
f16::from_f32(x.iter().map(|v| v.to_f32()).sum::<f32>())
},
op(Sum)
);
}
pub mod softmax_l2 {
#[inline(always)]
pub fn accurate_exp_f32(x: f32) -> f32 {
const LOG2E: f32 = 1.442_695_04;
const LN2_HI: f32 = 0.693_145_75;
const LN2_LO: f32 = 1.428_606_8e-6;
const MAGIC: f32 = 12_582_912.0;
let kf = (x * LOG2E + MAGIC) - MAGIC;
let rr = kf.mul_add(-LN2_LO, kf.mul_add(-LN2_HI, x));
let mut q = 1.383684405e-03f32;
q = q.mul_add(rr, 8.374815793e-03);
q = q.mul_add(rr, 4.166822560e-02);
q = q.mul_add(rr, 1.666642017e-01);
q = q.mul_add(rr, 4.999999208e-01);
q = q.mul_add(rr, 1.000000036e+00);
q = q.mul_add(rr, 1.000000001e+00);
let k = kf as i32;
let scale = f32::from_bits(((k + 127).clamp(1, 254) as u32) << 23);
#[allow(clippy::manual_range_contains)]
if x < -103.0 || x > 0.0 { 0.0 } else { q * scale }
}
#[cfg(target_arch = "aarch64")]
#[inline]
fn exp_sum_impl(x: &mut [f32], max: f32) -> f32 {
use std::arch::aarch64::*;
unsafe {
#[inline(always)]
unsafe fn exp_ps(x: float32x4_t) -> float32x4_t {
unsafe {
let kf = vrndnq_f32(vmulq_f32(x, vdupq_n_f32(1.442_695_04)));
let mut rr = vfmsq_f32(x, kf, vdupq_n_f32(0.693_145_75));
rr = vfmsq_f32(rr, kf, vdupq_n_f32(1.428_606_8e-6));
let mut q = vdupq_n_f32(8.297653546e-03);
q = vfmaq_f32(vdupq_n_f32(4.191538191e-02), q, rr);
q = vfmaq_f32(vdupq_n_f32(1.666757475e-01), q, rr);
q = vfmaq_f32(vdupq_n_f32(4.999889485e-01), q, rr);
q = vfmaq_f32(vdupq_n_f32(9.999996920e-01), q, rr);
q = vfmaq_f32(vdupq_n_f32(1.000000072e+00), q, rr);
let k = vcvtq_s32_f32(kf);
let biased = vmaxq_s32(
vminq_s32(vaddq_s32(k, vdupq_n_s32(127)), vdupq_n_s32(254)),
vdupq_n_s32(1),
);
let scale = vreinterpretq_f32_s32(vshlq_n_s32(biased, 23));
let out = vorrq_u32(
vcltq_f32(x, vdupq_n_f32(-103.0)),
vcgtq_f32(x, vdupq_n_f32(0.0)),
);
vbslq_f32(out, vdupq_n_f32(0.0), vmulq_f32(q, scale))
}
}
let vm = vdupq_n_f32(max);
let mut vsum = vdupq_n_f32(0.0);
let mut i = 0;
while i + 4 <= x.len() {
let y = exp_ps(vsubq_f32(vld1q_f32(x.as_ptr().add(i)), vm));
vst1q_f32(x.as_mut_ptr().add(i), y);
vsum = vaddq_f32(vsum, y);
i += 4;
}
let mut sum = vaddvq_f32(vsum);
for v in &mut x[i..] {
let y = accurate_exp_f32(*v - max);
*v = y;
sum += y;
}
sum
}
}
#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
#[inline]
fn exp_sum_impl(x: &mut [f32], max: f32) -> f32 {
use std::arch::wasm32::*;
#[inline(always)]
fn exp_ps(x: v128) -> v128 {
let kf = f32x4_nearest(f32x4_mul(x, f32x4_splat(1.442_695_04)));
let mut rr = f32x4_sub(x, f32x4_mul(kf, f32x4_splat(0.693_145_75)));
rr = f32x4_sub(rr, f32x4_mul(kf, f32x4_splat(1.428_606_8e-6)));
let mut q = f32x4_splat(8.297653546e-03);
q = f32x4_add(f32x4_splat(4.191538191e-02), f32x4_mul(q, rr));
q = f32x4_add(f32x4_splat(1.666757475e-01), f32x4_mul(q, rr));
q = f32x4_add(f32x4_splat(4.999889485e-01), f32x4_mul(q, rr));
q = f32x4_add(f32x4_splat(9.999996920e-01), f32x4_mul(q, rr));
q = f32x4_add(f32x4_splat(1.000000072e+00), f32x4_mul(q, rr));
let k = i32x4_trunc_sat_f32x4(kf);
let biased = i32x4_max(
i32x4_min(i32x4_add(k, i32x4_splat(127)), i32x4_splat(254)),
i32x4_splat(1),
);
let scale = i32x4_shl(biased, 23);
let out = v128_or(f32x4_lt(x, f32x4_splat(-103.0)), f32x4_gt(x, f32x4_splat(0.0)));
v128_bitselect(f32x4_splat(0.0), f32x4_mul(q, scale), out)
}
let vm = f32x4_splat(max);
let mut vsum = f32x4_splat(0.0);
let mut i = 0;
while i + 4 <= x.len() {
let y = exp_ps(f32x4_sub(unsafe { v128_load(x.as_ptr().add(i) as *const v128) }, vm));
unsafe { v128_store(x.as_mut_ptr().add(i) as *mut v128, y) };
vsum = f32x4_add(vsum, y);
i += 4;
}
let mut sum = f32x4_extract_lane::<0>(vsum)
+ f32x4_extract_lane::<1>(vsum)
+ f32x4_extract_lane::<2>(vsum)
+ f32x4_extract_lane::<3>(vsum);
for v in &mut x[i..] {
let y = accurate_exp_f32(*v - max);
*v = y;
sum += y;
}
sum
}
#[cfg(not(any(
target_arch = "aarch64",
all(target_family = "wasm", target_feature = "simd128")
)))]
#[inline]
fn exp_sum_impl(x: &mut [f32], max: f32) -> f32 {
let mut acc = [0f32; 4];
let mut it = x.chunks_exact_mut(4);
for c in &mut it {
for (j, v) in c.iter_mut().enumerate() {
let y = accurate_exp_f32(*v - max);
*v = y;
acc[j] += y;
}
}
let mut sum = (acc[0] + acc[1]) + (acc[2] + acc[3]);
for v in it.into_remainder().iter_mut() {
let y = accurate_exp_f32(*v - max);
*v = y;
sum += y;
}
sum
}
routine_map_reduce_rust!(generic;
f32,
SSoftMaxL2Accurate,
4,
4,
fn run(x: &mut [f32], max: f32) -> f32 {
debug_assert!(x.len() % Self::nr() == 0);
debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);
exp_sum_impl(x, max)
},
op(Softmax2)
);
}
#[cfg(test)]
mod f16_accumulators {
use super::*;
use crate::frame::reduce::ReduceKer;
use tract_data::internal::f16;
#[test]
fn plain_sum_keeps_long_rows() {
for len in [1024usize, 4096, 8192] {
let row: Vec<f16> = vec![f16::from_f32(1.0); len];
let got = sum::HSum8::red().run(&row).unwrap().to_f32();
let err = (got - len as f32).abs() / len as f32;
assert!(err < 0.01, "len {len}: summed to {got}, rel {err}");
}
}
}