1mod fixedpoint;
2pub mod math;
3
4use math::{
5 convert_scale_to_mult_shift, exp_on_negative_values, get_reciprocal, rescale,
6 rounding_divide_by_pot, saturating_rounding_doubling_high_mul,
7 saturating_rounding_multiply_by_pot,
8};
9use num_traits::Float;
10use std::fmt::Debug;
11use tract_num_traits::Zero;
12
13use crate::internal::*;
14use ndarray::prelude::*;
15
16#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
17pub enum SoftmaxKind {
18 Softmax(SoftmaxExp),
19 LogSoftmax,
20}
21
22impl Default for SoftmaxKind {
23 fn default() -> Self {
24 SoftmaxKind::Softmax(SoftmaxExp::default())
25 }
26}
27
28#[derive(Debug, Copy, Clone, Hash, Default, PartialEq, Eq)]
29pub enum SoftmaxExp {
30 #[default]
31 Libc,
32 FastCompact,
34}
35
36#[derive(Debug, Clone, new, Hash, Default, PartialEq, Eq)]
37pub struct Softmax {
38 pub axes: TVec<usize>,
39 pub quant_output_dt: Option<DatumType>,
40 pub kind: SoftmaxKind,
41}
42
43impl Op for Softmax {
44 fn name(&self) -> StaticName {
45 match self.kind {
46 SoftmaxKind::Softmax(_) => "Softmax".into(),
47 SoftmaxKind::LogSoftmax => "LogSoftmax".into(),
48 }
49 }
50
51 fn info(&self) -> TractResult<Vec<String>> {
52 let mut infos = vec![format!("Axis: {:?}", self.axes)];
53 if let SoftmaxKind::Softmax(exp) = self.kind {
54 infos.push(format!("Exp impl: {exp:?}"))
55 };
56 Ok(infos)
57 }
58
59 op_as_typed_op!();
60}
61
62impl TypedOp for Softmax {
63 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
64 let dt = inputs[0].datum_type;
65 if dt.is_float() {
66 ensure!(
67 self.quant_output_dt.is_none(),
68 "Float softmax should not have quant_output_dt, have {:?}",
69 self.quant_output_dt
70 );
71 } else if dt.is_quantized() {
72 ensure!(
73 self.quant_output_dt.map(|q| q.is_quantized()).unwrap_or(false),
74 "Quantized softmax should have a quantized output type (got {:?})",
75 self.quant_output_dt
76 );
77 } else {
78 bail!(
79 "Unsupported datum type in softmax: input type {:?}, output type {:?}",
80 dt,
81 self.quant_output_dt
82 );
83 }
84
85 let fact = self.quant_output_dt.unwrap_or(dt).fact(inputs[0].shape.clone());
86 Ok(tvec!(fact))
87 }
88
89 fn input_roi(
90 &self,
91 model: &TypedModel,
92 node: &TypedNode,
93 ) -> TractResult<Option<TVec<Option<TDim>>>> {
94 crate::optim::propagate_roi::bubble_roi(model, node)
95 }
96
97 fn axes_mapping(
98 &self,
99 inputs: &[&TypedFact],
100 outputs: &[&TypedFact],
101 ) -> TractResult<AxesMapping> {
102 AxesMapping::natural(inputs, outputs)
103 }
104
105 fn change_axes(
106 &self,
107 model: &TypedModel,
108 node: &TypedNode,
109 _io: InOut,
110 change: &AxisOp,
111 ) -> TractResult<Option<AxisChangeConsequence>> {
112 let axes: Option<TVec<usize>> =
113 self.axes.iter().map(|it| change.transform_axis(*it)).collect();
114 if let Some(axes) = axes {
115 Ok(Some(AxisChangeConsequence::new(
116 model,
117 node,
118 Some(Box::new(Softmax { axes, ..self.clone() })),
119 change,
120 )))
121 } else {
122 Ok(None)
123 }
124 }
125
126 as_op!();
127}
128
129impl EvalOp for Softmax {
130 fn is_stateless(&self) -> bool {
131 true
132 }
133
134 fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
135 let input = args_1!(inputs);
136 let dt = input.datum_type();
137
138 let output = match dt {
139 DatumType::F64 => self.eval_t::<f64>(input)?,
140 DatumType::F32 => self.eval_t::<f32>(input)?,
141 DatumType::F16 => self.eval_t::<f16>(input)?,
142 DatumType::QI8(_) | DatumType::QU8(_) => self.eval_quant(input)?,
143 dt => bail!("Unsupported type {dt:?}"),
144 };
145 Ok(output)
146 }
147}
148
149impl Softmax {
150 fn eval_t<T>(&self, input: TValue) -> TractResult<TVec<TValue>>
151 where
152 T: Float + Datum + std::iter::Sum,
153 {
154 let mut output = input.into_tensor();
155
156 let rank = output.rank();
162 let mut sm_axes: TVec<usize> = self.axes.clone();
163 sm_axes.sort_unstable();
164 let trailing = !sm_axes.is_empty()
165 && sm_axes.len() <= rank
166 && sm_axes.iter().enumerate().all(|(i, &a)| a == rank - sm_axes.len() + i);
167 if trailing
168 && matches!(T::datum_type(), DatumType::F32 | DatumType::F16)
169 && output.strides() == &*Tensor::natural_strides(output.shape())
170 {
171 let row_len: usize = sm_axes.iter().map(|&a| output.shape()[a]).product();
172 if row_len > 0 {
173 let kind = self.kind;
174 let mut output_plain = output.try_as_plain_mut()?;
175 if T::datum_type() == f32::datum_type() {
178 let data = output_plain.as_slice_mut::<f32>()?;
179 let total = data.len();
180 tract_linalg::multithread::par_chunks_mut(data, row_len, total, |_, chunk| {
181 for row in chunk.chunks_mut(row_len) {
182 self.softmax_inner_slice_f32(row, kind)?;
183 }
184 Ok(())
185 })?;
186 } else {
187 let data = output_plain.as_slice_mut::<f16>()?;
188 let total = data.len();
189 tract_linalg::multithread::par_chunks_mut(data, row_len, total, |_, chunk| {
190 for row in chunk.chunks_mut(row_len) {
191 self.softmax_inner_slice_f16(row, kind)?;
192 }
193 Ok(())
194 })?;
195 }
196 return Ok(tvec!(output.into_tvalue()));
197 }
198 }
199
200 let mut iterating_shape: TVec<usize> = output.shape().into();
203
204 for i in 0..iterating_shape.len() {
205 if self.axes.contains(&i) {
206 iterating_shape[i] = 1
207 }
208 }
209
210 let mut output_plain = output.try_as_plain_mut()?;
211 let mut view = output_plain.to_array_view_mut::<T>()?;
212
213 for it_coords in tract_ndarray::indices(&*iterating_shape) {
214 let mut view = view.view_mut();
215 for ix in 0..iterating_shape.len() {
216 if !self.axes.contains(&ix) {
217 view.collapse_axis(Axis(ix), it_coords[ix]);
218 }
219 }
220 if let Some(slice) =
221 view.as_slice_mut().filter(|_| T::datum_type() == f32::datum_type())
222 {
223 let slice: &mut [f32] = unsafe { std::mem::transmute(slice) };
224 self.softmax_inner_slice_f32(slice, self.kind)?;
225 } else if let Some(slice) =
226 view.as_slice_mut().filter(|_| T::datum_type() == f16::datum_type())
227 {
228 let slice: &mut [f16] = unsafe { std::mem::transmute(slice) };
229 self.softmax_inner_slice_f16(slice, self.kind)?;
230 } else {
231 softmax_inner(view, self.kind);
232 }
233 }
234
235 Ok(tvec!(output.into_tvalue()))
236 }
237
238 fn eval_quant(&self, input: TValue) -> TractResult<TVec<TValue>> {
239 if self.kind == SoftmaxKind::LogSoftmax {
240 bail!("Quantized LogSoftmax is not supported")
241 }
242 let mut iterating_shape: TVec<usize> = input.shape().into();
243 let output_dt =
244 self.quant_output_dt.context("Quandized softmax eval with no output type")?;
245
246 for i in 0..iterating_shape.len() {
247 if self.axes.contains(&i) {
248 iterating_shape[i] = 1
249 }
250 }
251
252 let src_is_signed = input.datum_type().is_signed();
254 let out_is_signed = output_dt.is_signed();
255 let in_qp = input.datum_type().qparams().unwrap(); let out_qp = output_dt.qparams().unwrap(); let mut output = unsafe { input.into_tensor().into_array_unchecked::<u8>() };
258
259 for it_coords in tract_ndarray::indices(&*iterating_shape) {
260 let mut view = output.view_mut();
261 for ix in 0..iterating_shape.len() {
262 if !self.axes.contains(&ix) {
263 view.collapse_axis(Axis(ix), it_coords[ix]);
264 }
265 }
266 softmax_quant_inner(view, src_is_signed, in_qp, out_is_signed, out_qp);
267 }
268
269 let mut output_tensor = output.into_tensor();
270 unsafe { output_tensor.set_datum_type(output_dt) };
271 Ok(tvec!(output_tensor.into_tvalue()))
272 }
273
274 fn softmax_inner_slice_f16(&self, slice: &mut [f16], kind: SoftmaxKind) -> TractResult<()> {
275 let max = (tract_linalg::ops().max_f16)().run(slice)?;
276 match kind {
277 SoftmaxKind::Softmax(exp_impl) => {
278 let sum = match exp_impl {
279 SoftmaxExp::Libc => {
280 let mut s = f16::zero();
281 slice.iter_mut().for_each(|x| {
282 *x = (*x - max).exp();
283 s += *x;
284 });
285 s
286 }
287 SoftmaxExp::FastCompact => (tract_linalg::ops().softmax2_fastcompact_f16)()
288 .run_with_params(slice, max)?,
289 };
290 let rsum = sum.recip();
291 (tract_linalg::ops().mul_by_scalar_f16)().run_with_params(slice, rsum)?;
292 }
293 SoftmaxKind::LogSoftmax => {
294 let mut exp_sum = f16::zero();
295 slice.iter_mut().for_each(|x| {
296 *x -= max;
297 exp_sum += x.exp();
298 });
299 let log_sum = exp_sum.ln();
300 slice.iter_mut().for_each(|x| *x -= log_sum);
301 }
302 }
303 Ok(())
304 }
305
306 fn softmax_inner_slice_f32(&self, slice: &mut [f32], kind: SoftmaxKind) -> TractResult<()> {
307 let max = (tract_linalg::ops().max_f32)().run(slice)?;
308 match kind {
309 SoftmaxKind::Softmax(exp_impl) => {
310 let sum = match exp_impl {
311 SoftmaxExp::Libc => {
312 let mut s = f32::zero();
313 slice.iter_mut().for_each(|x| {
314 *x = (*x - max).exp();
315 s += *x;
316 });
317 s
318 }
319 SoftmaxExp::FastCompact => (tract_linalg::ops().softmax2_fastcompact_f32)()
320 .run_with_params(slice, max)?,
321 };
322 let rsum = sum.recip();
323 (tract_linalg::ops().mul_by_scalar_f32)().run_with_params(slice, rsum)?;
324 }
325 SoftmaxKind::LogSoftmax => {
326 let mut exp_sum = f32::zero();
327 slice.iter_mut().for_each(|x| {
328 *x -= max;
329 exp_sum += x.exp();
330 });
331 let log_sum = exp_sum.ln();
332 slice.iter_mut().for_each(|x| *x -= log_sum);
333 }
334 }
335 Ok(())
336 }
337}
338
339fn softmax_inner<T: Float + Datum + std::iter::Sum, D: Dimension>(
340 mut view: ArrayViewMut<T, D>,
341 kind: SoftmaxKind,
342) {
343 let max =
344 *view.iter().max_by(|i, j| i.partial_cmp(j).unwrap_or(std::cmp::Ordering::Less)).unwrap();
345 view.mapv_inplace(|x| x - max);
346 let exp_sum = view.iter().map(|&x| x.exp()).sum();
347 match kind {
348 SoftmaxKind::Softmax(_) => {
349 view.mapv_inplace(|x| x.exp() / exp_sum);
350 }
351 SoftmaxKind::LogSoftmax => {
352 let log_sum = exp_sum.ln();
353 view.mapv_inplace(|x| x - log_sum);
354 }
355 }
356}
357
358fn softmax_quant_inner<D: Dimension>(
359 mut view: ArrayViewMut<u8, D>,
360 src_is_signed: bool,
361 in_qp: QParams,
362 out_is_signed: bool,
363 out_qp: QParams,
364) {
365 let (_, in_scale) = in_qp.zp_scale();
366 let (scale_in_multiplier, scale_in_shift) = convert_scale_to_mult_shift(in_scale).unwrap();
367 let (_, out_scale) = out_qp.zp_scale();
368 let (scale_out_multiplier, scale_out_shift) = convert_scale_to_mult_shift(out_scale).unwrap();
369 let shift = 26 - scale_in_shift;
370
371 let mut buffer = vec![0_i32; view.len()];
373
374 let safe_u8 = if src_is_signed { |x: &u8| x.wrapping_add(128) } else { |x: &u8| *x };
376
377 let max = view.iter().map(safe_u8).max().unwrap();
378 view.iter().zip(buffer.iter_mut()).for_each(|(x, exp)| {
379 let input_diff = safe_u8(x) as i32 - max as i32;
380
381 let scaled_input_diff = if scale_in_multiplier != 0 {
383 saturating_rounding_multiply_by_pot(
384 saturating_rounding_doubling_high_mul(input_diff, scale_in_multiplier),
385 shift as i32,
386 )
387 } else {
388 saturating_rounding_multiply_by_pot(input_diff, shift as i32)
389 };
390
391 *exp = exp_on_negative_values(scaled_input_diff);
393 });
394
395 let sum_of_exp = buffer.iter().map(|it| rescale(*it, 0, 12)).sum();
398
399 let (inv_sum_of_exp, num_bits_over_unit) = get_reciprocal(sum_of_exp, 12);
402
403 let exponent = num_bits_over_unit as isize + 31 - 8;
405
406 view.iter_mut().zip(buffer.iter()).for_each(|(it, exp)| {
407 let unsat_output = rounding_divide_by_pot(
409 saturating_rounding_doubling_high_mul(inv_sum_of_exp, *exp),
410 exponent as i32,
411 );
412
413 let unsat_scaled_output = {
415 if scale_out_multiplier != 0 {
416 let (inv_multiplier, num_bits) = get_reciprocal(scale_out_multiplier, 1);
417 rounding_divide_by_pot(
418 saturating_rounding_doubling_high_mul(unsat_output, inv_multiplier),
419 (8 - scale_out_shift - 1 - num_bits as isize) as i32,
420 )
421 } else {
422 rounding_divide_by_pot(unsat_output, (8 - scale_out_shift) as i32)
423 }
424 };
425
426 #[allow(unknown_lints, unnecessary_transmutes)]
429 if out_is_signed {
430 *it = unsafe {
431 std::mem::transmute::<i8, u8>(i32::max(
432 i32::min(unsat_scaled_output, i8::MAX as i32),
433 i8::MIN as i32,
434 ) as i8)
435 };
436 } else {
437 *it = i32::max(i32::min(unsat_scaled_output, u8::MAX as i32), u8::MIN as i32) as u8;
438 }
439 });
440}
441
442#[cfg(test)]
443mod test {
444 use super::*;
445 use crate::ops::nn::DataFormat::NCHW;
446 use anyhow::Result;
447 use num_traits::PrimInt;
448 use proptest::collection::vec;
449 use proptest::prelude::*;
450 use tract_data::internal::QParams::ZpScale;
451
452 fn assert_is_close(found: f32, expected: f32, in_dt: DatumType, out_dt: DatumType) {
453 let (_, in_epsilon) = in_dt.zp_scale();
454 let (_, out_epsilon) = out_dt.zp_scale();
455 let epsilon = in_epsilon + out_epsilon;
456 let error = (found - expected).abs();
457 assert!(
458 error <= epsilon,
459 "epsilon eq failed: |{found:?}-{expected:?}|={error} should be <= {epsilon}"
460 );
461 }
462
463 fn qtensor<T: PrimInt + Datum + Arbitrary>(shape: Vec<usize>) -> BoxedStrategy<Tensor> {
465 let len = shape.iter().product::<usize>();
466 let dt = q_datum::<T>((0.0001f32..0.1).boxed());
467 (vec(any::<T>(), len..=len), dt)
468 .prop_map(move |(vec, dt)| (ArrayD::from_shape_vec(shape.clone(), vec).unwrap(), dt))
469 .prop_map(move |(array, dt)| {
470 let mut tensor = array.into_tensor();
471 unsafe { tensor.set_datum_type(dt) };
472 tensor
473 })
474 .boxed()
475 }
476
477 fn q_datum<T: PrimInt + Datum>(range: BoxedStrategy<f32>) -> BoxedStrategy<DatumType> {
479 let max_integer_bits = std::mem::size_of::<T>() * 8 - T::datum_type().is_signed() as usize;
480 prop_oneof![
481 (1usize..max_integer_bits).prop_map(|fixed_point| { 2f32.powi(-(fixed_point as i32)) }),
482 range
483 ]
484 .prop_map(|scale| {
485 if T::datum_type().is_signed() {
486 DatumType::QI8(ZpScale { zero_point: 0, scale })
487 } else {
488 DatumType::QU8(ZpScale { zero_point: 0, scale })
489 }
490 })
491 .boxed()
492 }
493
494 #[derive(Debug)]
495 struct SoftmaxProblem {
496 data: Tensor,
497 axes: TVec<usize>,
498 output_dt: DatumType,
499 }
500
501 impl SoftmaxProblem {
502 fn check(&self) -> Result<()> {
503 let inputs = tvec!(self.data.clone().into_tvalue());
504 let quant_output_dt = Some(self.output_dt).filter(|dt| !dt.is_float());
505 let softmax =
506 Softmax { axes: self.axes.clone(), quant_output_dt, ..Softmax::default() };
507
508 let result = softmax.eval(inputs)?;
510 let result = args_1!(result);
511 let result_float = result.cast_to::<f32>()?;
512
513 let input_float = self.data.cast_to::<f32>()?;
515 let inputs_float = tvec!(input_float.into_owned().into_tvalue());
516 let softmax_float = Softmax { axes: self.axes.clone(), ..Softmax::default() };
517 let reference_float = softmax_float.eval(inputs_float)?;
518 let reference_array = args_1!(reference_float);
519 let reference = reference_array.to_plain_array_view::<f32>()?;
520
521 result_float
522 .to_plain_array_view::<f32>()?
523 .iter()
524 .zip(reference.iter())
525 .for_each(|(a, b)| assert_is_close(*a, *b, self.data.datum_type(), self.output_dt));
526 Ok(())
527 }
528 }
529
530 impl Arbitrary for SoftmaxProblem {
531 type Parameters = ();
532 type Strategy = BoxedStrategy<SoftmaxProblem>;
533 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
534 (1usize..2, 1usize..2, 1usize..5, 1usize..5, 0usize..4)
535 .prop_flat_map(|(n, c, h, w, axis)| {
536 let shape_in: Vec<usize> =
537 NCHW.from_n_c_hw(n, c, [h, w]).unwrap().shape.to_vec();
538 (
539 prop_oneof![qtensor::<i8>(shape_in.clone()), qtensor::<u8>(shape_in)],
540 Just(tvec![axis]),
541 prop_oneof![
542 q_datum::<u8>((0.008f32..0.1).boxed()),
543 q_datum::<i8>((0.008f32..0.1).boxed())
544 ],
545 )
546 })
547 .prop_map(|(data, axes, output_dt)| SoftmaxProblem { data, axes, output_dt })
548 .boxed()
549 }
550 }
551
552 #[derive(Debug)]
553 pub struct InnerSoftmaxProblem {
554 in_qp: QParams,
555 out_qp: QParams,
556 data: Vec<i8>,
557 }
558
559 impl InnerSoftmaxProblem {
560 fn check(&self) -> Result<()> {
561 let quantized = self.quantized();
562 let reference = self.reference();
563 assert!(quantized.iter().zip(reference.iter()).all(|(quantized, expected)| {
564 let abs_diff = if *quantized > *expected {
565 quantized - *expected
566 } else {
567 expected - *quantized
568 };
569 abs_diff <= 1
570 }));
571 Ok(())
572 }
573
574 fn reference(&self) -> Vec<u8> {
575 let (in_zero_point, in_scale) = self.in_qp.zp_scale();
576 let (out_zero_point, out_scale) = self.out_qp.zp_scale();
577 let in_float =
578 self.data.iter().map(|it| (*it as f32 - in_zero_point as f32) * in_scale).collect();
579 let mut in_float_array = Array1::from_vec(in_float);
580 softmax_inner(in_float_array.view_mut(), SoftmaxKind::default());
581 in_float_array
582 .iter()
583 .map(|it| {
584 ((*it / out_scale).round() as i32 + out_zero_point)
585 .max(u8::MIN as i32)
586 .min(u8::MAX as i32) as u8
587 })
588 .collect()
589 }
590
591 fn quantized(&self) -> Vec<u8> {
592 let in_data: Vec<u8> = unsafe { std::mem::transmute(self.data.clone()) };
593 let mut in_array = Array1::from_vec(in_data);
594 softmax_quant_inner(in_array.view_mut(), true, self.in_qp, false, self.out_qp);
595 in_array.to_vec()
596 }
597 }
598
599 impl Arbitrary for InnerSoftmaxProblem {
600 type Parameters = ();
601 type Strategy = BoxedStrategy<InnerSoftmaxProblem>;
602 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
603 (
604 prop_oneof![
605 q_datum::<i8>((0.0001f32..0.01).boxed()),
606 q_datum::<u8>((0.0001f32..0.01).boxed())
607 ],
608 prop_oneof![
609 q_datum::<u8>((0.008f32..0.1).boxed()),
610 q_datum::<i8>((0.008f32..0.1).boxed())
611 ],
612 vec(any::<i8>(), 1..10),
613 )
614 .prop_map(|(in_qp, out_qp, data)| InnerSoftmaxProblem {
615 in_qp: in_qp.qparams().unwrap(),
616 out_qp: out_qp.qparams().unwrap(),
617 data,
618 })
619 .boxed()
620 }
621 }
622
623 proptest::proptest! {
624 #![proptest_config(ProptestConfig::with_cases(1000))]
625 #[test]
626 fn test_softmax_inner_prop(pb in any::<InnerSoftmaxProblem>()) {
627 pb.check().unwrap()
628 }
629 }
630
631 proptest::proptest! {
632 #![proptest_config(ProptestConfig::with_cases(1000))]
633 #[test]
634 fn test_softmax_prop(pb in any::<SoftmaxProblem>()) {
635 pb.check().unwrap()
636 }
637 }
638
639 #[test]
640 fn test_softmax_trivial_0() -> Result<()> {
642 let input_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.03125 }); let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.00390625 }); let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_u8, 0, 0, 4])?;
645 unsafe { data.set_datum_type(input_dt) };
646
647 let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };
648 prob.check()?;
649 Ok(())
650 }
651
652 #[test]
653 fn test_softmax_trivial_1() -> Result<()> {
655 let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0625 }); let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.00390625 }); let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_i8, 0, 0, 4])?;
658 unsafe { data.set_datum_type(input_dt) };
659
660 let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };
661 prob.check()?;
662 Ok(())
663 }
664
665 #[test]
666 fn test_softmax_trivial_2() -> Result<()> {
668 let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0625 }); let output_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0078125 }); let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_i8, 0, 0, -4])?;
671 unsafe { data.set_datum_type(input_dt) };
672
673 let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };
674 prob.check()?;
675 Ok(())
676 }
677
678 #[test]
679 fn test_softmax_trivial_3() -> Result<()> {
681 let input_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.03125 }); let output_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0078125 }); let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_u8, 0, 0, 4])?;
684 unsafe { data.set_datum_type(input_dt) };
685
686 let prob = SoftmaxProblem { data, axes: tvec![2], output_dt };
687 prob.check()?;
688 Ok(())
689 }
690
691 #[test]
692 fn test_softmax_1() -> Result<()> {
693 let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.5 }); let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.5 }); let mut data = Tensor::from_shape(&[1, 1, 1, 2], &[115_i8, 115])?;
696 unsafe { data.set_datum_type(input_dt) };
697
698 let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };
699 prob.check()?;
700 Ok(())
701 }
702
703 #[test]
704 fn test_softmax_2() -> Result<()> {
705 let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0001 });
706 let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.008 });
707 let mut data = Tensor::from_shape(&[1, 1, 1, 2], &[115_i8, 115])?;
708 unsafe { data.set_datum_type(input_dt) };
709
710 let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };
711 prob.check()?;
712 Ok(())
713 }
714
715 #[test]
716 fn test_softmax_3() -> Result<()> {
717 let input_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.6220956 });
718 let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.5187921 });
719 let mut data = Tensor::from_shape(&[1, 1, 1, 2], &[13_u8, 218])?;
720 unsafe { data.set_datum_type(input_dt) };
721
722 let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };
723 prob.check()?;
724 Ok(())
725 }
726
727 #[test]
728 fn test_inner_softmax_1() -> Result<()> {
729 let in_qp = ZpScale { zero_point: 0, scale: 0.03125 };
730 let out_qp = ZpScale { zero_point: 0, scale: 0.5 };
731 let data = vec![0_i8, 1];
732
733 let prob = InnerSoftmaxProblem { in_qp, out_qp, data };
734 prob.check()?;
735 Ok(())
736 }
737
738 #[test]
739 fn test_inner_softmax_2() -> Result<()> {
740 let in_qp = ZpScale { zero_point: 0, scale: 0.5 };
741 let out_qp = ZpScale { zero_point: 0, scale: 0.03125 };
742 let data = vec![100i8, -28];
743
744 let prob = InnerSoftmaxProblem { in_qp, out_qp, data };
745 prob.check()?;
746 Ok(())
747 }
748
749 #[test]
750 fn test_inner_softmax_not_pow_2_1() -> Result<()> {
751 let in_qp = ZpScale { zero_point: 0, scale: 0.7298456 };
752 let out_qp = ZpScale { zero_point: 0, scale: 0.03125 };
753 let data = vec![100i8, -28];
754
755 let prob = InnerSoftmaxProblem { in_qp, out_qp, data };
756 prob.check()?;
757 Ok(())
758 }
759
760 #[test]
761 #[ignore]
762 fn test_inner_softmax_not_pow_2_2() -> Result<()> {
766 let in_qp = ZpScale { zero_point: 0, scale: 0.2123116 };
767 let out_qp = ZpScale { zero_point: 0, scale: 0.008 };
768 let data = vec![118i8, 108];
769
770 let prob = InnerSoftmaxProblem { in_qp, out_qp, data };
771 prob.check()?;
772 Ok(())
773 }
774
775 #[test]
776 #[ignore]
777 fn test_inner_softmax_not_pow_2_3() -> Result<()> {
781 let in_qp = ZpScale { zero_point: 0, scale: 0.33034274 };
782 let out_qp = ZpScale { zero_point: 0, scale: 0.015625 };
783 let data = vec![45i8, 43];
784
785 let prob = InnerSoftmaxProblem { in_qp, out_qp, data };
786 prob.check()?;
787 Ok(())
788 }
789}