1use std::fmt::Debug;
2use std::marker::PhantomData;
3
4use tract_data::TractResult;
5
6use crate::LADatum;
7
8use super::element_wise_helper::map_slice_with_alignment;
9
10macro_rules! routine_ew_rust {
19 (arm; $($rest:tt)*) => { routine_ew_rust!(@ arm, target_arch = "arm"; $($rest)*); };
20 (aarch64; $($rest:tt)*) => { routine_ew_rust!(@ aarch64, target_arch = "aarch64"; $($rest)*); };
21 (x86_64; $($rest:tt)*) => { routine_ew_rust!(@ x86_64, target_arch = "x86_64"; $($rest)*); };
22 (riscv64; $($rest:tt)*) => {
23 routine_ew_rust!(@ riscv64, target_arch = "riscv64"; $($rest)*);
24 };
25 (wasm32; $($rest:tt)*) => {
26 routine_ew_rust!(@ wasm32, all(target_arch = "wasm32", target_feature = "simd128");
27 $($rest)*);
28 };
29 (generic; $($rest:tt)*) => { routine_ew_rust!(@ generic, all(); $($rest)*); };
30
31 (@ $arch:ident, $built:meta; $ti:ident, $ker:ident, $nr:expr, $alignment_items:expr,
34 $run:item, func($f:ident), param $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
35 paste! {
36 routine_ew_rust!(@@ $arch, $built; $ti, $ker, $nr, $alignment_items, $ti, $run, $f,
37 [<$ti:upper Param>] $(, isa($($isa),+))? $(, boost($boost))?);
38 }
39 };
40 (@ $arch:ident, $built:meta; $ti:ident, $ker:ident, $nr:expr, $alignment_items:expr,
41 $run:item, func($f:ident) $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
42 paste! {
43 routine_ew_rust!(@@ $arch, $built; $ti, $ker, $nr, $alignment_items, (), $run, $f,
44 [<$ti:upper>] $(, isa($($isa),+))? $(, boost($boost))?);
45 }
46 };
47
48 (@@ $arch:ident, $built:meta; $ti:ident, $ker:ident, $nr:expr, $alignment_items:expr,
49 $params:ty, $run:item, $f:ident, $factory:ident
50 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
51 ew_kernel!(@ $built; $ti, $ker, $nr, $alignment_items, $params, $run);
52 paste! {
53 submit_routine!($arch; $factory, $f, $ker $(, isa($($isa),+))? $(, boost($boost))?);
54 #[cfg(test)]
55 mod [<test_ $ker:snake>] {
56 use super::*;
57 crate::[<$f:snake _frame_tests>]!(
58 cfg!($built)
59 && $crate::isa::IsaReq::ANY
60 $(.needing(&[$($crate::isa::Isa::$isa),+]))?
61 .satisfied_by($crate::isa::native()),
62 $ti,
63 $ker
64 );
65 }
66 }
67 };
68}
69
70macro_rules! ew_kernel {
71 (arm; $($rest:tt)*) => { ew_kernel!(@ target_arch = "arm"; $($rest)*); };
72 (aarch64; $($rest:tt)*) => { ew_kernel!(@ target_arch = "aarch64"; $($rest)*); };
73 (x86_64; $($rest:tt)*) => { ew_kernel!(@ target_arch = "x86_64"; $($rest)*); };
74 (riscv64; $($rest:tt)*) => { ew_kernel!(@ target_arch = "riscv64"; $($rest)*); };
75 (wasm32; $($rest:tt)*) => { ew_kernel!(@ all(target_arch = "wasm32", target_feature = "simd128"); $($rest)*); };
76
77 (@ $built:meta; $ti:ident, $func:ident, $nr:expr, $alignment_items:expr, $params:ty, $run:item) => {
78 #[cfg($built)]
79 ew_kernel!($ti, $func, $nr, $alignment_items, $params, $run);
80 #[cfg(not($built))]
81 ew_kernel!($ti, $func, $nr, $alignment_items, $params,
82 fn run(_vec: &mut [$ti], _params: $params) {
83 panic!(concat!(stringify!($func), ": kernel not built for this target"))
84 }
85 );
86 };
87
88 ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty, $run: item) => {
89 paste! {
90 #[derive(Copy, Clone, Debug)]
91 #[allow(non_camel_case_types)]
92 pub struct $func;
93
94 impl crate::frame::element_wise::ElementWiseKer<$ti, $params> for $func {
95 #[inline(always)]
96 fn name() -> &'static str {
97 stringify!($func)
98 }
99 #[inline(always)]
100 fn nr() -> usize {
101 $nr
102 }
103 #[inline(always)]
104 fn alignment_items() -> usize {
105 $alignment_items
106 }
107 $run
108 }
109 }
110 };
111}
112
113macro_rules! routine_ew_via_f32 {
135 (aarch64; $($rest:tt)*) => {
136 routine_ew_via_f32!(@ aarch64, target_arch = "aarch64"; $($rest)*);
137 };
138 (x86_64; $($rest:tt)*) => {
139 routine_ew_via_f32!(@ x86_64, target_arch = "x86_64"; $($rest)*);
140 };
141 (arm; $($rest:tt)*) => { routine_ew_via_f32!(@ arm, target_arch = "arm"; $($rest)*); };
142 (wasm32; $($rest:tt)*) => {
143 routine_ew_via_f32!(@ wasm32, all(target_arch = "wasm32", target_feature = "simd128");
144 $($rest)*);
145 };
146
147 (@ $arch:ident, $built:meta; $ker:ident, $nr:expr, $alignment_items:expr, $chunk:expr,
148 $scratch_align:literal, $cvt_in:path, $cvt_out:path, $f32_kernel:ty, func($f:ident),
149 param($pname:ident => $pconv:expr) $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
150 ew_kernel_via_f32!($ker, $nr, $alignment_items, $chunk, $scratch_align,
151 $cvt_in, $cvt_out, $f32_kernel, f16, $pname => $pconv);
152 routine_ew_via_f32!(@@ $arch, $built; $ker, $f, F16Param
153 $(, isa($($isa),+))? $(, boost($boost))?);
154 };
155
156 (@ $arch:ident, $built:meta; $ker:ident, $nr:expr, $alignment_items:expr, $chunk:expr,
157 $scratch_align:literal, $cvt_in:path, $cvt_out:path, $f32_kernel:ty, func($f:ident)
158 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
159 ew_kernel_via_f32!($ker, $nr, $alignment_items, $chunk, $scratch_align,
160 $cvt_in, $cvt_out, $f32_kernel);
161 routine_ew_via_f32!(@@ $arch, $built; $ker, $f, F16
162 $(, isa($($isa),+))? $(, boost($boost))?);
163 };
164
165 (@@ $arch:ident, $built:meta; $ker:ident, $f:ident, $factory:ident
166 $(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
167 submit_routine!($arch; $factory, $f, $ker
168 $(, isa($($isa),+))? $(, boost($boost))?, round_trip(true));
169 paste! {
170 #[cfg(test)]
171 mod [<test_ $ker:snake>] {
172 use super::*;
173 crate::[<$f:snake _frame_tests>]!(
174 cfg!($built)
175 && $crate::isa::IsaReq::ANY
176 $(.needing(&[$($crate::isa::Isa::$isa),+]))?
177 .satisfied_by($crate::isa::native()),
178 f16,
179 $ker
180 );
181 }
182 }
183 };
184}
185
186macro_rules! ew_kernel_via_f32 {
187 ($func:ident, $nr:expr, $alignment_items:expr, $chunk:expr, $scratch_align:literal,
188 $cvt_in:path, $cvt_out:path, $f32_kernel:ty) => {
189 ew_kernel_via_f32!(@build $func, $nr, $alignment_items, $chunk, $scratch_align,
190 $cvt_in, $cvt_out, $f32_kernel, (), _params, ());
191 };
192 ($func:ident, $nr:expr, $alignment_items:expr, $chunk:expr, $scratch_align:literal,
193 $cvt_in:path, $cvt_out:path, $f32_kernel:ty, $params:ty, $pname:ident => $pconv:expr) => {
194 ew_kernel_via_f32!(@build $func, $nr, $alignment_items, $chunk, $scratch_align,
195 $cvt_in, $cvt_out, $f32_kernel, $params, $pname, $pconv);
196 };
197 (@build $func:ident, $nr:expr, $alignment_items:expr, $chunk:expr, $scratch_align:literal,
198 $cvt_in:path, $cvt_out:path, $f32_kernel:ty, $params:ty, $pname:ident, $pconv:expr) => {
199 ew_kernel!(
200 f16, $func, $nr, $alignment_items, $params,
201 #[inline(never)]
202 fn run(buf: &mut [f16], $pname: $params) {
203 const _: () = assert!(
204 $chunk % $nr == 0,
205 "CHUNK must be a multiple of nr; the f32 kernel steps nr lanes with no tail"
206 );
207 #[repr(C, align($scratch_align))]
208 struct AlignedScratch([f32; $chunk]);
209 debug_assert!(buf.len() % Self::nr() == 0);
210 debug_assert!(buf.as_ptr() as usize % Self::alignment_bytes() == 0);
211 if buf.is_empty() {
212 return;
213 }
214 let f32_params = $pconv;
215 let mut scratch = std::mem::MaybeUninit::<AlignedScratch>::uninit();
216 let s = unsafe { &mut (*scratch.as_mut_ptr()).0 };
220 let mut i = 0;
221 while i < buf.len() {
222 let n = ($chunk).min(buf.len() - i);
223 unsafe { $cvt_in(&buf[i..i + n], &mut s[..n]) };
224 <$f32_kernel>::run(&mut s[..n], f32_params);
225 unsafe { $cvt_out(&s[..n], &mut buf[i..i + n]) };
226 i += n;
227 }
228 }
229 );
230 };
231}
232
233macro_rules! routine_ew_extern {
247 (arm; $($rest:tt)*) => { routine_ew_extern!(@ arm, target_arch = "arm"; $($rest)*); };
248 (aarch64; $($rest:tt)*) => { routine_ew_extern!(@ aarch64, target_arch = "aarch64"; $($rest)*); };
249 (x86_64; $($rest:tt)*) => { routine_ew_extern!(@ x86_64, target_arch = "x86_64"; $($rest)*); };
250 (riscv64; $($rest:tt)*) => { routine_ew_extern!(@ riscv64, target_arch = "riscv64"; $($rest)*); };
251 (wasm32; $($rest:tt)*) => {
252 routine_ew_extern!(@ wasm32,
253 all(target_arch = "wasm32", target_feature = "simd128"); $($rest)*);
254 };
255
256 (@ $arch:ident, $built:meta; $func:ident, $ti:ident, $ker:ident,
257 $nr:expr, $alignment_items:expr $(, isa($($isa:ident),+))?) => {
258 ew_kernel_extern!($arch; $ti, $ker, $nr, $alignment_items);
259 paste! {
260 submit_routine!($arch; [<$ti:upper>], $func, $ker $(, isa($($isa),+))?);
261 }
262 #[cfg(test)]
263 paste! {
264 mod [<test_ $ker:snake>] {
265 use super::*;
266 [<$func:snake _frame_tests>]!(
267 cfg!($built)
268 && $crate::isa::IsaReq::ANY
269 $(.needing(&[$($crate::isa::Isa::$isa),+]))?
270 .satisfied_by($crate::isa::native()),
271 $ti,
272 $ker
273 );
274 }
275 }
276 };
277}
278
279macro_rules! ew_kernel_extern {
280 (arm; $($rest:tt)*) => { ew_kernel_extern!(@ target_arch = "arm"; $($rest)*); };
281 (aarch64; $($rest:tt)*) => { ew_kernel_extern!(@ target_arch = "aarch64"; $($rest)*); };
282 (x86_64; $($rest:tt)*) => { ew_kernel_extern!(@ target_arch = "x86_64"; $($rest)*); };
283 (riscv64; $($rest:tt)*) => { ew_kernel_extern!(@ target_arch = "riscv64"; $($rest)*); };
284 (wasm32; $($rest:tt)*) => { ew_kernel_extern!(@ all(target_arch = "wasm32", target_feature = "simd128"); $($rest)*); };
285
286 (@ $built:meta; $ti:ident, $func:ident, $nr:expr, $alignment_items:expr) => {
287 paste! {
288 mod [<sys_ $func>] {
289 #[allow(unused_imports)]
290 use tract_data::prelude::f16;
291
292 #[cfg($built)]
293 extern_kernel!(fn $func(ptr: *mut $ti, count: usize) -> ());
294
295 #[cfg(not($built))]
296 #[allow(dead_code)]
297 pub unsafe fn $func(_ptr: *mut $ti, _count: usize) {
298 panic!(concat!(stringify!($func), ": activation kernel not built for this target"))
299 }
300 }
301 ew_kernel!($ti, $func, $nr, $alignment_items, (),
302 #[inline(never)]
303 fn run(buf: &mut [$ti], _params: ()) {
304 unsafe { [<sys_ $func>]::$func(buf.as_mut_ptr(), buf.len()) }
305 }
306 );
307 }
308 };
309
310}
311
312pub trait ElementWise<T, Params = ()>: Send + Sync + Debug + dyn_clone::DynClone
313where
314 Params: Copy + Send + Sync + Debug + 'static + Default,
315 T: Copy + Debug + PartialEq + Send + Sync,
316{
317 fn name(&self) -> &'static str;
318 fn run(&self, vec: &mut [T]) -> TractResult<()> {
319 self.run_with_params(vec, Params::default())
320 }
321 fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<()>;
322}
323
324dyn_clone::clone_trait_object!(<T, Params> ElementWise<T, Params> where T: Copy, Params: Copy);
325
326#[derive(Debug, Clone, new)]
327pub struct ElementWiseImpl<K, T, Params = ()>
328where
329 T: LADatum,
330 Params: Copy + Send + Sync + Debug + 'static + Default,
331 K: ElementWiseKer<T, Params> + Clone,
332{
333 phantom: PhantomData<(K, T, Params)>,
334}
335
336impl<K, T, Params> ElementWise<T, Params> for ElementWiseImpl<K, T, Params>
337where
338 T: LADatum,
339 Params: Copy + Send + Sync + Debug + 'static + Default,
340 K: ElementWiseKer<T, Params> + Clone,
341{
342 fn name(&self) -> &'static str {
343 K::name()
344 }
345 fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<()> {
346 map_slice_with_alignment(vec, |data| K::run(data, params), K::nr(), K::alignment_bytes())
347 }
348}
349
350pub trait ElementWiseKer<T, Params = ()>:
351 Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static
352where
353 Params: Copy + Send + Sync + Debug + 'static + Default,
354 T: LADatum,
355{
356 fn name() -> &'static str;
357 fn alignment_bytes() -> usize {
358 Self::alignment_items() * T::datum_type().size_of()
359 }
360 fn alignment_items() -> usize;
361 fn nr() -> usize;
362 fn run(vec: &mut [T], params: Params);
363 fn ew() -> Box<dyn ElementWise<T, Params>> {
364 Box::new(ElementWiseImpl::<Self, T, Params>::new())
365 }
366}
367
368#[cfg(test)]
369pub mod test {
370 use crate::{LADatum, frame::element_wise::*};
371 use num_traits::AsPrimitive;
372 use proptest::test_runner::{TestCaseError, TestCaseResult};
373 use tract_data::internal::*;
374
375 fn invariant_sweep<T: LADatum>() -> Vec<T>
386 where
387 f32: AsPrimitive<T>,
388 {
389 if T::datum_type() == f16::datum_type() {
390 let all: Vec<f16> =
391 (0..=u16::MAX).map(f16::from_bits).filter(|x| x.is_finite()).collect();
392 let all = tensor1(&all).cast_to::<T>().unwrap().into_owned();
393 return all.try_as_plain().unwrap().as_slice::<T>().unwrap().to_vec();
394 }
395 (-30 * 4096..=30 * 4096).map(|i| (i as f32 / 4096.).as_()).collect()
396 }
397
398 pub fn test_element_wise_invariant<K: ElementWiseKer<T>, T: LADatum>(
405 expected: &str,
406 invariant: impl Fn(T, T) -> bool,
407 ) -> TestCaseResult
408 where
409 f32: AsPrimitive<T>,
410 {
411 crate::setup_test_logger();
412 let values = invariant_sweep::<T>();
413 let mut found = values.clone();
414 K::ew().run(&mut found).unwrap();
415 for (x, y) in values.iter().zip(found.iter()) {
416 proptest::prop_assert!(
417 invariant(*x, *y),
418 "{}({x:?}) returned {y:?}, expected {expected}",
419 K::name()
420 );
421 }
422 Ok(())
423 }
424
425 pub fn test_element_wise<K: ElementWiseKer<T, ()>, T: LADatum, F: Fn(T) -> T>(
426 values: &[T],
427 reference: F,
428 ) -> TestCaseResult {
429 test_element_wise_params::<K, T, F, ()>(values, reference, ())
430 }
431
432 pub fn test_element_wise_params<
433 K: ElementWiseKer<T, Params>,
434 T: LADatum,
435 F: Fn(T) -> T,
436 Params,
437 >(
438 values: &[T],
439 reference: F,
440 params: Params,
441 ) -> TestCaseResult
442 where
443 Params: Copy + Send + Sync + Debug + 'static + Default,
444 {
445 crate::setup_test_logger();
446 let op = ElementWiseImpl::<K, T, Params>::new();
447 let mut values = values.to_vec();
448 while values.len() < K::nr() {
449 values.push(T::zero());
450 }
451 let expected = values.iter().copied().map(reference).collect::<Vec<_>>();
452 let mut found = values;
453 op.run_with_params(&mut found, params).unwrap();
454 tensor1(&found)
455 .close_enough(&tensor1(&expected), true)
456 .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;
457 Ok(())
458 }
459}