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! ew_impl_wrap {
11 ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty, $run: item) => {
12 paste! {
13 #[derive(Copy, Clone, Debug)]
14 #[allow(non_camel_case_types)]
15 pub struct $func;
16
17 impl crate::frame::element_wise::ElementWiseKer<$ti, $params> for $func {
18 #[inline(always)]
19 fn name() -> &'static str {
20 stringify!($func)
21 }
22 #[inline(always)]
23 fn nr() -> usize {
24 $nr
25 }
26 #[inline(always)]
27 fn alignment_items() -> usize {
28 $alignment_items
29 }
30 $run
31 }
32 }
33 };
34}
35
36macro_rules! ew_impl_f16_via_f32 {
54 ($func:ident, $nr:expr, $alignment_items:expr, $chunk:expr, $scratch_align:literal,
55 $cvt_in:path, $cvt_out:path, $f32_kernel:ty) => {
56 ew_impl_f16_via_f32!(@build $func, $nr, $alignment_items, $chunk, $scratch_align,
57 $cvt_in, $cvt_out, $f32_kernel, (), _params, ());
58 };
59 ($func:ident, $nr:expr, $alignment_items:expr, $chunk:expr, $scratch_align:literal,
60 $cvt_in:path, $cvt_out:path, $f32_kernel:ty, $params:ty, $pname:ident => $pconv:expr) => {
61 ew_impl_f16_via_f32!(@build $func, $nr, $alignment_items, $chunk, $scratch_align,
62 $cvt_in, $cvt_out, $f32_kernel, $params, $pname, $pconv);
63 };
64 (@build $func:ident, $nr:expr, $alignment_items:expr, $chunk:expr, $scratch_align:literal,
65 $cvt_in:path, $cvt_out:path, $f32_kernel:ty, $params:ty, $pname:ident, $pconv:expr) => {
66 ew_impl_wrap!(
67 f16, $func, $nr, $alignment_items, $params,
68 #[inline(never)]
69 fn run(buf: &mut [f16], $pname: $params) {
70 const _: () = assert!(
71 $chunk % $nr == 0,
72 "CHUNK must be a multiple of nr; the f32 kernel steps nr lanes with no tail"
73 );
74 #[repr(C, align($scratch_align))]
75 struct AlignedScratch([f32; $chunk]);
76 debug_assert!(buf.len() % Self::nr() == 0);
77 debug_assert!(buf.as_ptr() as usize % Self::alignment_bytes() == 0);
78 if buf.is_empty() {
79 return;
80 }
81 let f32_params = $pconv;
82 let mut scratch = std::mem::MaybeUninit::<AlignedScratch>::uninit();
83 let s = unsafe { &mut (*scratch.as_mut_ptr()).0 };
87 let mut i = 0;
88 while i < buf.len() {
89 let n = ($chunk).min(buf.len() - i);
90 unsafe { $cvt_in(&buf[i..i + n], &mut s[..n]) };
91 <$f32_kernel>::run(&mut s[..n], f32_params);
92 unsafe { $cvt_out(&s[..n], &mut buf[i..i + n]) };
93 i += n;
94 }
95 }
96 );
97 };
98}
99
100macro_rules! ew_impl {
101 ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr) => {
102 paste! {
103 mod [<sys_ $func>] {
104 #[allow(unused_imports)]
105 use tract_data::prelude::f16;
106 extern_kernel!(fn $func(ptr: *mut $ti, count: usize) -> ());
107 }
108 ew_impl_wrap!($ti, $func, $nr, $alignment_items, (),
109 #[inline(never)]
110 fn run(buf: &mut [$ti], _params: ()) {
111 unsafe { [<sys_ $func>]::$func(buf.as_mut_ptr(), buf.len()) }
112 }
113 );
114 }
115 };
116 ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty) => {
117 paste! {
118 mod [<sys_ $func>] {
119 #[allow(unused_imports)]
120 use tract_data::prelude::f16;
121 extern_kernel!(fn $func(ptr: *mut $ti, count: usize, params: $params) -> ());
122 }
123 ew_impl_wrap!($ti, $func, $nr, $alignment_items, $params,
124 #[inline(never)]
125 fn run(buf: &mut [$ti], params: $params) {
126 unsafe { [<sys_ $func>]::$func(buf.as_mut_ptr(), buf.len(), params) }
127 }
128 );
129 }
130 };
131}
132
133pub trait ElementWise<T, Params = ()>: Send + Sync + Debug + dyn_clone::DynClone
134where
135 Params: Copy + Send + Sync + Debug + 'static + Default,
136 T: Copy + Debug + PartialEq + Send + Sync,
137{
138 fn name(&self) -> &'static str;
139 fn run(&self, vec: &mut [T]) -> TractResult<()> {
140 self.run_with_params(vec, Params::default())
141 }
142 fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<()>;
143}
144
145dyn_clone::clone_trait_object!(<T, Params> ElementWise<T, Params> where T: Copy, Params: Copy);
146
147#[derive(Debug, Clone, new)]
148pub struct ElementWiseImpl<K, T, Params = ()>
149where
150 T: LADatum,
151 Params: Copy + Send + Sync + Debug + 'static + Default,
152 K: ElementWiseKer<T, Params> + Clone,
153{
154 phantom: PhantomData<(K, T, Params)>,
155}
156
157impl<K, T, Params> ElementWise<T, Params> for ElementWiseImpl<K, T, Params>
158where
159 T: LADatum,
160 Params: Copy + Send + Sync + Debug + 'static + Default,
161 K: ElementWiseKer<T, Params> + Clone,
162{
163 fn name(&self) -> &'static str {
164 K::name()
165 }
166 fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<()> {
167 map_slice_with_alignment(vec, |data| K::run(data, params), K::nr(), K::alignment_bytes())
168 }
169}
170
171pub trait ElementWiseKer<T, Params = ()>:
172 Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static
173where
174 Params: Copy + Send + Sync + Debug + 'static + Default,
175 T: LADatum,
176{
177 fn name() -> &'static str;
178 fn alignment_bytes() -> usize {
179 Self::alignment_items() * T::datum_type().size_of()
180 }
181 fn alignment_items() -> usize;
182 fn nr() -> usize;
183 fn run(vec: &mut [T], params: Params);
184 fn ew() -> Box<dyn ElementWise<T, Params>> {
185 Box::new(ElementWiseImpl::<Self, T, Params>::new())
186 }
187}
188
189#[cfg(test)]
190pub mod test {
191 use crate::{LADatum, frame::element_wise::*};
192 use num_traits::AsPrimitive;
193 use proptest::test_runner::{TestCaseError, TestCaseResult};
194 use tract_data::internal::*;
195
196 fn invariant_sweep<T: LADatum>() -> Vec<T>
207 where
208 f32: AsPrimitive<T>,
209 {
210 if T::datum_type() == f16::datum_type() {
211 let all: Vec<f16> =
212 (0..=u16::MAX).map(f16::from_bits).filter(|x| x.is_finite()).collect();
213 let all = tensor1(&all).cast_to::<T>().unwrap().into_owned();
214 return all.try_as_plain().unwrap().as_slice::<T>().unwrap().to_vec();
215 }
216 (-30 * 4096..=30 * 4096).map(|i| (i as f32 / 4096.).as_()).collect()
217 }
218
219 pub fn test_element_wise_invariant<K: ElementWiseKer<T>, T: LADatum>(
226 expected: &str,
227 invariant: impl Fn(T, T) -> bool,
228 ) -> TestCaseResult
229 where
230 f32: AsPrimitive<T>,
231 {
232 crate::setup_test_logger();
233 let values = invariant_sweep::<T>();
234 let mut found = values.clone();
235 K::ew().run(&mut found).unwrap();
236 for (x, y) in values.iter().zip(found.iter()) {
237 proptest::prop_assert!(
238 invariant(*x, *y),
239 "{}({x:?}) returned {y:?}, expected {expected}",
240 K::name()
241 );
242 }
243 Ok(())
244 }
245
246 pub fn test_element_wise<K: ElementWiseKer<T, ()>, T: LADatum, F: Fn(T) -> T>(
247 values: &[T],
248 reference: F,
249 ) -> TestCaseResult {
250 test_element_wise_params::<K, T, F, ()>(values, reference, ())
251 }
252
253 pub fn test_element_wise_params<
254 K: ElementWiseKer<T, Params>,
255 T: LADatum,
256 F: Fn(T) -> T,
257 Params,
258 >(
259 values: &[T],
260 reference: F,
261 params: Params,
262 ) -> TestCaseResult
263 where
264 Params: Copy + Send + Sync + Debug + 'static + Default,
265 {
266 crate::setup_test_logger();
267 let op = ElementWiseImpl::<K, T, Params>::new();
268 let mut values = values.to_vec();
269 while values.len() < K::nr() {
270 values.push(T::zero());
271 }
272 let expected = values.iter().copied().map(reference).collect::<Vec<_>>();
273 let mut found = values;
274 op.run_with_params(&mut found, params).unwrap();
275 tensor1(&found)
276 .close_enough(&tensor1(&expected), true)
277 .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;
278 Ok(())
279 }
280}