1use std::borrow::Cow;
36
37use onnx_runtime_ep_api::{EpError, Result, TensorMut, TensorView};
38use onnx_runtime_ir::DataType;
39
40use crate::strided::{elem_offset, next_index, numel};
41
42pub trait ComputeDomain: Copy + Default {
49 fn c_add(self, o: Self) -> Self;
50 fn c_sub(self, o: Self) -> Self;
51 fn c_mul(self, o: Self) -> Self;
52 fn c_div(self, o: Self) -> Self;
53 fn c_pow(self, o: Self) -> Self;
54 fn c_div_usize(self, divisor: usize) -> Self;
55 fn c_min(self, o: Self) -> Self;
57 fn c_max(self, o: Self) -> Self;
59}
60
61macro_rules! impl_float_compute {
62 ($($t:ty),*) => {$(
63 impl ComputeDomain for $t {
64 #[inline] fn c_add(self, o: Self) -> Self { self + o }
65 #[inline] fn c_sub(self, o: Self) -> Self { self - o }
66 #[inline] fn c_mul(self, o: Self) -> Self { self * o }
67 #[inline] fn c_div(self, o: Self) -> Self { self / o }
68 #[inline] fn c_pow(self, o: Self) -> Self { self.powf(o) }
69 #[inline] fn c_div_usize(self, divisor: usize) -> Self { self / divisor as $t }
70 #[inline] fn c_min(self, o: Self) -> Self {
73 if self.is_nan() || o.is_nan() { <$t>::NAN } else { self.min(o) }
74 }
75 #[inline] fn c_max(self, o: Self) -> Self {
76 if self.is_nan() || o.is_nan() { <$t>::NAN } else { self.max(o) }
77 }
78 }
79 )*};
80}
81impl_float_compute!(f32, f64);
82
83macro_rules! impl_int_compute {
84 ($($t:ty),*) => {$(
85 impl ComputeDomain for $t {
86 #[inline] fn c_add(self, o: Self) -> Self { self.wrapping_add(o) }
88 #[inline] fn c_sub(self, o: Self) -> Self { self.wrapping_sub(o) }
89 #[inline] fn c_mul(self, o: Self) -> Self { self.wrapping_mul(o) }
90 #[inline] fn c_div(self, o: Self) -> Self {
93 if o == 0 { 0 } else { self.wrapping_div(o) }
94 }
95 #[inline] fn c_pow(self, o: Self) -> Self { (self as f64).powf(o as f64) as $t }
98 #[inline] fn c_div_usize(self, divisor: usize) -> Self {
99 ((self as i128) / divisor as i128) as $t
100 }
101 #[inline] fn c_min(self, o: Self) -> Self { core::cmp::min(self, o) }
102 #[inline] fn c_max(self, o: Self) -> Self { core::cmp::max(self, o) }
103 }
104 )*};
105}
106impl_int_compute!(i8, i16, i32, i64, u8, u16, u32, u64);
107
108pub trait NumericElem: Copy {
116 const DTYPE: DataType;
118 type Acc: ComputeDomain;
120 fn to_acc(self) -> Self::Acc;
121 fn from_acc(a: Self::Acc) -> Self;
122 fn from_f32_scalar(f: f32) -> Self;
123}
124
125pub trait FloatElem: Copy {
128 const DTYPE: DataType;
129 fn to_f32(self) -> f32;
130 fn from_f32(f: f32) -> Self;
131}
132
133impl NumericElem for f32 {
135 const DTYPE: DataType = DataType::Float32;
136 type Acc = f32;
137 #[inline]
138 fn to_acc(self) -> f32 {
139 self
140 }
141 #[inline]
142 fn from_acc(a: f32) -> Self {
143 a
144 }
145 #[inline]
146 fn from_f32_scalar(f: f32) -> Self {
147 f
148 }
149}
150impl FloatElem for f32 {
151 const DTYPE: DataType = DataType::Float32;
152 #[inline]
153 fn to_f32(self) -> f32 {
154 self
155 }
156 #[inline]
157 fn from_f32(f: f32) -> Self {
158 f
159 }
160}
161
162impl NumericElem for f64 {
164 const DTYPE: DataType = DataType::Float64;
165 type Acc = f64;
166 #[inline]
167 fn to_acc(self) -> f64 {
168 self
169 }
170 #[inline]
171 fn from_acc(a: f64) -> Self {
172 a
173 }
174 #[inline]
175 fn from_f32_scalar(f: f32) -> Self {
176 f as f64
177 }
178}
179impl FloatElem for f64 {
180 const DTYPE: DataType = DataType::Float64;
181 #[inline]
182 fn to_f32(self) -> f32 {
183 self as f32
184 }
185 #[inline]
186 fn from_f32(f: f32) -> Self {
187 f as f64
188 }
189}
190
191impl NumericElem for half::f16 {
193 const DTYPE: DataType = DataType::Float16;
194 type Acc = f32;
195 #[inline]
196 fn to_acc(self) -> f32 {
197 self.to_f32()
198 }
199 #[inline]
200 fn from_acc(a: f32) -> Self {
201 half::f16::from_f32(a)
202 }
203 #[inline]
204 fn from_f32_scalar(f: f32) -> Self {
205 half::f16::from_f32(f)
206 }
207}
208impl FloatElem for half::f16 {
209 const DTYPE: DataType = DataType::Float16;
210 #[inline]
211 fn to_f32(self) -> f32 {
212 half::f16::to_f32(self)
213 }
214 #[inline]
215 fn from_f32(f: f32) -> Self {
216 half::f16::from_f32(f)
217 }
218}
219impl NumericElem for half::bf16 {
220 const DTYPE: DataType = DataType::BFloat16;
221 type Acc = f32;
222 #[inline]
223 fn to_acc(self) -> f32 {
224 self.to_f32()
225 }
226 #[inline]
227 fn from_acc(a: f32) -> Self {
228 half::bf16::from_f32(a)
229 }
230 #[inline]
231 fn from_f32_scalar(f: f32) -> Self {
232 half::bf16::from_f32(f)
233 }
234}
235impl FloatElem for half::bf16 {
236 const DTYPE: DataType = DataType::BFloat16;
237 #[inline]
238 fn to_f32(self) -> f32 {
239 half::bf16::to_f32(self)
240 }
241 #[inline]
242 fn from_f32(f: f32) -> Self {
243 half::bf16::from_f32(f)
244 }
245}
246
247macro_rules! impl_int_elem {
249 ($($t:ty => $dt:expr),* $(,)?) => {$(
250 impl NumericElem for $t {
251 const DTYPE: DataType = $dt;
252 type Acc = $t;
253 #[inline] fn to_acc(self) -> $t { self }
254 #[inline] fn from_acc(a: $t) -> Self { a }
255 #[inline] fn from_f32_scalar(f: f32) -> Self { f as $t }
256 }
257 )*};
258}
259impl_int_elem!(
260 i8 => DataType::Int8,
261 i16 => DataType::Int16,
262 i32 => DataType::Int32,
263 i64 => DataType::Int64,
264 u8 => DataType::Uint8,
265 u16 => DataType::Uint16,
266 u32 => DataType::Uint32,
267 u64 => DataType::Uint64,
268);
269
270pub fn to_dense<T: NumericElem>(view: &TensorView) -> Result<Vec<T>> {
277 read_strided::<T>(view, T::DTYPE)
278}
279
280pub fn to_dense_float<T: FloatElem>(view: &TensorView) -> Result<Vec<T>> {
282 read_strided::<T>(view, T::DTYPE)
283}
284
285fn read_strided<T: Copy>(view: &TensorView, want: DataType) -> Result<Vec<T>> {
286 view.validate()?;
287 debug_assert_eq!(
288 std::mem::size_of::<T>(),
289 want.byte_size(),
290 "read_strided element width must match dtype byte size"
291 );
292 if view.dtype != want {
293 return Err(EpError::InvalidTensorView {
294 reason: format!("expected {want:?} view, got {:?}", view.dtype),
295 });
296 }
297 let n = numel(view.shape);
298 let mut out = Vec::with_capacity(n);
299 if n == 0 {
300 return Ok(out);
301 }
302 let origin = view.data_ptr::<T>();
303 let mut idx = vec![0usize; view.shape.len()];
304 loop {
305 let off = elem_offset(view.strides, &idx);
306 out.push(unsafe { *origin.offset(off) });
312 if !next_index(view.shape, &mut idx) {
313 break;
314 }
315 }
316 Ok(out)
317}
318
319pub fn write_dense<T: NumericElem>(out: &mut TensorMut, data: &[T]) -> Result<()> {
323 write_strided::<T>(out, data, T::DTYPE)
324}
325
326pub fn write_dense_float<T: FloatElem>(out: &mut TensorMut, data: &[T]) -> Result<()> {
328 write_strided::<T>(out, data, T::DTYPE)
329}
330
331fn write_strided<T: Copy>(out: &mut TensorMut, data: &[T], want: DataType) -> Result<()> {
332 out.validate()?;
333 if out.dtype != want {
334 return Err(EpError::InvalidTensorView {
335 reason: format!("expected {want:?} output, got {:?}", out.dtype),
336 });
337 }
338 let n = numel(out.shape);
339 if data.len() != n {
340 return Err(EpError::KernelFailed(format!(
341 "output element count {n} does not match produced {}",
342 data.len()
343 )));
344 }
345 if n == 0 {
346 return Ok(());
347 }
348 let origin = out.data_ptr_mut::<T>();
349 let strides = out.strides;
350 let shape = out.shape;
351 let mut idx = vec![0usize; shape.len()];
352 let mut i = 0usize;
353 loop {
354 let off = elem_offset(strides, &idx);
355 unsafe {
360 *origin.offset(off) = data[i];
361 }
362 i += 1;
363 if !next_index(shape, &mut idx) {
364 break;
365 }
366 }
367 Ok(())
368}
369
370pub fn unsupported_dtype(op: &str, dtype: DataType) -> EpError {
373 EpError::KernelFailed(format!(
374 "{op}: unsupported element type {dtype:?} (WHAT: this CPU kernel was asked \
375 to run {op} on a {dtype:?} tensor). WHY: ONNX does not define {op} for \
376 {dtype:?}, or arithmetic on it is not implemented by this execution \
377 provider. HOW: insert a `Cast` to a supported numeric dtype (e.g. \
378 Float32) before {op}, or run the op on an EP that implements {dtype:?}."
379 ))
380}
381
382#[macro_export]
391macro_rules! dispatch_arith {
392 ($dtype:expr, $op:expr, $T:ident => $body:expr) => {{
393 match $dtype {
394 ::onnx_runtime_ir::DataType::Float32 => {
395 type $T = f32;
396 $body
397 }
398 ::onnx_runtime_ir::DataType::Float16 => {
399 type $T = half::f16;
400 $body
401 }
402 ::onnx_runtime_ir::DataType::BFloat16 => {
403 type $T = half::bf16;
404 $body
405 }
406 ::onnx_runtime_ir::DataType::Float64 => {
407 type $T = f64;
408 $body
409 }
410 ::onnx_runtime_ir::DataType::Int8 => {
411 type $T = i8;
412 $body
413 }
414 ::onnx_runtime_ir::DataType::Int16 => {
415 type $T = i16;
416 $body
417 }
418 ::onnx_runtime_ir::DataType::Int32 => {
419 type $T = i32;
420 $body
421 }
422 ::onnx_runtime_ir::DataType::Int64 => {
423 type $T = i64;
424 $body
425 }
426 ::onnx_runtime_ir::DataType::Uint8 => {
427 type $T = u8;
428 $body
429 }
430 ::onnx_runtime_ir::DataType::Uint16 => {
431 type $T = u16;
432 $body
433 }
434 ::onnx_runtime_ir::DataType::Uint32 => {
435 type $T = u32;
436 $body
437 }
438 ::onnx_runtime_ir::DataType::Uint64 => {
439 type $T = u64;
440 $body
441 }
442 other => Err($crate::dtype::unsupported_dtype($op, other)),
443 }
444 }};
445}
446
447#[macro_export]
450macro_rules! dispatch_float {
451 ($dtype:expr, $op:expr, $T:ident => $body:expr) => {{
452 match $dtype {
453 ::onnx_runtime_ir::DataType::Float32 => {
454 type $T = f32;
455 $body
456 }
457 ::onnx_runtime_ir::DataType::Float16 => {
458 type $T = half::f16;
459 $body
460 }
461 ::onnx_runtime_ir::DataType::BFloat16 => {
462 type $T = half::bf16;
463 $body
464 }
465 ::onnx_runtime_ir::DataType::Float64 => {
466 type $T = f64;
467 $body
468 }
469 other => Err($crate::dtype::unsupported_dtype($op, other)),
470 }
471 }};
472}
473
474#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
492mod f16c {
493 #[cfg(target_arch = "x86")]
494 use std::arch::x86::*;
495 #[cfg(target_arch = "x86_64")]
496 use std::arch::x86_64::*;
497
498 #[inline]
499 pub fn available() -> bool {
500 std::arch::is_x86_feature_detected!("f16c") && std::arch::is_x86_feature_detected!("avx2")
501 }
502
503 #[target_feature(enable = "f16c,avx2")]
509 pub unsafe fn widen(src: &[u16], dst: &mut [f32]) {
510 debug_assert_eq!(src.len(), dst.len());
511 let n = src.len();
512 let sp = src.as_ptr();
513 let dp = dst.as_mut_ptr();
514 unsafe {
515 let mut i = 0;
516 while i + 8 <= n {
517 let h = _mm_loadu_si128(sp.add(i) as *const __m128i);
518 _mm256_storeu_ps(dp.add(i), _mm256_cvtph_ps(h));
519 i += 8;
520 }
521 while i < n {
523 *dp.add(i) = half::f16::from_bits(*sp.add(i)).to_f32();
524 i += 1;
525 }
526 }
527 }
528
529 #[target_feature(enable = "f16c,avx2")]
536 pub unsafe fn narrow(src: &[f32], dst: &mut [u16]) {
537 debug_assert_eq!(src.len(), dst.len());
538 let n = src.len();
539 let sp = src.as_ptr();
540 let dp = dst.as_mut_ptr();
541 unsafe {
542 let mut i = 0;
543 while i + 8 <= n {
544 let v = _mm256_loadu_ps(sp.add(i));
545 let h = _mm256_cvtps_ph::<_MM_FROUND_TO_NEAREST_INT>(v);
546 _mm_storeu_si128(dp.add(i) as *mut __m128i, h);
547 i += 8;
548 }
549 while i < n {
550 *dp.add(i) = half::f16::from_f32(*sp.add(i)).to_bits();
551 i += 1;
552 }
553 }
554 }
555}
556
557pub fn widen_f16_slice_into(src: &[u16], dst: &mut [f32]) {
565 debug_assert_eq!(src.len(), dst.len());
566 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
567 if f16c::available() {
568 unsafe { f16c::widen(src, dst) };
570 return;
571 }
572 for (d, &s) in dst.iter_mut().zip(src) {
573 *d = half::f16::from_bits(s).to_f32();
574 }
575}
576
577pub fn to_dense_f32_widen<'a>(op: &str, view: &'a TensorView<'_>) -> Result<Cow<'a, [f32]>> {
581 if view.dtype == DataType::Float32 && view.is_contiguous() {
582 view.validate()?;
583 let len = view.numel();
584 if len == 0 {
585 return Ok(Cow::Borrowed(&[]));
586 }
587 let data = unsafe { std::slice::from_raw_parts(view.data_ptr::<f32>(), len) };
591 return Ok(Cow::Borrowed(data));
592 }
593 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
595 if view.dtype == DataType::Float16 && view.is_contiguous() && f16c::available() {
596 view.validate()?;
597 let len = view.numel();
598 if len == 0 {
599 return Ok(Cow::Borrowed(&[]));
600 }
601 let src = unsafe { std::slice::from_raw_parts(view.data_ptr::<u16>(), len) };
605 let mut dst = vec![0.0f32; len];
606 unsafe { f16c::widen(src, &mut dst) };
608 return Ok(Cow::Owned(dst));
609 }
610 dispatch_float!(view.dtype, op, T => {
611 let raw = to_dense_float::<T>(view)?;
612 Ok(Cow::Owned(
613 raw.into_iter().map(|v| v.to_f32()).collect(),
614 ))
615 })
616}
617
618pub fn write_dense_f32_narrow(op: &str, out: &mut TensorMut, data: &[f32]) -> Result<()> {
621 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
623 if out.dtype == DataType::Float16 && out.is_contiguous() && f16c::available() {
624 out.validate()?;
625 let n = out.numel();
626 if data.len() != n {
627 return Err(EpError::KernelFailed(format!(
628 "output element count {n} does not match produced {}",
629 data.len()
630 )));
631 }
632 if n == 0 {
633 return Ok(());
634 }
635 let dst = unsafe { std::slice::from_raw_parts_mut(out.data_ptr_mut::<u16>(), n) };
639 unsafe { f16c::narrow(data, dst) };
641 return Ok(());
642 }
643 dispatch_float!(out.dtype, op, T => {
644 let narrowed: Vec<T> = data.iter().map(|&v| T::from_f32(v)).collect();
645 write_dense_float::<T>(out, &narrowed)
646 })
647}
648
649#[inline]
658pub fn slice_byte_range<T>(slice: &[T]) -> core::ops::Range<usize> {
659 let start = slice.as_ptr() as usize;
660 start..start.saturating_add(std::mem::size_of_val(slice))
661}
662
663#[inline]
665pub fn byte_ranges_overlap(a: &core::ops::Range<usize>, b: &core::ops::Range<usize>) -> bool {
666 a.start < b.end && b.start < a.end
667}
668
669pub fn output_direct_write_eligible(
693 output: &mut TensorMut,
694 len: usize,
695 read_ranges: &[core::ops::Range<usize>],
696) -> bool {
697 if output.dtype != DataType::Float32
698 || !output.is_contiguous()
699 || !output.device.is_host_accessible()
700 || output.numel() != len
701 {
702 return false;
703 }
704 let start = output.data_ptr_mut::<f32>() as usize;
705 let out_range = start..start.saturating_add(len * std::mem::size_of::<f32>());
706 read_ranges
707 .iter()
708 .all(|r| !byte_ranges_overlap(&out_range, r))
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714
715 #[test]
716 fn f16_roundtrips_through_f32_without_bit_reinterpret() {
717 let h = half::f16::from_f32(1.0);
720 assert_eq!(h.to_bits(), 0x3C00);
721 assert_eq!(NumericElem::to_acc(h), 1.0f32);
722 assert_eq!(half::f16::from_acc(1.0f32).to_bits(), 0x3C00);
723 }
724
725 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
731 #[test]
732 fn f16c_widen_narrow_bit_identical_to_scalar() {
733 if !f16c::available() {
734 eprintln!("skipping: host lacks f16c/avx2");
735 return;
736 }
737
738 let src: Vec<u16> = (0u32..=u16::MAX as u32).map(|b| b as u16).collect();
741 for len in [0usize, 1, 7, 8, 15, 16, 65_533, src.len()] {
742 let s = &src[..len];
743 let mut simd = vec![0.0f32; len];
744 unsafe { f16c::widen(s, &mut simd) };
746 for (i, &bits) in s.iter().enumerate() {
747 let want = half::f16::from_bits(bits).to_f32();
748 assert_eq!(
750 simd[i].to_bits(),
751 want.to_bits(),
752 "widen mismatch at f16 bits {bits:#06x}"
753 );
754 }
755 }
756
757 let vals: Vec<f32> = vec![
760 0.0,
761 -0.0,
762 1.0,
763 -1.0,
764 0.5,
765 2049.0, 65_504.0, 65_520.0, -65_520.0, 6.1e-5, 1e-8, 3.140625,
772 f32::INFINITY,
773 f32::NEG_INFINITY,
774 f32::NAN,
775 std::f32::consts::PI,
776 123456.0,
777 ];
778 for len in [0usize, 1, 7, 8, 15, vals.len()] {
779 let s = &vals[..len.min(vals.len())];
780 let mut simd = vec![0u16; s.len()];
781 unsafe { f16c::narrow(s, &mut simd) };
783 for (i, &v) in s.iter().enumerate() {
784 let want = half::f16::from_f32(v).to_bits();
785 let got = simd[i];
786 if half::f16::from_bits(want).is_nan() {
789 assert!(
790 half::f16::from_bits(got).is_nan(),
791 "narrow NaN mismatch for {v}"
792 );
793 } else {
794 assert_eq!(got, want, "narrow mismatch for f32 {v}");
795 }
796 }
797 }
798 }
799
800 #[test]
801 fn int_div_by_zero_is_zero_not_panic() {
802 assert_eq!(5i32.c_div(0), 0);
803 assert_eq!(i32::MIN.c_div(-1), i32::MIN); }
805
806 #[test]
807 fn float_min_max_propagate_nan() {
808 assert!(f32::NAN.c_min(1.0).is_nan());
809 assert!(1.0f32.c_max(f32::NAN).is_nan());
810 assert_eq!(2.0f32.c_min(3.0), 2.0);
811 assert_eq!(2.0f32.c_max(3.0), 3.0);
812 }
813
814 #[test]
815 fn int_ops_wrap() {
816 assert_eq!(i8::MAX.c_add(1), i8::MIN);
817 assert_eq!(200u8.c_mul(2), 144); }
819
820 #[test]
821 fn unsupported_dtype_message_has_what_why_how() {
822 let e = unsupported_dtype("Add", DataType::Bool);
823 let s = format!("{e}");
824 assert!(s.contains("WHAT"));
825 assert!(s.contains("WHY"));
826 assert!(s.contains("HOW"));
827 }
828
829 #[test]
830 fn direct_write_guard_detects_overlap_and_shape() {
831 use onnx_runtime_ep_api::DevicePtrMut;
832 use onnx_runtime_ir::{DeviceId, compute_contiguous_strides};
833
834 let mut buf = vec![0.0f32; 8];
835 let base = buf.as_ptr() as usize;
836 let shape = [2usize, 4];
837 let strides = compute_contiguous_strides(&shape);
838 let mut out = TensorMut::new(
839 DevicePtrMut(buf.as_mut_ptr() as *mut std::ffi::c_void),
840 DataType::Float32,
841 &shape,
842 &strides,
843 DeviceId::cpu(),
844 );
845
846 let disjoint = (base + 8 * 4)..(base + 8 * 4 + 16);
848 assert!(output_direct_write_eligible(&mut out, 8, &[disjoint]));
849
850 let overlap = (base + 4)..(base + 4 + 16);
852 assert!(!output_direct_write_eligible(&mut out, 8, &[overlap]));
853
854 assert!(!output_direct_write_eligible(&mut out, 7, &[]));
856
857 let s = [0.0f32; 4];
859 let r = slice_byte_range(&s);
860 assert_eq!(r.end - r.start, 16);
861 assert!(byte_ranges_overlap(&(0..10), &(5..15)));
862 assert!(!byte_ranges_overlap(&(0..10), &(10..20)));
863 }
864}