1use crate::element_wise::{ElementWise, ElementWiseKer};
21use crate::isa::{Arch, Isa, IsaReq, IsaSet, LEVEL_BOOST};
22use crate::lut::Lut;
23use crate::reduce::{MapReduce, MapReduceKer, Reduce, ReduceKer};
24use tract_data::internal::*;
25
26#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
29pub enum Func {
30 Sigmoid,
31 Tanh,
32 Silu,
33 Gelu,
34 Erf,
35 Hardswish,
36 LeakyRelu,
37 MulByScalar,
38 ReduceMax,
39 ReduceMin,
40 ReduceSum,
41 Softmax2,
42 RmsNorm,
43 Lut,
44 BinByScalar(crate::BinOp),
46 BinUnicast(crate::BinOp),
48}
49
50impl Func {
51 const BIN: [Func; 12] = {
53 use crate::BinOp::*;
54 [
55 Func::BinByScalar(Min),
56 Func::BinByScalar(Max),
57 Func::BinByScalar(Add),
58 Func::BinByScalar(Mul),
59 Func::BinByScalar(Sub),
60 Func::BinByScalar(SubF),
61 Func::BinUnicast(Min),
62 Func::BinUnicast(Max),
63 Func::BinUnicast(Add),
64 Func::BinUnicast(Mul),
65 Func::BinUnicast(Sub),
66 Func::BinUnicast(SubF),
67 ]
68 };
69
70 pub const ALL: [Func; 26] = [
71 Func::Sigmoid,
72 Func::Tanh,
73 Func::Silu,
74 Func::Gelu,
75 Func::Erf,
76 Func::Hardswish,
77 Func::ReduceMax,
78 Func::ReduceMin,
79 Func::ReduceSum,
80 Func::Softmax2,
81 Func::RmsNorm,
82 Func::Lut,
83 Func::LeakyRelu,
84 Func::MulByScalar,
85 Func::BIN[0],
86 Func::BIN[1],
87 Func::BIN[2],
88 Func::BIN[3],
89 Func::BIN[4],
90 Func::BIN[5],
91 Func::BIN[6],
92 Func::BIN[7],
93 Func::BIN[8],
94 Func::BIN[9],
95 Func::BIN[10],
96 Func::BIN[11],
97 ];
98
99 fn slot(self) -> usize {
101 match self {
102 Func::Sigmoid => 0,
103 Func::Tanh => 1,
104 Func::Silu => 2,
105 Func::Gelu => 3,
106 Func::Erf => 4,
107 Func::Hardswish => 5,
108 Func::ReduceMax => 6,
109 Func::ReduceMin => 7,
110 Func::ReduceSum => 8,
111 Func::Softmax2 => 9,
112 Func::RmsNorm => 10,
113 Func::Lut => 11,
114 Func::LeakyRelu => 12,
115 Func::MulByScalar => 13,
116 Func::BinByScalar(op) => 14 + op as usize,
117 Func::BinUnicast(op) => 20 + op as usize,
118 }
119 }
120
121 pub fn name(&self) -> &'static str {
123 match self {
124 Func::Sigmoid => "sigmoid",
125 Func::Tanh => "tanh",
126 Func::Silu => "silu",
127 Func::Gelu => "gelu",
128 Func::Erf => "erf",
129 Func::Hardswish => "hardswish",
130 Func::LeakyRelu => "leaky_relu",
131 Func::MulByScalar => "mul_by_scalar",
132 Func::ReduceMax => "reduce_max",
133 Func::ReduceMin => "reduce_min",
134 Func::ReduceSum => "reduce_sum",
135 Func::Softmax2 => "softmax2",
136 Func::RmsNorm => "rms_norm",
137 Func::Lut => "lut",
138 Func::BinByScalar(op) => match op {
139 crate::BinOp::Min => "by_scalar_min",
140 crate::BinOp::Max => "by_scalar_max",
141 crate::BinOp::Add => "by_scalar_add",
142 crate::BinOp::Mul => "by_scalar_mul",
143 crate::BinOp::Sub => "by_scalar_sub",
144 crate::BinOp::SubF => "by_scalar_subf",
145 },
146 Func::BinUnicast(op) => match op {
147 crate::BinOp::Min => "unicast_min",
148 crate::BinOp::Max => "unicast_max",
149 crate::BinOp::Add => "unicast_add",
150 crate::BinOp::Mul => "unicast_mul",
151 crate::BinOp::Sub => "unicast_sub",
152 crate::BinOp::SubF => "unicast_subf",
153 },
154 }
155 }
156
157 fn best_here(self, dt: DatumType) -> TractResult<&'static Routine> {
161 native_best(self, dt).with_context(|| {
162 format!("No {} kernel for {dt:?} on {:?}", self.name(), crate::isa::native())
163 })
164 }
165
166 pub fn ew_f32(self) -> TractResult<Box<dyn ElementWise<f32>>> {
168 match self.best_here(DatumType::F32)?.factory {
169 RoutineFactory::F32(f) => Ok(f()),
170 _ => bail!("{} is not a plain element-wise kernel", self.name()),
174 }
175 }
176
177 pub fn ew_f16(self) -> TractResult<Box<dyn ElementWise<f16>>> {
179 match self.best_here(DatumType::F16)?.factory {
180 RoutineFactory::F16(f) => Ok(f()),
181 _ => bail!("{} is not a plain element-wise kernel", self.name()),
182 }
183 }
184
185 pub fn ew_f32_param(self) -> TractResult<Box<dyn ElementWise<f32, f32>>> {
187 match self.best_here(DatumType::F32)?.factory {
188 RoutineFactory::F32Param(f) => Ok(f()),
189 _ => bail!("{} is not a scalar-parameter kernel", self.name()),
190 }
191 }
192
193 pub fn ew_f16_param(self) -> TractResult<Box<dyn ElementWise<f16, f16>>> {
195 match self.best_here(DatumType::F16)?.factory {
196 RoutineFactory::F16Param(f) => Ok(f()),
197 _ => bail!("{} is not a scalar-parameter kernel", self.name()),
198 }
199 }
200
201 pub fn reduce_f32(self) -> TractResult<Box<dyn Reduce<f32>>> {
203 match self.best_here(DatumType::F32)?.factory {
204 RoutineFactory::F32Reduce(f) => Ok(f()),
205 _ => bail!("{} is not a reduction", self.name()),
206 }
207 }
208
209 pub fn reduce_f16(self) -> TractResult<Box<dyn Reduce<f16>>> {
211 match self.best_here(DatumType::F16)?.factory {
212 RoutineFactory::F16Reduce(f) => Ok(f()),
213 _ => bail!("{} is not a reduction", self.name()),
214 }
215 }
216
217 pub fn map_reduce_f32(self) -> TractResult<Box<dyn MapReduce<f32, f32>>> {
219 match self.best_here(DatumType::F32)?.factory {
220 RoutineFactory::F32MapReduce(f) => Ok(f()),
221 _ => bail!("{} is not a map-reduction", self.name()),
222 }
223 }
224
225 pub fn bin(self, dt: DatumType) -> Option<Box<crate::BinFn>> {
229 match native_best(self, dt)?.factory {
230 RoutineFactory::BinF32 { make, .. } | RoutineFactory::BinF16 { make, .. } => {
231 Some(make())
232 }
233 _ => None,
234 }
235 }
236}
237
238#[allow(clippy::type_complexity)]
241pub enum RoutineFactory {
242 F32(fn() -> Box<dyn ElementWise<f32>>),
243 F16(fn() -> Box<dyn ElementWise<f16>>),
244 F32Param(fn() -> Box<dyn ElementWise<f32, f32>>),
246 F16Param(fn() -> Box<dyn ElementWise<f16, f16>>),
247 F32Reduce(fn() -> Box<dyn Reduce<f32>>),
249 F16Reduce(fn() -> Box<dyn Reduce<f16>>),
250 F32MapReduce(fn() -> Box<dyn MapReduce<f32, f32>>),
252 RmsNormF32 {
255 name: &'static str,
256 run: fn(&mut [f32], f32),
257 },
258 LutU8 {
260 name: fn() -> &'static str,
261 make: fn(&[u8]) -> Box<dyn Lut>,
262 },
263 BinF32 {
266 name: fn() -> &'static str,
267 make: fn() -> Box<crate::BinFn>,
268 },
269 BinF16 {
270 name: fn() -> &'static str,
271 make: fn() -> Box<crate::BinFn>,
272 },
273}
274
275pub struct Routine {
277 pub func: Func,
278 pub arch: Option<Arch>,
280 pub isa: IsaReq,
283 pub boost: isize,
288 pub round_trip: bool,
292 pub factory: RoutineFactory,
293}
294
295inventory::collect!(Routine);
296
297impl Routine {
298 pub fn dt(&self) -> DatumType {
299 match self.factory {
300 RoutineFactory::F32(_)
301 | RoutineFactory::F32Param(_)
302 | RoutineFactory::F32Reduce(_)
303 | RoutineFactory::F32MapReduce(_)
304 | RoutineFactory::RmsNormF32 { .. } => DatumType::F32,
305 RoutineFactory::F16(_) | RoutineFactory::F16Param(_) | RoutineFactory::F16Reduce(_) => {
306 DatumType::F16
307 }
308 RoutineFactory::LutU8 { .. } => DatumType::U8,
309 RoutineFactory::BinF32 { .. } => DatumType::F32,
310 RoutineFactory::BinF16 { .. } => DatumType::F16,
311 }
312 }
313
314 pub fn name(&self) -> &'static str {
318 match self.factory {
319 RoutineFactory::F32(f) => f().name(),
320 RoutineFactory::F16(f) => f().name(),
321 RoutineFactory::F32Param(f) => f().name(),
322 RoutineFactory::F16Param(f) => f().name(),
323 RoutineFactory::F32Reduce(f) => f().name(),
324 RoutineFactory::F16Reduce(f) => f().name(),
325 RoutineFactory::F32MapReduce(f) => f().name(),
326 RoutineFactory::RmsNormF32 { name, .. } => name,
327 RoutineFactory::LutU8 { name, .. } => name(),
328 RoutineFactory::BinF32 { name, .. } | RoutineFactory::BinF16 { name, .. } => name(),
329 }
330 }
331
332 pub fn runnable_on(&self, isa: &IsaSet) -> bool {
335 self.arch.is_none_or(|a| Some(a) == isa.arch()) && self.isa.satisfied_by(*isa)
336 }
337
338 fn preference(&self) -> isize {
342 self.isa.level() as isize * LEVEL_BOOST + self.boost
343 }
344}
345
346pub fn declared() -> impl Iterator<Item = &'static Routine> {
348 inventory::iter::<Routine>()
349}
350
351pub fn best_for(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static Routine> {
356 declared()
357 .filter(|r| r.func == func && r.dt() == dt && r.runnable_on(isa))
358 .max_by_key(|r| (r.arch.is_some(), r.preference(), r.name()))
359}
360
361pub struct Settled {
368 pub isa: Isa,
370 pub func: Func,
371 pub dt: DatumType,
372 pub kernel: &'static str,
374 pub why: &'static str,
376}
377
378inventory::collect!(Settled);
379
380impl Settled {
381 pub fn covers(&self, isa: &IsaSet) -> bool {
384 Some(self.isa.arch()) == isa.arch()
385 && isa.has(self.isa)
386 && best_for(self.func, self.dt, isa).is_some_and(|r| r.name() == self.kernel)
387 }
388}
389
390pub fn settlements() -> impl Iterator<Item = &'static Settled> {
392 inventory::iter::<Settled>()
393}
394
395pub fn settled_for(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static Settled> {
398 settlements().find(|s| s.func == func && s.dt == dt && s.covers(isa))
399}
400
401#[derive(Copy, Clone, PartialEq, Eq, Debug)]
404pub enum Standing {
405 Missing,
407 Dedicated,
409 Unspecialized,
412 Emulated,
415 Settled,
417}
418
419fn unsettled(func: Func, dt: DatumType, isa: &IsaSet) -> Standing {
422 let Some(routine) = best_for(func, dt, isa) else { return Standing::Missing };
423 if routine.arch.is_none() && dt == DatumType::F16 {
427 Standing::Emulated
428 } else if routine.round_trip && !isa.fp16_arithmetic() {
429 Standing::Settled
430 } else if routine.arch.is_some() && routine.isa.level() == isa.level() {
431 Standing::Dedicated
432 } else {
433 Standing::Unspecialized
434 }
435}
436
437pub fn standing(func: Func, dt: DatumType, isa: &IsaSet) -> Standing {
439 let answer = unsettled(func, dt, isa);
440 let settleable = matches!(answer, Standing::Unspecialized | Standing::Emulated);
441 if settleable && settled_for(func, dt, isa).is_some() { Standing::Settled } else { answer }
442}
443
444const NO_FP16_ARITHMETIC: &str = "no f16 arithmetic here: a chunk through an f32 kernel is it";
447
448pub fn settled_why(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static str> {
451 if standing(func, dt, isa) != Standing::Settled {
452 return None;
453 }
454 Some(settled_for(func, dt, isa).map_or(NO_FP16_ARITHMETIC, |settled| settled.why))
455}
456
457fn native_best(func: Func, dt: DatumType) -> Option<&'static Routine> {
461 const SLOTS: usize = Func::ALL.len() * 3;
462 static NATIVE: std::sync::OnceLock<[Option<&'static Routine>; SLOTS]> =
463 std::sync::OnceLock::new();
464 let dt_slot = match dt {
465 DatumType::F32 => 0,
466 DatumType::F16 => 1,
467 DatumType::U8 => 2,
468 _ => return None,
469 };
470 NATIVE.get_or_init(|| {
471 let isa = crate::isa::native();
472 let mut table = [None; SLOTS];
473 for func in Func::ALL {
474 for (dt_slot, dt) in [DatumType::F32, DatumType::F16, DatumType::U8].iter().enumerate()
475 {
476 table[func.slot() * 3 + dt_slot] = best_for(func, *dt, &isa);
477 }
478 }
479 table
480 })[func.slot() * 3 + dt_slot]
481}
482
483pub fn rms_norm_f32() -> TractResult<fn(&mut [f32], f32)> {
485 match Func::RmsNorm.best_here(DatumType::F32)?.factory {
486 RoutineFactory::RmsNormF32 { run, .. } => Ok(run),
487 _ => bail!("rms_norm is not a plain function"),
488 }
489}
490
491pub fn lut_u8(table: &[u8]) -> TractResult<Box<dyn Lut>> {
493 match Func::Lut.best_here(DatumType::U8)?.factory {
494 RoutineFactory::LutU8 { make, .. } => Ok(make(table)),
495 _ => bail!("lut is not a table kernel"),
496 }
497}
498
499macro_rules! submit_routine {
506 (arm; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Arm); $($rest)*); };
507 (aarch64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Aarch64); $($rest)*); };
508 (x86_64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::X86_64); $($rest)*); };
509 (riscv64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::RiscV64); $($rest)*); };
510 (wasm32; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Wasm32Simd128); $($rest)*); };
511 (generic; $($rest:tt)*) => { submit_routine!(@ None; $($rest)*); };
512
513 ($factory:ident, $($rest:tt)*) => { submit_routine!(@ None; $factory, $($rest)*); };
514
515 (@ $arch:expr; RmsNormF32, $func:ident, $name:literal, $run:path
519 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
520 submit_routine!(@@ $arch, $func,
521 $crate::routines::RoutineFactory::RmsNormF32 { name: $name, run: $run }
522 $(, isa($($isa),+))? $(, boost($boost))?);
523 };
524 (@ $arch:expr; BinF32, BinByScalar($op:ident), $ker:path
525 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
526 submit_routine!(@@ $arch, BinByScalar($crate::BinOp::$op),
527 $crate::routines::RoutineFactory::BinF32 {
528 name: <$ker as $crate::element_wise::ElementWiseKer<f32, f32>>::name,
529 make: <$ker as $crate::by_scalar::ByScalarKer<f32>>::bin,
530 }
531 $(, isa($($isa),+))? $(, boost($boost))?);
532 };
533 (@ $arch:expr; BinF16, BinByScalar($op:ident), $ker:path
534 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
535 submit_routine!(@@ $arch, BinByScalar($crate::BinOp::$op),
536 $crate::routines::RoutineFactory::BinF16 {
537 name: <$ker as $crate::element_wise::ElementWiseKer<$crate::f16, $crate::f16>>::name,
538 make: <$ker as $crate::by_scalar::ByScalarKer<$crate::f16>>::bin,
539 }
540 $(, isa($($isa),+))? $(, boost($boost))?);
541 };
542 (@ $arch:expr; BinF32, BinUnicast($op:ident), $ker:path
543 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
544 submit_routine!(@@ $arch, BinUnicast($crate::BinOp::$op),
545 $crate::routines::RoutineFactory::BinF32 {
546 name: <$ker as $crate::unicast::UnicastKer<f32>>::name,
547 make: <$ker as $crate::unicast::UnicastKer<f32>>::bin,
548 }
549 $(, isa($($isa),+))? $(, boost($boost))?);
550 };
551 (@ $arch:expr; BinF16, BinUnicast($op:ident), $ker:path
552 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
553 submit_routine!(@@ $arch, BinUnicast($crate::BinOp::$op),
554 $crate::routines::RoutineFactory::BinF16 {
555 name: <$ker as $crate::unicast::UnicastKer<$crate::f16>>::name,
556 make: <$ker as $crate::unicast::UnicastKer<$crate::f16>>::bin,
557 }
558 $(, isa($($isa),+))? $(, boost($boost))?);
559 };
560 (@ $arch:expr; LutU8, $func:ident, $ker:path
561 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
562 submit_routine!(@@ $arch, $func,
563 $crate::routines::RoutineFactory::LutU8 {
564 name: <$ker as $crate::lut::LutKer>::name,
565 make: |table| $crate::routines::lut_of::<$ker>(table),
566 }
567 $(, isa($($isa),+))? $(, boost($boost))?);
568 };
569 (@ $arch:expr; F32Reduce, $func:ident, $ker:path
570 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
571 submit_routine!(@@ $arch, $func,
572 $crate::routines::RoutineFactory::F32Reduce(|| $crate::routines::reduce_of::<$ker, _>())
573 $(, isa($($isa),+))? $(, boost($boost))?);
574 };
575 (@ $arch:expr; F16Reduce, $func:ident, $ker:path
576 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
577 submit_routine!(@@ $arch, $func,
578 $crate::routines::RoutineFactory::F16Reduce(|| $crate::routines::reduce_of::<$ker, _>())
579 $(, isa($($isa),+))? $(, boost($boost))?);
580 };
581 (@ $arch:expr; F32MapReduce, $func:ident, $ker:path
582 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
583 submit_routine!(@@ $arch, $func,
584 $crate::routines::RoutineFactory::F32MapReduce(
585 || $crate::routines::map_reduce_of::<$ker, _>()
586 )
587 $(, isa($($isa),+))? $(, boost($boost))?);
588 };
589 (@ $arch:expr; $factory:ident, $func:ident, $ker:path
590 $(, isa($($isa:ident),+))? $(, boost($boost:expr))? $(, round_trip($round_trip:expr))?) => {
591 submit_routine!(@@ $arch, $func,
592 $crate::routines::RoutineFactory::$factory(
593 || $crate::routines::factory_of::<$ker, _, _>()
594 )
595 $(, isa($($isa),+))? $(, boost($boost))? $(, round_trip($round_trip))?);
596 };
597
598 (@@ $arch:expr, $func:ident $(($($payload:tt)*))?, $factory:expr
599 $(, isa($($isa:ident),+))? $(, boost($boost:expr))? $(, round_trip($round_trip:expr))?) => {
600 inventory::submit! {
601 $crate::routines::Routine {
602 func: $crate::routines::Func::$func $(($($payload)*))?,
603 arch: $arch,
604 isa: $crate::isa::IsaReq::ANY $(.needing(&[$($crate::isa::Isa::$isa),+]))?,
605 boost: {
606 #[allow(unused_mut, unused_assignments)]
607 let mut boost = 0;
608 $(boost = $boost;)?
609 boost
610 },
611 round_trip: {
612 #[allow(unused_mut, unused_assignments)]
613 let mut round_trip = false;
614 $(round_trip = $round_trip;)?
615 round_trip
616 },
617 factory: $factory,
618 }
619 }
620 };
621}
622
623macro_rules! settled {
627 ($isa:ident, $func:ident $(($op:ident))?, $dt:ident, $kernel:ident, $why:literal) => {
628 inventory::submit! {
629 $crate::routines::Settled {
630 isa: $crate::isa::Isa::$isa,
631 func: $crate::routines::Func::$func $(($crate::BinOp::$op))?,
632 dt: tract_data::prelude::DatumType::$dt,
633 kernel: stringify!($kernel),
634 why: $why,
635 }
636 }
637 };
638}
639
640pub fn lut_of<K: crate::lut::LutKer + 'static>(table: &[u8]) -> Box<dyn Lut> {
642 Box::new(crate::lut::LutImpl::<K>::new(table))
643}
644
645pub fn reduce_of<K, T>() -> Box<dyn Reduce<T>>
647where
648 T: crate::LADatum,
649 K: ReduceKer<T> + Clone,
650{
651 K::red()
652}
653
654pub fn map_reduce_of<K, T>() -> Box<dyn MapReduce<T, T>>
656where
657 T: crate::LADatum,
658 K: MapReduceKer<T, T> + Clone,
659{
660 K::red()
661}
662
663pub fn factory_of<K, T, P>() -> Box<dyn ElementWise<T, P>>
664where
665 T: crate::LADatum,
666 P: Copy + Send + Sync + std::fmt::Debug + 'static + Default,
667 K: ElementWiseKer<T, P> + Clone,
668{
669 K::ew()
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[test]
679 fn every_func_owns_a_slot() {
680 let mut slots = std::collections::HashSet::new();
681 for func in Func::ALL {
682 assert!(func.slot() < Func::ALL.len(), "{} is out of the table", func.name());
683 assert!(slots.insert(func.slot()), "{} shares a slot", func.name());
684 }
685 assert_eq!(slots.len(), Func::ALL.len());
686 let isa = crate::isa::native();
687 for func in Func::ALL {
688 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
689 assert_eq!(
690 native_best(func, dt).map(|r| r.name()),
691 best_for(func, dt, &isa).map(|r| r.name()),
692 "{} {dt:?}",
693 func.name()
694 );
695 }
696 }
697 }
698
699 #[test]
703 fn a_kernel_nothing_can_choose_says_so() {
704 let mut chosen = std::collections::HashSet::new();
705 for isa in IsaSet::every_ladder() {
706 for func in Func::ALL {
707 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
708 if let Some(r) = best_for(func, dt, &isa) {
709 chosen.insert((func, dt, r.name()));
710 }
711 }
712 }
713 }
714 for r in declared() {
715 assert!(
716 chosen.contains(&(r.func, r.dt(), r.name())) || r.boost < 0,
717 "{} {:?} {} can never be chosen, and does not decline",
718 r.func.name(),
719 r.dt(),
720 r.name()
721 );
722 }
723 }
724
725 #[test]
729 fn an_unfilled_pair_fails() {
730 let err = Func::Erf.ew_f16().unwrap_err().to_string();
731 assert!(err.starts_with("No erf kernel for F16 on "), "{err}");
732 let err = Func::ReduceMin.reduce_f16().unwrap_err().to_string();
734 assert!(err.starts_with("No reduce_min kernel for F16 on "), "{err}");
735 let err = Func::ReduceMax.ew_f32().unwrap_err().to_string();
737 assert_eq!(err, "reduce_max is not a plain element-wise kernel");
738 }
739 #[test]
743 fn what_this_machine_declares_it_can_build() {
744 let isa = crate::isa::native();
745 for func in Func::ALL {
746 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
747 let Some(routine) = best_for(func, dt, &isa) else { continue };
748 let built = match routine.factory {
749 RoutineFactory::F32(_) => func.ew_f32().map(|k| k.name()),
750 RoutineFactory::F16(_) => func.ew_f16().map(|k| k.name()),
751 RoutineFactory::F32Param(_) => func.ew_f32_param().map(|k| k.name()),
752 RoutineFactory::F16Param(_) => func.ew_f16_param().map(|k| k.name()),
753 RoutineFactory::F32Reduce(_) => func.reduce_f32().map(|k| k.name()),
754 RoutineFactory::F16Reduce(_) => func.reduce_f16().map(|k| k.name()),
755 RoutineFactory::F32MapReduce(_) => func.map_reduce_f32().map(|k| k.name()),
756 RoutineFactory::RmsNormF32 { name, .. } => Ok(name),
757 RoutineFactory::LutU8 { name, .. } => lut_u8(&[0u8; 256]).map(|_| name()),
758 RoutineFactory::BinF32 { name, .. } | RoutineFactory::BinF16 { name, .. } => {
759 func.bin(dt).map(|_| name()).ok_or_else(|| format_err!("no bin kernel"))
760 }
761 };
762 assert_eq!(
763 built.map_err(|e| e.to_string()),
764 Ok(routine.name()),
765 "{} {dt:?}",
766 func.name()
767 );
768 }
769 }
770 }
771
772 #[test]
776 fn every_settlement_closes_a_cell() {
777 for s in settlements() {
778 assert!(
779 IsaSet::every_ladder().any(|m| s.covers(&m)
780 && matches!(
781 unsettled(s.func, s.dt, &m),
782 Standing::Unspecialized | Standing::Emulated
783 )),
784 "{} {:?} on {} settles nothing, {} being what it keeps",
785 s.func.name(),
786 s.dt,
787 s.isa,
788 s.kernel
789 );
790 }
791 }
792
793 #[test]
796 fn a_cell_is_settled_once() {
797 for isa in IsaSet::every_ladder() {
798 for func in Func::ALL {
799 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
800 let count = settlements()
801 .filter(|s| s.func == func && s.dt == dt && s.covers(&isa))
802 .count();
803 assert!(
804 count <= 1,
805 "{} {dt:?} on {isa:?} is settled {count} times",
806 func.name()
807 );
808 }
809 }
810 }
811 }
812}