Skip to main content

tract_linalg/
lib.rs

1#![allow(clippy::missing_safety_doc)]
2#![allow(clippy::redundant_closure_call)]
3#![allow(clippy::len_zero)]
4#![allow(clippy::excessive_precision)]
5#![allow(clippy::approx_constant)]
6#![allow(clippy::manual_is_multiple_of)]
7#![allow(unexpected_cfgs)]
8#![allow(unused_macros)]
9#[macro_use]
10extern crate derive_new;
11extern crate lazy_static;
12extern crate log;
13extern crate num_traits;
14#[macro_use]
15extern crate pastey;
16#[cfg(test)]
17extern crate proptest;
18
19include!(concat!(env!("OUT_DIR"), "/extern_kernel_macro.rs"));
20
21#[macro_use]
22mod frame;
23pub mod cache;
24pub mod generic;
25pub mod knobs;
26pub mod multithread;
27pub use frame::weights::WeightType;
28pub use generic::{ScaleShiftAndRound, Scaler};
29use lazy_static::lazy_static;
30use mmm::{MMMInputFormat, MatMatMul, PanelExtractor};
31use tract_data::internal::TensorView;
32#[cfg(target_arch = "x86_64")]
33pub mod x86_64_fma;
34
35pub mod hwbench;
36
37#[cfg(target_arch = "aarch64")]
38pub mod arm64;
39
40#[cfg(target_arch = "aarch64")]
41pub use arm64::has_fp16;
42use tract_itertools::Itertools;
43
44#[cfg(not(target_arch = "aarch64"))]
45pub fn has_fp16() -> bool {
46    false
47}
48
49#[cfg(any(target_arch = "arm", target_arch = "armv7", target_arch = "arm"))]
50pub mod arm32;
51
52#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
53pub mod wasm;
54
55pub use self::frame::*;
56
57use tract_data::prelude::*;
58
59pub type MMMImpl = Box<
60    dyn Fn(Option<usize>, Option<usize>, Option<usize>) -> Box<dyn mmm::MatMatMul> + Send + Sync,
61>;
62
63type MMVImpl = Box<dyn Fn(Option<usize>, Option<usize>) -> Box<dyn mmm::MatMatMul> + Send + Sync>;
64
65#[allow(clippy::type_complexity)]
66pub struct Ops {
67    mmm_impls: Vec<Box<dyn mmm::MatMatMul>>,
68    panel_extractors: Vec<mmm::PanelExtractor>,
69
70    mmm_f64: MMMImpl,
71    mmv_f64: MMVImpl,
72
73    mmm_f32: MMMImpl,
74    mmv_f32: MMVImpl,
75
76    mmm_f16: MMMImpl,
77    mmv_f16: MMVImpl,
78
79    qmmm_i32: MMMImpl,
80    qmmv_i32: MMVImpl,
81
82    pub leaky_relu_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16, f16>> + Send + Sync>,
83    pub leaky_relu_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32, f32>> + Send + Sync>,
84    pub mul_by_scalar_f32:
85        Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32, f32>> + Send + Sync>,
86    pub mul_by_scalar_f16:
87        Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16, f16>> + Send + Sync>,
88
89    pub sigmoid_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,
90    pub sigmoid_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,
91    pub tanh_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,
92    pub tanh_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,
93    pub erf_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,
94    pub hardswish_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,
95    pub hardswish_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,
96    pub silu_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,
97    pub silu_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,
98    pub gelu_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,
99    pub gelu_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,
100    pub lut_u8: Box<dyn Fn(&[u8]) -> Box<dyn lut::Lut> + Send + Sync>,
101
102    pub max_f16: Box<dyn Fn() -> Box<dyn reduce::Reduce<f16>> + Send + Sync>,
103    pub max_f32: Box<dyn Fn() -> Box<dyn reduce::Reduce<f32>> + Send + Sync>,
104    pub min_f32: Box<dyn Fn() -> Box<dyn reduce::Reduce<f32>> + Send + Sync>,
105
106    pub sum_f16: Box<dyn Fn() -> Box<dyn reduce::Reduce<f16>> + Send + Sync>,
107    pub sum_f32: Box<dyn Fn() -> Box<dyn reduce::Reduce<f32>> + Send + Sync>,
108
109    pub softmax2_fastcompact_f16:
110        Box<dyn Fn() -> Box<dyn reduce::MapReduce<f16, f16>> + Send + Sync>,
111    pub softmax2_fastcompact_f32:
112        Box<dyn Fn() -> Box<dyn reduce::MapReduce<f32, f32>> + Send + Sync>,
113
114    /// Fused row-wise RmsNorm: out_i = x_i * rsqrt(mean(x_i²) + eps).
115    /// Replaces a 4-call composition (MeanOfSquares + Add + Rsqrt + Mul) with
116    /// a single 2-pass kernel. Called once per row by `core::ops::nn::RmsNorm`
117    /// when the input is f32 and the axis is the last (contiguous) one.
118    pub rms_norm_f32: Box<dyn Fn(&mut [f32], f32) + Send + Sync>,
119}
120
121impl Ops {
122    pub fn mmm_impls(&self) -> &[Box<dyn mmm::MatMatMul>] {
123        &self.mmm_impls
124    }
125
126    pub fn all_possible_packing(
127        &self,
128        weight_type: impl Into<WeightType>,
129    ) -> impl Iterator<Item = &dyn MMMInputFormat> {
130        let weight_type = weight_type.into();
131        self.mmm_impls
132            .iter()
133            .flat_map(|m| m.packings())
134            .map(|p| &*p.0)
135            .flat_map(move |p| {
136                let mut packs: Vec<&dyn MMMInputFormat> = vec![];
137                if p.precursor() == weight_type {
138                    packs.push(p)
139                };
140                for pe in &self.panel_extractors {
141                    if pe.from.precursor() == weight_type && pe.to.dyn_eq(p) {
142                        packs.push(&*pe.from);
143                    }
144                }
145                packs.into_iter()
146            })
147            .sorted_by_key(|p| p.to_string())
148            .dedup()
149    }
150
151    pub fn filter_impls<'o>(
152        &'o self,
153        weight: &'o dyn MMMInputFormat,
154        acc: &[DatumType],
155        act: DatumType,
156        store: DatumType,
157    ) -> impl Iterator<
158        Item = (
159            &'o dyn MatMatMul,
160            usize,
161            &'o dyn MMMInputFormat,
162            Option<&'o PanelExtractor>,
163            &'o dyn MMMInputFormat,
164        ),
165    > {
166        let acc = acc.to_vec();
167        self.mmm_impls
168            .iter()
169            .filter(move |mmm| acc.contains(&mmm.internal_type()) && mmm.stores().contains(&store))
170            .flat_map(|mmm| {
171                mmm.packings()
172                    .iter()
173                    .enumerate()
174                    .map(|(pack_ix, (a, b))| (&**mmm, pack_ix, &**a, &**b))
175            })
176            .filter_map(|(mmm, ix, a, b)| {
177                if a.dyn_eq(weight) {
178                    Some((mmm, ix, a, None, b))
179                } else {
180                    self.panel_extractors
181                        .iter()
182                        .find(|pe| pe.from.dyn_eq(weight) && pe.to.dyn_eq(a))
183                        .map(|pe| (mmm, ix, a, Some(pe), b))
184                }
185            })
186            .filter(move |(_mmm, _ix, _a, _pe, b)| {
187                b.precursor().as_dt().is_some_and(|dt| dt == act)
188            })
189    }
190
191    pub fn panel_extractors(&self) -> &[mmm::panel_extract::PanelExtractor] {
192        &self.panel_extractors
193    }
194
195    pub fn mmm(
196        &self,
197        accumulator: DatumType,
198        m: Option<usize>,
199        k: Option<usize>,
200        n: Option<usize>,
201    ) -> Option<Box<dyn mmm::MatMatMul>> {
202        use DatumType::*;
203        match accumulator {
204            F64 => Some(if n == Some(1) { (self.mmv_f64)(m, k) } else { (self.mmm_f64)(m, k, n) }),
205            F32 => Some(if n == Some(1) { (self.mmv_f32)(m, k) } else { (self.mmm_f32)(m, k, n) }),
206            F16 => Some(if n == Some(1) { (self.mmv_f16)(m, k) } else { (self.mmm_f16)(m, k, n) }),
207            I32 => {
208                Some(if n == Some(1) { (self.qmmv_i32)(m, k) } else { (self.qmmm_i32)(m, k, n) })
209            }
210            _ => None,
211        }
212    }
213}
214
215pub fn generic() -> Ops {
216    use crate::generic::mmm::*;
217    use element_wise::ElementWiseKer;
218    use reduce::{MapReduceKer, ReduceKer};
219    let mut ops = Ops {
220        mmm_impls: vec![],
221        panel_extractors: vec![],
222        mmm_f64: Box::new(|_, _, _| generic_f64_4x4.mmm()),
223        mmv_f64: Box::new(|_, _| generic_f64_4x1.mmm()),
224        mmm_f32: Box::new(|_, _, _| generic_f32_4x4.mmm()),
225        mmv_f32: Box::new(|_, _| generic_f32_4x1.mmm()),
226        mmm_f16: Box::new(|_, _, _| generic_f16_4x4.mmm()),
227        mmv_f16: Box::new(|_, _| generic_f16_4x1.mmm()),
228        qmmm_i32: Box::new(|_, _, _| generic_i32_4x4.mmm()),
229        qmmv_i32: Box::new(|_, _| generic_i32_4x4.mmm()),
230        leaky_relu_f16: Box::new(|| generic::HLeakyRelu8::ew()),
231        leaky_relu_f32: Box::new(|| generic::SLeakyRelu4::ew()),
232        mul_by_scalar_f16: Box::new(|| generic::HMulByScalar8::ew()),
233        mul_by_scalar_f32: Box::new(|| generic::SMulByScalar4::ew()),
234        sigmoid_f16: Box::new(|| generic::HSigmoid8::ew()),
235        sigmoid_f32: Box::new(|| generic::SSigmoid4::ew()),
236        tanh_f16: Box::new(|| generic::HTanh8::ew()),
237        tanh_f32: Box::new(|| generic::STanh4::ew()),
238        erf_f32: Box::new(|| generic::SErf4::ew()),
239        hardswish_f16: Box::new(|| generic::HHardSwish8::ew()),
240        hardswish_f32: Box::new(|| generic::SHardSwish4::ew()),
241        silu_f16: Box::new(|| generic::HSiLU8::ew()),
242        silu_f32: Box::new(|| generic::SSiLU4::ew()),
243        gelu_f16: Box::new(|| generic::HGelu8::ew()),
244        gelu_f32: Box::new(|| generic::SGelu4::ew()),
245        lut_u8: Box::new(|table: &[u8]| Box::new(lut::LutImpl::<generic::GenericLut8>::new(table))),
246        max_f16: Box::new(|| generic::reduce::max::HMax8::red()),
247        max_f32: Box::new(|| generic::reduce::max::SMax4::red()),
248        min_f32: Box::new(|| generic::reduce::min::SMin4::red()),
249        sum_f16: Box::new(|| generic::reduce::sum::HSum8::red()),
250        sum_f32: Box::new(|| generic::reduce::sum::SSum4::red()),
251        /*
252        activation_f32: Box::new(|microcode| generic::SActivation::new(microcode))
253        */
254        softmax2_fastcompact_f16: Box::new(|| generic::reduce::softmax_l2::HSoftMaxL2::red()),
255        softmax2_fastcompact_f32: Box::new(|| generic::reduce::softmax_l2::SSoftMaxL2::red()),
256        rms_norm_f32: Box::new(generic::rms_norm::rms_norm_f32),
257    };
258    crate::generic::mmm::plug(&mut ops);
259    ops
260}
261
262#[allow(unreachable_code, unused_mut, unexpected_cfgs)]
263pub fn best() -> Ops {
264    let mut ops = generic();
265    #[cfg(target_arch = "x86_64")]
266    x86_64_fma::plug(&mut ops);
267    #[cfg(any(target_arch = "arm", target_arch = "armv7"))]
268    arm32::plug(&mut ops);
269    #[cfg(target_arch = "aarch64")]
270    arm64::plug(&mut ops);
271    #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
272    wasm::plug(&mut ops);
273
274    ops
275}
276
277lazy_static::lazy_static! {
278    static ref OPS: Ops = {
279        best()
280    };
281}
282
283#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
284pub enum BinOp {
285    Min,
286    Max,
287    Add,
288    Mul,
289    Sub,
290    SubF,
291}
292
293impl BinOp {
294    pub fn flip(&self) -> BinOp {
295        use BinOp::*;
296        match self {
297            Sub => SubF,
298            SubF => Sub,
299            sym => *sym,
300        }
301    }
302}
303
304fn register_all_unicast(registry: &mut LinalgRegistry) {
305    generic::register_all_unicast(registry);
306    #[cfg(target_arch = "aarch64")]
307    arm64::register_all_unicast(registry);
308}
309
310fn register_all_by_scalar(registry: &mut LinalgRegistry) {
311    generic::register_all_by_scalar(registry);
312    #[cfg(target_arch = "aarch64")]
313    arm64::register_all_by_scalar(registry);
314}
315
316pub type LinalgFn = dyn Fn(&mut TensorView, &TensorView) -> TractResult<()> + Send + Sync;
317type LinalgRegistry = HashMap<(BinOp, DatumType), Box<dyn Fn() -> Box<LinalgFn> + Send + Sync>>;
318lazy_static! {
319    static ref BIN_UNICAST_OPS: Mutex<LinalgRegistry> = {
320        let mut registry = HashMap::default();
321        register_all_unicast(&mut registry);
322        Mutex::new(registry)
323    };
324    static ref BIN_BY_SCALAR_OPS: Mutex<LinalgRegistry> = {
325        let mut registry = HashMap::default();
326        register_all_by_scalar(&mut registry);
327        Mutex::new(registry)
328    };
329}
330
331pub fn bin_by_scalar(dt: DatumType, bin: BinOp) -> Option<Box<LinalgFn>> {
332    let map = BIN_BY_SCALAR_OPS.lock().unwrap();
333    if (dt == DatumType::F16) && !has_fp16() {
334        return None;
335    }
336    map.get(&(bin, dt)).map(|it| (it)())
337}
338
339pub fn bin_unicast(dt: DatumType, bin: BinOp) -> Option<Box<LinalgFn>> {
340    let map = BIN_UNICAST_OPS.lock().unwrap();
341    if (dt == DatumType::F16) && !has_fp16() {
342        return None;
343    }
344    map.get(&(bin, dt)).map(|it| (it)())
345}
346
347pub fn ops() -> &'static Ops {
348    &OPS
349}
350
351use dyn_eq::DynEq;
352use num_traits::*;
353use std::collections::HashMap;
354use std::fmt::Debug;
355use std::ops::*;
356use std::sync::Mutex;
357
358pub trait LADatum:
359    Sized
360    + std::fmt::Display
361    + Debug
362    + Copy
363    + Clone
364    + Zero
365    + One
366    + 'static
367    + Add<Output = Self>
368    + Sub<Output = Self>
369    + Mul
370    + AddAssign
371    + PartialOrd
372    + Bounded
373    + tract_data::prelude::Datum
374{
375    #[cfg(test)]
376    fn strat() -> proptest::prelude::BoxedStrategy<Self>;
377}
378
379#[cfg(test)]
380use proptest::prelude::*;
381
382impl LADatum for f16 {
383    #[cfg(test)]
384    fn strat() -> BoxedStrategy<Self> {
385        f32::strat().prop_map(|f| f.as_()).boxed()
386    }
387}
388
389impl LADatum for f32 {
390    #[cfg(test)]
391    fn strat() -> BoxedStrategy<Self> {
392        (-1000isize..1000).prop_map(|i| i as f32 / 1000.0).boxed()
393    }
394}
395
396impl LADatum for f64 {
397    #[cfg(test)]
398    fn strat() -> BoxedStrategy<Self> {
399        (-1000isize..1000).prop_map(|i| i as f64 / 1000.0).boxed()
400    }
401}
402
403impl LADatum for u8 {
404    #[cfg(test)]
405    fn strat() -> BoxedStrategy<Self> {
406        any::<u8>().boxed()
407    }
408}
409
410impl LADatum for i8 {
411    #[cfg(test)]
412    fn strat() -> BoxedStrategy<Self> {
413        any::<i8>().boxed()
414    }
415}
416
417impl LADatum for i32 {
418    #[cfg(test)]
419    fn strat() -> BoxedStrategy<Self> {
420        any::<i32>().boxed()
421    }
422}
423
424#[cfg(test)]
425#[allow(dead_code)]
426fn setup_test_logger() {
427    let _ = env_logger::Builder::from_env("TRACT_LOG").try_init();
428}