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 Ln,
36 Exp,
37 Hardswish,
38 LeakyRelu,
39 MulByScalar,
40 ReduceMax,
41 ReduceMin,
42 ReduceSum,
43 Softmax2,
44 RmsNorm,
45 Lut,
46 BinByScalar(crate::BinOp),
48 BinUnicast(crate::BinOp),
50 DepthwiseW,
52 DepthwiseC,
55 ConvW4,
58 SoftmaxRows,
60}
61
62impl Func {
63 const BIN: [Func; 12] = {
65 use crate::BinOp::*;
66 [
67 Func::BinByScalar(Min),
68 Func::BinByScalar(Max),
69 Func::BinByScalar(Add),
70 Func::BinByScalar(Mul),
71 Func::BinByScalar(Sub),
72 Func::BinByScalar(SubF),
73 Func::BinUnicast(Min),
74 Func::BinUnicast(Max),
75 Func::BinUnicast(Add),
76 Func::BinUnicast(Mul),
77 Func::BinUnicast(Sub),
78 Func::BinUnicast(SubF),
79 ]
80 };
81
82 pub const ALL: [Func; 32] = [
83 Func::Sigmoid,
84 Func::Tanh,
85 Func::Silu,
86 Func::Gelu,
87 Func::Erf,
88 Func::Ln,
89 Func::Exp,
90 Func::Hardswish,
91 Func::ReduceMax,
92 Func::ReduceMin,
93 Func::ReduceSum,
94 Func::Softmax2,
95 Func::RmsNorm,
96 Func::Lut,
97 Func::LeakyRelu,
98 Func::MulByScalar,
99 Func::BIN[0],
100 Func::BIN[1],
101 Func::BIN[2],
102 Func::BIN[3],
103 Func::BIN[4],
104 Func::BIN[5],
105 Func::BIN[6],
106 Func::BIN[7],
107 Func::BIN[8],
108 Func::BIN[9],
109 Func::BIN[10],
110 Func::BIN[11],
111 Func::DepthwiseW,
112 Func::DepthwiseC,
113 Func::ConvW4,
114 Func::SoftmaxRows,
115 ];
116
117 fn slot(self) -> usize {
119 match self {
120 Func::Sigmoid => 0,
121 Func::Tanh => 1,
122 Func::Silu => 2,
123 Func::Gelu => 3,
124 Func::Erf => 4,
125 Func::Ln => 5,
126 Func::Exp => 6,
127 Func::Hardswish => 7,
128 Func::ReduceMax => 8,
129 Func::ReduceMin => 9,
130 Func::ReduceSum => 10,
131 Func::Softmax2 => 11,
132 Func::RmsNorm => 12,
133 Func::Lut => 13,
134 Func::LeakyRelu => 14,
135 Func::MulByScalar => 15,
136 Func::BinByScalar(op) => 16 + op as usize,
137 Func::BinUnicast(op) => 22 + op as usize,
138 Func::DepthwiseW => 28,
139 Func::DepthwiseC => 29,
140 Func::ConvW4 => 30,
141 Func::SoftmaxRows => 31,
142 }
143 }
144
145 pub fn name(&self) -> &'static str {
147 match self {
148 Func::Sigmoid => "sigmoid",
149 Func::Tanh => "tanh",
150 Func::Silu => "silu",
151 Func::Gelu => "gelu",
152 Func::Erf => "erf",
153 Func::Ln => "ln",
154 Func::Exp => "exp",
155 Func::Hardswish => "hardswish",
156 Func::LeakyRelu => "leaky_relu",
157 Func::MulByScalar => "mul_by_scalar",
158 Func::ReduceMax => "reduce_max",
159 Func::ReduceMin => "reduce_min",
160 Func::ReduceSum => "reduce_sum",
161 Func::Softmax2 => "softmax2",
162 Func::RmsNorm => "rms_norm",
163 Func::Lut => "lut",
164 Func::BinByScalar(op) => match op {
165 crate::BinOp::Min => "by_scalar_min",
166 crate::BinOp::Max => "by_scalar_max",
167 crate::BinOp::Add => "by_scalar_add",
168 crate::BinOp::Mul => "by_scalar_mul",
169 crate::BinOp::Sub => "by_scalar_sub",
170 crate::BinOp::SubF => "by_scalar_subf",
171 },
172 Func::BinUnicast(op) => match op {
173 crate::BinOp::Min => "unicast_min",
174 crate::BinOp::Max => "unicast_max",
175 crate::BinOp::Add => "unicast_add",
176 crate::BinOp::Mul => "unicast_mul",
177 crate::BinOp::Sub => "unicast_sub",
178 crate::BinOp::SubF => "unicast_subf",
179 },
180 Func::DepthwiseW => "depthwise_w",
181 Func::DepthwiseC => "depthwise_c",
182 Func::ConvW4 => "conv_w4",
183 Func::SoftmaxRows => "softmax_rows",
184 }
185 }
186
187 fn best_here(self, dt: DatumType) -> TractResult<&'static Routine> {
191 native_best(self, dt).with_context(|| {
192 format!("No {} kernel for {dt:?} on {:?}", self.name(), crate::isa::native())
193 })
194 }
195
196 pub fn ew_f32(self) -> TractResult<Box<dyn ElementWise<f32>>> {
198 match self.best_here(DatumType::F32)?.factory {
199 RoutineFactory::F32(f) => Ok(f()),
200 _ => bail!("{} is not a plain element-wise kernel", self.name()),
204 }
205 }
206
207 pub fn ew_f16(self) -> TractResult<Box<dyn ElementWise<f16>>> {
209 match self.best_here(DatumType::F16)?.factory {
210 RoutineFactory::F16(f) => Ok(f()),
211 _ => bail!("{} is not a plain element-wise kernel", self.name()),
212 }
213 }
214
215 pub fn ew_f32_param(self) -> TractResult<Box<dyn ElementWise<f32, f32>>> {
217 match self.best_here(DatumType::F32)?.factory {
218 RoutineFactory::F32Param(f) => Ok(f()),
219 _ => bail!("{} is not a scalar-parameter kernel", self.name()),
220 }
221 }
222
223 pub fn ew_f16_param(self) -> TractResult<Box<dyn ElementWise<f16, f16>>> {
225 match self.best_here(DatumType::F16)?.factory {
226 RoutineFactory::F16Param(f) => Ok(f()),
227 _ => bail!("{} is not a scalar-parameter kernel", self.name()),
228 }
229 }
230
231 pub fn reduce_f32(self) -> TractResult<Box<dyn Reduce<f32>>> {
233 match self.best_here(DatumType::F32)?.factory {
234 RoutineFactory::F32Reduce(f) => Ok(f()),
235 _ => bail!("{} is not a reduction", self.name()),
236 }
237 }
238
239 pub fn reduce_f16(self) -> TractResult<Box<dyn Reduce<f16>>> {
241 match self.best_here(DatumType::F16)?.factory {
242 RoutineFactory::F16Reduce(f) => Ok(f()),
243 _ => bail!("{} is not a reduction", self.name()),
244 }
245 }
246
247 pub fn map_reduce_f32(self) -> TractResult<Box<dyn MapReduce<f32, f32>>> {
249 match self.best_here(DatumType::F32)?.factory {
250 RoutineFactory::F32MapReduce(f) => Ok(f()),
251 _ => bail!("{} is not a map-reduction", self.name()),
252 }
253 }
254
255 pub fn bin(self, dt: DatumType) -> Option<Box<crate::BinFn>> {
259 match native_best(self, dt)?.factory {
260 RoutineFactory::BinF32 { make, .. } | RoutineFactory::BinF16 { make, .. } => {
261 Some(make())
262 }
263 _ => None,
264 }
265 }
266}
267
268pub type DepthwiseWF32 = unsafe fn(*const f32, *mut f32, &[f32], &[isize], f32, usize, isize);
276
277pub type DepthwiseCF32 = unsafe fn(
290 *const f32,
291 *mut f32,
292 *const f32,
293 &[usize],
294 &[isize],
295 *const f32,
296 usize,
297 usize,
298 usize,
299 isize,
300 isize,
301);
302
303pub type SoftmaxRowsF32 = fn(&mut [f32], usize);
308
309pub type ConvW4F32 =
319 unsafe fn(*const f32, *mut f32, &[f32], &[isize], &[f32; 4], usize, isize, isize);
320
321#[allow(clippy::type_complexity)]
324pub enum RoutineFactory {
325 F32(fn() -> Box<dyn ElementWise<f32>>),
326 F16(fn() -> Box<dyn ElementWise<f16>>),
327 F32Param(fn() -> Box<dyn ElementWise<f32, f32>>),
329 F16Param(fn() -> Box<dyn ElementWise<f16, f16>>),
330 F32Reduce(fn() -> Box<dyn Reduce<f32>>),
332 F16Reduce(fn() -> Box<dyn Reduce<f16>>),
333 F32MapReduce(fn() -> Box<dyn MapReduce<f32, f32>>),
335 RmsNormF32 {
338 name: &'static str,
339 run: fn(&mut [f32], f32),
340 },
341 LutU8 {
343 name: fn() -> &'static str,
344 make: fn(&[u8]) -> Box<dyn Lut>,
345 },
346 BinF32 {
349 name: fn() -> &'static str,
350 make: fn() -> Box<crate::BinFn>,
351 },
352 BinF16 {
353 name: fn() -> &'static str,
354 make: fn() -> Box<crate::BinFn>,
355 },
356 DepthwiseWF32 {
359 name: &'static str,
360 run: DepthwiseWF32,
361 },
362 DepthwiseCF32 {
364 name: &'static str,
365 run: DepthwiseCF32,
366 },
367 ConvW4F32 {
369 name: &'static str,
370 run: ConvW4F32,
371 },
372 SoftmaxRowsF32 {
374 name: &'static str,
375 run: SoftmaxRowsF32,
376 },
377}
378
379pub struct Routine {
381 pub func: Func,
382 pub arch: Option<Arch>,
384 pub isa: IsaReq,
387 pub boost: isize,
392 pub round_trip: bool,
396 pub factory: RoutineFactory,
397}
398
399inventory::collect!(Routine);
400
401impl Routine {
402 pub fn dt(&self) -> DatumType {
403 match self.factory {
404 RoutineFactory::F32(_)
405 | RoutineFactory::F32Param(_)
406 | RoutineFactory::F32Reduce(_)
407 | RoutineFactory::F32MapReduce(_)
408 | RoutineFactory::RmsNormF32 { .. }
409 | RoutineFactory::DepthwiseWF32 { .. }
410 | RoutineFactory::DepthwiseCF32 { .. }
411 | RoutineFactory::ConvW4F32 { .. }
412 | RoutineFactory::SoftmaxRowsF32 { .. } => DatumType::F32,
413 RoutineFactory::F16(_) | RoutineFactory::F16Param(_) | RoutineFactory::F16Reduce(_) => {
414 DatumType::F16
415 }
416 RoutineFactory::LutU8 { .. } => DatumType::U8,
417 RoutineFactory::BinF32 { .. } => DatumType::F32,
418 RoutineFactory::BinF16 { .. } => DatumType::F16,
419 }
420 }
421
422 pub fn name(&self) -> &'static str {
426 match self.factory {
427 RoutineFactory::F32(f) => f().name(),
428 RoutineFactory::F16(f) => f().name(),
429 RoutineFactory::F32Param(f) => f().name(),
430 RoutineFactory::F16Param(f) => f().name(),
431 RoutineFactory::F32Reduce(f) => f().name(),
432 RoutineFactory::F16Reduce(f) => f().name(),
433 RoutineFactory::F32MapReduce(f) => f().name(),
434 RoutineFactory::RmsNormF32 { name, .. } => name,
435 RoutineFactory::DepthwiseWF32 { name, .. } => name,
436 RoutineFactory::DepthwiseCF32 { name, .. } => name,
437 RoutineFactory::ConvW4F32 { name, .. } => name,
438 RoutineFactory::SoftmaxRowsF32 { name, .. } => name,
439 RoutineFactory::LutU8 { name, .. } => name(),
440 RoutineFactory::BinF32 { name, .. } | RoutineFactory::BinF16 { name, .. } => name(),
441 }
442 }
443
444 pub fn runnable_on(&self, isa: &IsaSet) -> bool {
447 self.arch.is_none_or(|a| Some(a) == isa.arch()) && self.isa.satisfied_by(*isa)
448 }
449
450 fn preference(&self) -> isize {
454 self.isa.level() as isize * LEVEL_BOOST + self.boost
455 }
456}
457
458pub fn declared() -> impl Iterator<Item = &'static Routine> {
460 inventory::iter::<Routine>()
461}
462
463pub fn best_for(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static Routine> {
468 declared()
469 .filter(|r| r.func == func && r.dt() == dt && r.runnable_on(isa))
470 .max_by_key(|r| (r.arch.is_some(), r.preference(), r.name()))
471}
472
473pub struct Settled {
480 pub isa: Isa,
482 pub func: Func,
483 pub dt: DatumType,
484 pub kernel: &'static str,
486 pub why: &'static str,
488}
489
490inventory::collect!(Settled);
491
492impl Settled {
493 pub fn covers(&self, isa: &IsaSet) -> bool {
496 Some(self.isa.arch()) == isa.arch()
497 && isa.has(self.isa)
498 && best_for(self.func, self.dt, isa).is_some_and(|r| r.name() == self.kernel)
499 }
500}
501
502pub fn settlements() -> impl Iterator<Item = &'static Settled> {
504 inventory::iter::<Settled>()
505}
506
507pub fn settled_for(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static Settled> {
510 settlements().find(|s| s.func == func && s.dt == dt && s.covers(isa))
511}
512
513#[derive(Copy, Clone, PartialEq, Eq, Debug)]
516pub enum Standing {
517 Missing,
519 Dedicated,
521 Unspecialized,
524 Emulated,
527 Settled,
529}
530
531fn unsettled(func: Func, dt: DatumType, isa: &IsaSet) -> Standing {
534 let Some(routine) = best_for(func, dt, isa) else { return Standing::Missing };
535 if routine.arch.is_none() && dt == DatumType::F16 {
539 Standing::Emulated
540 } else if routine.round_trip && !isa.fp16_arithmetic() {
541 Standing::Settled
542 } else if routine.arch.is_some() && routine.isa.level() == isa.level() {
543 Standing::Dedicated
544 } else {
545 Standing::Unspecialized
546 }
547}
548
549pub fn standing(func: Func, dt: DatumType, isa: &IsaSet) -> Standing {
551 let answer = unsettled(func, dt, isa);
552 let settleable = matches!(answer, Standing::Unspecialized | Standing::Emulated);
553 if settleable && settled_for(func, dt, isa).is_some() { Standing::Settled } else { answer }
554}
555
556const NO_FP16_ARITHMETIC: &str = "no f16 arithmetic here: a chunk through an f32 kernel is it";
559
560pub fn settled_why(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static str> {
563 if standing(func, dt, isa) != Standing::Settled {
564 return None;
565 }
566 Some(settled_for(func, dt, isa).map_or(NO_FP16_ARITHMETIC, |settled| settled.why))
567}
568
569fn native_best(func: Func, dt: DatumType) -> Option<&'static Routine> {
573 const SLOTS: usize = Func::ALL.len() * 3;
574 static NATIVE: std::sync::OnceLock<[Option<&'static Routine>; SLOTS]> =
575 std::sync::OnceLock::new();
576 let dt_slot = match dt {
577 DatumType::F32 => 0,
578 DatumType::F16 => 1,
579 DatumType::U8 => 2,
580 _ => return None,
581 };
582 NATIVE.get_or_init(|| {
583 let isa = crate::isa::native();
584 let mut table = [None; SLOTS];
585 for func in Func::ALL {
586 for (dt_slot, dt) in [DatumType::F32, DatumType::F16, DatumType::U8].iter().enumerate()
587 {
588 table[func.slot() * 3 + dt_slot] = best_for(func, *dt, &isa);
589 }
590 }
591 table
592 })[func.slot() * 3 + dt_slot]
593}
594
595pub fn rms_norm_f32() -> TractResult<fn(&mut [f32], f32)> {
597 match Func::RmsNorm.best_here(DatumType::F32)?.factory {
598 RoutineFactory::RmsNormF32 { run, .. } => Ok(run),
599 _ => bail!("rms_norm is not a plain function"),
600 }
601}
602
603pub fn depthwise_w_f32() -> Option<DepthwiseWF32> {
607 match native_best(Func::DepthwiseW, DatumType::F32)?.factory {
608 RoutineFactory::DepthwiseWF32 { run, .. } => Some(run),
609 _ => None,
610 }
611}
612
613pub fn depthwise_c_f32() -> Option<DepthwiseCF32> {
616 match native_best(Func::DepthwiseC, DatumType::F32)?.factory {
617 RoutineFactory::DepthwiseCF32 { run, .. } => Some(run),
618 _ => None,
619 }
620}
621
622pub fn conv_w4_f32() -> Option<ConvW4F32> {
625 match native_best(Func::ConvW4, DatumType::F32)?.factory {
626 RoutineFactory::ConvW4F32 { run, .. } => Some(run),
627 _ => None,
628 }
629}
630
631pub fn softmax_rows_f32() -> Option<SoftmaxRowsF32> {
634 match native_best(Func::SoftmaxRows, DatumType::F32)?.factory {
635 RoutineFactory::SoftmaxRowsF32 { run, .. } => Some(run),
636 _ => None,
637 }
638}
639
640pub fn lut_u8(table: &[u8]) -> TractResult<Box<dyn Lut>> {
642 match Func::Lut.best_here(DatumType::U8)?.factory {
643 RoutineFactory::LutU8 { make, .. } => Ok(make(table)),
644 _ => bail!("lut is not a table kernel"),
645 }
646}
647
648macro_rules! submit_routine {
655 (arm; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Arm); $($rest)*); };
656 (aarch64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Aarch64); $($rest)*); };
657 (x86_64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::X86_64); $($rest)*); };
658 (riscv64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::RiscV64); $($rest)*); };
659 (wasm32; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Wasm32Simd128); $($rest)*); };
660 (generic; $($rest:tt)*) => { submit_routine!(@ None; $($rest)*); };
661
662 ($factory:ident, $($rest:tt)*) => { submit_routine!(@ None; $factory, $($rest)*); };
663
664 (@ $arch:expr; RmsNormF32, $func:ident, $name:literal, $run:path
668 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
669 submit_routine!(@@ $arch, $func,
670 $crate::routines::RoutineFactory::RmsNormF32 { name: $name, run: $run }
671 $(, isa($($isa),+))? $(, boost($boost))?);
672 };
673 (@ $arch:expr; DepthwiseWF32, $func:ident, $name:literal, $run:path
674 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
675 submit_routine!(@@ $arch, $func,
676 $crate::routines::RoutineFactory::DepthwiseWF32 { name: $name, run: $run }
677 $(, isa($($isa),+))? $(, boost($boost))?);
678 };
679 (@ $arch:expr; DepthwiseCF32, $func:ident, $name:literal, $run:path
680 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
681 submit_routine!(@@ $arch, $func,
682 $crate::routines::RoutineFactory::DepthwiseCF32 { name: $name, run: $run }
683 $(, isa($($isa),+))? $(, boost($boost))?);
684 };
685 (@ $arch:expr; ConvW4F32, $func:ident, $name:literal, $run:path
686 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
687 submit_routine!(@@ $arch, $func,
688 $crate::routines::RoutineFactory::ConvW4F32 { name: $name, run: $run }
689 $(, isa($($isa),+))? $(, boost($boost))?);
690 };
691 (@ $arch:expr; SoftmaxRowsF32, $func:ident, $name:literal, $run:path
692 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
693 submit_routine!(@@ $arch, $func,
694 $crate::routines::RoutineFactory::SoftmaxRowsF32 { name: $name, run: $run }
695 $(, isa($($isa),+))? $(, boost($boost))?);
696 };
697 (@ $arch:expr; BinF32, BinByScalar($op:ident), $ker:path
698 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
699 submit_routine!(@@ $arch, BinByScalar($crate::BinOp::$op),
700 $crate::routines::RoutineFactory::BinF32 {
701 name: <$ker as $crate::element_wise::ElementWiseKer<f32, f32>>::name,
702 make: <$ker as $crate::by_scalar::ByScalarKer<f32>>::bin,
703 }
704 $(, isa($($isa),+))? $(, boost($boost))?);
705 };
706 (@ $arch:expr; BinF16, BinByScalar($op:ident), $ker:path
707 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
708 submit_routine!(@@ $arch, BinByScalar($crate::BinOp::$op),
709 $crate::routines::RoutineFactory::BinF16 {
710 name: <$ker as $crate::element_wise::ElementWiseKer<$crate::f16, $crate::f16>>::name,
711 make: <$ker as $crate::by_scalar::ByScalarKer<$crate::f16>>::bin,
712 }
713 $(, isa($($isa),+))? $(, boost($boost))?);
714 };
715 (@ $arch:expr; BinF32, BinUnicast($op:ident), $ker:path
716 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
717 submit_routine!(@@ $arch, BinUnicast($crate::BinOp::$op),
718 $crate::routines::RoutineFactory::BinF32 {
719 name: <$ker as $crate::unicast::UnicastKer<f32>>::name,
720 make: <$ker as $crate::unicast::UnicastKer<f32>>::bin,
721 }
722 $(, isa($($isa),+))? $(, boost($boost))?);
723 };
724 (@ $arch:expr; BinF16, BinUnicast($op:ident), $ker:path
725 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
726 submit_routine!(@@ $arch, BinUnicast($crate::BinOp::$op),
727 $crate::routines::RoutineFactory::BinF16 {
728 name: <$ker as $crate::unicast::UnicastKer<$crate::f16>>::name,
729 make: <$ker as $crate::unicast::UnicastKer<$crate::f16>>::bin,
730 }
731 $(, isa($($isa),+))? $(, boost($boost))?);
732 };
733 (@ $arch:expr; LutU8, $func:ident, $ker:path
734 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
735 submit_routine!(@@ $arch, $func,
736 $crate::routines::RoutineFactory::LutU8 {
737 name: <$ker as $crate::lut::LutKer>::name,
738 make: |table| $crate::routines::lut_of::<$ker>(table),
739 }
740 $(, isa($($isa),+))? $(, boost($boost))?);
741 };
742 (@ $arch:expr; F32Reduce, $func:ident, $ker:path
743 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
744 submit_routine!(@@ $arch, $func,
745 $crate::routines::RoutineFactory::F32Reduce(|| $crate::routines::reduce_of::<$ker, _>())
746 $(, isa($($isa),+))? $(, boost($boost))?);
747 };
748 (@ $arch:expr; F16Reduce, $func:ident, $ker:path
749 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
750 submit_routine!(@@ $arch, $func,
751 $crate::routines::RoutineFactory::F16Reduce(|| $crate::routines::reduce_of::<$ker, _>())
752 $(, isa($($isa),+))? $(, boost($boost))?);
753 };
754 (@ $arch:expr; F32MapReduce, $func:ident, $ker:path
755 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
756 submit_routine!(@@ $arch, $func,
757 $crate::routines::RoutineFactory::F32MapReduce(
758 || $crate::routines::map_reduce_of::<$ker, _>()
759 )
760 $(, isa($($isa),+))? $(, boost($boost))?);
761 };
762 (@ $arch:expr; $factory:ident, $func:ident, $ker:path
763 $(, isa($($isa:ident),+))? $(, boost($boost:expr))? $(, round_trip($round_trip:expr))?) => {
764 submit_routine!(@@ $arch, $func,
765 $crate::routines::RoutineFactory::$factory(
766 || $crate::routines::factory_of::<$ker, _, _>()
767 )
768 $(, isa($($isa),+))? $(, boost($boost))? $(, round_trip($round_trip))?);
769 };
770
771 (@@ $arch:expr, $func:ident $(($($payload:tt)*))?, $factory:expr
772 $(, isa($($isa:ident),+))? $(, boost($boost:expr))? $(, round_trip($round_trip:expr))?) => {
773 inventory::submit! {
774 $crate::routines::Routine {
775 func: $crate::routines::Func::$func $(($($payload)*))?,
776 arch: $arch,
777 isa: $crate::isa::IsaReq::ANY $(.needing(&[$($crate::isa::Isa::$isa),+]))?,
778 boost: {
779 #[allow(unused_mut, unused_assignments)]
780 let mut boost = 0;
781 $(boost = $boost;)?
782 boost
783 },
784 round_trip: {
785 #[allow(unused_mut, unused_assignments)]
786 let mut round_trip = false;
787 $(round_trip = $round_trip;)?
788 round_trip
789 },
790 factory: $factory,
791 }
792 }
793 };
794}
795
796macro_rules! settled {
800 ($isa:ident, $func:ident $(($op:ident))?, $dt:ident, $kernel:ident, $why:literal) => {
801 inventory::submit! {
802 $crate::routines::Settled {
803 isa: $crate::isa::Isa::$isa,
804 func: $crate::routines::Func::$func $(($crate::BinOp::$op))?,
805 dt: tract_data::prelude::DatumType::$dt,
806 kernel: stringify!($kernel),
807 why: $why,
808 }
809 }
810 };
811}
812
813pub fn lut_of<K: crate::lut::LutKer + 'static>(table: &[u8]) -> Box<dyn Lut> {
815 Box::new(crate::lut::LutImpl::<K>::new(table))
816}
817
818pub fn reduce_of<K, T>() -> Box<dyn Reduce<T>>
820where
821 T: crate::LADatum,
822 K: ReduceKer<T> + Clone,
823{
824 K::red()
825}
826
827pub fn map_reduce_of<K, T>() -> Box<dyn MapReduce<T, T>>
829where
830 T: crate::LADatum,
831 K: MapReduceKer<T, T> + Clone,
832{
833 K::red()
834}
835
836pub fn factory_of<K, T, P>() -> Box<dyn ElementWise<T, P>>
837where
838 T: crate::LADatum,
839 P: Copy + Send + Sync + std::fmt::Debug + 'static + Default,
840 K: ElementWiseKer<T, P> + Clone,
841{
842 K::ew()
843}
844
845#[cfg(test)]
846mod tests {
847 use super::*;
848
849 #[test]
853 fn the_registry_is_never_empty() {
854 assert!(declared().next().is_some());
855 }
856
857 #[test]
860 fn every_func_owns_a_slot() {
861 let mut slots = std::collections::HashSet::new();
862 for func in Func::ALL {
863 assert!(func.slot() < Func::ALL.len(), "{} is out of the table", func.name());
864 assert!(slots.insert(func.slot()), "{} shares a slot", func.name());
865 }
866 assert_eq!(slots.len(), Func::ALL.len());
867 let isa = crate::isa::native();
868 for func in Func::ALL {
869 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
870 assert_eq!(
871 native_best(func, dt).map(|r| r.name()),
872 best_for(func, dt, &isa).map(|r| r.name()),
873 "{} {dt:?}",
874 func.name()
875 );
876 }
877 }
878 }
879
880 #[test]
884 fn a_kernel_nothing_can_choose_says_so() {
885 let mut chosen = std::collections::HashSet::new();
886 for isa in IsaSet::every_ladder() {
887 for func in Func::ALL {
888 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
889 if let Some(r) = best_for(func, dt, &isa) {
890 chosen.insert((func, dt, r.name()));
891 }
892 }
893 }
894 }
895 for r in declared() {
896 assert!(
897 chosen.contains(&(r.func, r.dt(), r.name())) || r.boost < 0,
898 "{} {:?} {} can never be chosen, and does not decline",
899 r.func.name(),
900 r.dt(),
901 r.name()
902 );
903 }
904 }
905
906 #[test]
910 fn an_unfilled_pair_fails() {
911 let err = Func::Erf.ew_f16().unwrap_err().to_string();
912 assert!(err.starts_with("No erf kernel for F16 on "), "{err}");
913 let err = Func::ReduceMin.reduce_f16().unwrap_err().to_string();
915 assert!(err.starts_with("No reduce_min kernel for F16 on "), "{err}");
916 let err = Func::ReduceMax.ew_f32().unwrap_err().to_string();
918 assert_eq!(err, "reduce_max is not a plain element-wise kernel");
919 }
920 #[test]
924 fn what_this_machine_declares_it_can_build() {
925 let isa = crate::isa::native();
926 for func in Func::ALL {
927 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
928 let Some(routine) = best_for(func, dt, &isa) else { continue };
929 let built = match routine.factory {
930 RoutineFactory::F32(_) => func.ew_f32().map(|k| k.name()),
931 RoutineFactory::F16(_) => func.ew_f16().map(|k| k.name()),
932 RoutineFactory::F32Param(_) => func.ew_f32_param().map(|k| k.name()),
933 RoutineFactory::F16Param(_) => func.ew_f16_param().map(|k| k.name()),
934 RoutineFactory::F32Reduce(_) => func.reduce_f32().map(|k| k.name()),
935 RoutineFactory::F16Reduce(_) => func.reduce_f16().map(|k| k.name()),
936 RoutineFactory::F32MapReduce(_) => func.map_reduce_f32().map(|k| k.name()),
937 RoutineFactory::RmsNormF32 { name, .. }
938 | RoutineFactory::DepthwiseWF32 { name, .. }
939 | RoutineFactory::DepthwiseCF32 { name, .. }
940 | RoutineFactory::ConvW4F32 { name, .. }
941 | RoutineFactory::SoftmaxRowsF32 { name, .. } => Ok(name),
942 RoutineFactory::LutU8 { name, .. } => lut_u8(&[0u8; 256]).map(|_| name()),
943 RoutineFactory::BinF32 { name, .. } | RoutineFactory::BinF16 { name, .. } => {
944 func.bin(dt).map(|_| name()).ok_or_else(|| format_err!("no bin kernel"))
945 }
946 };
947 assert_eq!(
948 built.map_err(|e| e.to_string()),
949 Ok(routine.name()),
950 "{} {dt:?}",
951 func.name()
952 );
953 }
954 }
955 }
956
957 #[test]
961 fn every_settlement_closes_a_cell() {
962 for s in settlements() {
963 assert!(
964 IsaSet::every_ladder().any(|m| s.covers(&m)
965 && matches!(
966 unsettled(s.func, s.dt, &m),
967 Standing::Unspecialized | Standing::Emulated
968 )),
969 "{} {:?} on {} settles nothing, {} being what it keeps",
970 s.func.name(),
971 s.dt,
972 s.isa,
973 s.kernel
974 );
975 }
976 }
977
978 #[test]
981 fn a_cell_is_settled_once() {
982 for isa in IsaSet::every_ladder() {
983 for func in Func::ALL {
984 for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
985 let count = settlements()
986 .filter(|s| s.func == func && s.dt == dt && s.covers(&isa))
987 .count();
988 assert!(
989 count <= 1,
990 "{} {dt:?} on {isa:?} is settled {count} times",
991 func.name()
992 );
993 }
994 }
995 }
996 }
997}