1use std::mem::MaybeUninit;
2
3use rten_simd::functional::{simd_apply, simd_map};
4use rten_simd::ops::{BitOps, FloatOps, NumOps};
5use rten_simd::span::SrcDest;
6use rten_simd::{Isa, Simd, SimdIterable, SimdOp, SimdUnaryOp};
7
8use crate::exp::ReducedRangeExp;
9
10pub struct Softmax<'src, 'dst> {
18 src_dest: SrcDest<'src, 'dst, f32>,
19 flush_nans_to_zero: bool,
20}
21
22impl<'src, 'dst> Softmax<'src, 'dst> {
23 #[track_caller]
26 pub fn new(input: &'src [f32], output: &'dst mut [MaybeUninit<f32>]) -> Self {
27 Softmax {
28 src_dest: (input, output).into(),
29 flush_nans_to_zero: false,
30 }
31 }
32
33 pub fn new_mut(input: &'dst mut [f32]) -> Self
35 where
36 'dst: 'src,
37 {
38 Softmax {
39 src_dest: input.into(),
40 flush_nans_to_zero: false,
41 }
42 }
43
44 pub fn flush_nans_to_zero(mut self, flush: bool) -> Self {
55 self.flush_nans_to_zero = flush;
56 self
57 }
58}
59
60impl<'dst> SimdOp for Softmax<'_, 'dst> {
61 type Output = &'dst mut [f32];
63
64 #[inline(always)]
65 fn eval<I: Isa>(self, isa: I) -> Self::Output {
66 let ops = isa.f32();
67
68 let max_val = max(ops, self.src_dest.src());
69
70 let (dest, exp_sum) = exp_sum_minus_max(isa, self.src_dest, max_val);
72
73 let exp_sum = ops.splat(exp_sum);
75 let inv_exp_sum = ops.reciprocal(exp_sum);
76 const UNROLL: usize = 2;
77 let zero = ops.zero();
78
79 if self.flush_nans_to_zero {
80 simd_apply::<_, _, _, UNROLL>(
81 ops,
82 dest,
83 #[inline(always)]
84 |x| {
85 let y = ops.mul(x, inv_exp_sum);
86 let not_nan = ops.eq(y, y);
87 ops.select(y, zero, not_nan)
88 },
89 );
90 } else {
91 simd_apply::<_, _, _, UNROLL>(
92 ops,
93 dest,
94 #[inline(always)]
95 |x| ops.mul(x, inv_exp_sum),
96 );
97 }
98
99 dest
100 }
101}
102
103pub struct LogSoftmax<'src, 'dst> {
108 src_dest: SrcDest<'src, 'dst, f32>,
109}
110
111impl<'src, 'dst> LogSoftmax<'src, 'dst> {
112 pub fn new(input: &'src [f32], output: &'dst mut [MaybeUninit<f32>]) -> Self {
115 LogSoftmax {
116 src_dest: (input, output).into(),
117 }
118 }
119
120 pub fn new_mut(input: &'dst mut [f32]) -> Self
122 where
123 'dst: 'src,
124 {
125 LogSoftmax {
126 src_dest: input.into(),
127 }
128 }
129}
130
131impl<'dst> SimdOp for LogSoftmax<'_, 'dst> {
132 type Output = &'dst mut [f32];
134
135 #[inline(always)]
136 fn eval<I: Isa>(self, isa: I) -> Self::Output {
137 let ops = isa.f32();
138
139 let max_val = max(ops, self.src_dest.src());
146
147 let max_vec = ops.splat(max_val);
149 let exp_sum = self.src_dest.src().simd_iter(ops).fold(
150 ops.zero(),
151 #[inline(always)]
152 |exp_sum, x| {
153 let y = ReducedRangeExp::apply(isa, ops.sub(x, max_vec));
155 ops.add(exp_sum, y)
156 },
157 );
158 let exp_sum: f32 = exp_sum.to_array().into_iter().sum();
159
160 let log_exp_sum = ops.splat(exp_sum.ln());
167 simd_map(
168 ops,
169 self.src_dest,
170 #[inline(always)]
171 |x| ops.sub(ops.sub(x, max_vec), log_exp_sum),
172 )
173 }
174}
175
176#[inline(always)]
178fn max<O: FloatOps<f32>>(ops: O, xs: &[f32]) -> f32 {
179 let max_val = xs.simd_iter(ops).fold_unroll::<4>(
180 ops.splat(f32::MIN),
181 #[inline(always)]
182 |max, x| ops.max(max, x),
183 #[inline(always)]
184 |max, x| ops.max(max, x),
185 );
186 max_val
187 .to_array()
188 .into_iter()
189 .fold(f32::MIN, |max, x| max.max(x))
190}
191
192#[inline(always)]
194fn exp_sum_minus_max<'dst, I: Isa>(
195 isa: I,
196 src_dest: SrcDest<'_, 'dst, f32>,
197 max_val: f32,
198) -> (&'dst mut [f32], f32) {
199 let ops = isa.f32();
200
201 let max_val = ops.splat(max_val);
202
203 let mut prev_exp_sum = ops.zero();
205 let mut exp_sum = ops.zero();
206 let dest = simd_map(
207 ops,
208 src_dest,
209 #[inline(always)]
210 |x| {
211 let y = ReducedRangeExp::apply(isa, ops.sub(x, max_val));
213 prev_exp_sum = exp_sum;
214 exp_sum = ops.add(exp_sum, y);
215 y
216 },
217 );
218
219 let remainder = dest.len() % ops.len();
221 if remainder != 0 {
222 let remainder_mask = ops.first_n_mask(remainder);
223 exp_sum = ops.select(exp_sum, prev_exp_sum, remainder_mask);
224 }
225 let exp_sum = exp_sum.to_array().into_iter().sum();
226
227 (dest, exp_sum)
228}
229
230#[cfg(test)]
231mod tests {
232 use rten_simd::SimdOp;
233
234 use super::{LogSoftmax, Softmax};
235 use crate::testing::{AsUninit, benchmark_op, check_f32s_are_equal_ulps, triples};
236
237 fn reference_log_softmax(xs: &[f32], ys: &mut [f32]) {
238 let max = xs.iter().copied().fold(f32::MIN, |max, x| max.max(x));
239 let log_exp_sum = xs
240 .iter()
241 .fold(0., |exp_sum: f32, x| exp_sum + (x - max).exp())
242 .ln();
243 for (x, y) in xs.iter().zip(ys.iter_mut()) {
244 *y = (*x - max) - log_exp_sum;
245 }
246 }
247
248 fn reference_softmax(xs: &[f32], ys: &mut [f32]) {
249 let max = xs.iter().copied().fold(f32::MIN, |max, x| max.max(x));
250
251 let mut exp_sum = 0.;
252 for (x, y) in xs.iter().zip(ys.iter_mut()) {
253 *y = (*x - max).exp();
254 exp_sum += *y;
255 }
256
257 for el in ys.iter_mut() {
258 *el /= exp_sum;
259 }
260 }
261
262 #[test]
263 fn test_softmax() {
264 let input = vec![0.1634, 0.8647, 0.6401, 0.8265, 0.0560, 0.2304];
266 let expected = &([
267 0.11715934, 0.23623686, 0.18871443, 0.2273828, 0.10522857, 0.12527795,
268 ]);
269 let mut actual = vec![0.; input.len()];
270
271 Softmax::new(&input, actual.as_mut_slice().as_uninit()).dispatch();
272 check_f32s_are_equal_ulps(triples(&input, &actual, expected), 1. );
273
274 for len in 1..20 {
276 let input: Vec<f32> = (0..len).map(|x| x as f32 + 0.1).collect();
277 let mut expected = vec![0.; input.len()];
278 reference_softmax(&input, &mut expected);
279
280 let mut actual = vec![0.; input.len()];
281 Softmax::new(&input, actual.as_mut_slice().as_uninit()).dispatch();
282
283 check_f32s_are_equal_ulps(triples(&input, &actual, &expected), 3. );
284 }
285 }
286
287 #[test]
288 fn test_softmax_flush_nans_to_zero() {
289 let mut input = [f32::NEG_INFINITY; 3];
290 Softmax::new_mut(&mut input).dispatch();
291 assert!(input.iter().all(|x| x.is_nan()));
292
293 let mut input = [f32::NEG_INFINITY; 3];
294 Softmax::new_mut(&mut input)
295 .flush_nans_to_zero(true)
296 .dispatch();
297 assert_eq!(input, [0.; 3]);
298 }
299
300 #[test]
301 fn test_log_softmax() {
302 for len in 1..20 {
304 let input: Vec<f32> = (0..len).map(|x| x as f32 + 0.1).collect();
305 let mut expected = vec![0.; input.len()];
306 reference_log_softmax(&input, &mut expected);
307
308 let mut actual = vec![0.; input.len()];
309 LogSoftmax::new(&input, actual.as_mut_slice().as_uninit()).dispatch();
310
311 check_f32s_are_equal_ulps(triples(&input, &actual, &expected), 3. );
312 }
313 }
314
315 #[test]
316 fn test_log_softmax_in_place() {
317 let input: Vec<f32> = (0..32).map(|x| x as f32 * 0.5 - 8.).collect();
318 let mut expected = vec![0.; input.len()];
319 reference_log_softmax(&input, &mut expected);
320
321 let mut actual = input.clone();
322 LogSoftmax::new_mut(&mut actual).dispatch();
323
324 check_f32s_are_equal_ulps(triples(&input, &actual, &expected), 3. );
325 }
326
327 #[test]
328 fn test_log_softmax_sums_to_one() {
329 for scale in [1e-3, 1., 10., 100.] {
332 let input: Vec<f32> = (0..64).map(|x| (x as f32 - 32.) * scale).collect();
333 let mut actual = input.clone();
334 LogSoftmax::new_mut(&mut actual).dispatch();
335
336 let sum: f32 = actual.iter().map(|x| x.exp()).sum();
337 assert!((sum - 1.).abs() < 1e-4, "scale {scale} sum {sum}");
338 }
339 }
340
341 #[test]
342 #[ignore]
343 fn bench_softmax() {
344 benchmark_op(reference_softmax, |src, dest| {
345 Softmax::new(src, dest).dispatch();
346 });
347 }
348
349 #[test]
350 #[ignore]
351 fn bench_log_softmax() {
352 benchmark_op(reference_log_softmax, |src, dest| {
353 LogSoftmax::new(src, dest).dispatch();
354 });
355 }
356}