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
474pub fn to_dense_f32_widen<'a>(op: &str, view: &'a TensorView<'_>) -> Result<Cow<'a, [f32]>> {
478 if view.dtype == DataType::Float32 && view.is_contiguous() {
479 view.validate()?;
480 let len = view.numel();
481 if len == 0 {
482 return Ok(Cow::Borrowed(&[]));
483 }
484 let data = unsafe { std::slice::from_raw_parts(view.data_ptr::<f32>(), len) };
488 return Ok(Cow::Borrowed(data));
489 }
490 dispatch_float!(view.dtype, op, T => {
491 let raw = to_dense_float::<T>(view)?;
492 Ok(Cow::Owned(
493 raw.into_iter().map(|v| v.to_f32()).collect(),
494 ))
495 })
496}
497
498pub fn write_dense_f32_narrow(op: &str, out: &mut TensorMut, data: &[f32]) -> Result<()> {
501 dispatch_float!(out.dtype, op, T => {
502 let narrowed: Vec<T> = data.iter().map(|&v| T::from_f32(v)).collect();
503 write_dense_float::<T>(out, &narrowed)
504 })
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[test]
512 fn f16_roundtrips_through_f32_without_bit_reinterpret() {
513 let h = half::f16::from_f32(1.0);
516 assert_eq!(h.to_bits(), 0x3C00);
517 assert_eq!(NumericElem::to_acc(h), 1.0f32);
518 assert_eq!(half::f16::from_acc(1.0f32).to_bits(), 0x3C00);
519 }
520
521 #[test]
522 fn int_div_by_zero_is_zero_not_panic() {
523 assert_eq!(5i32.c_div(0), 0);
524 assert_eq!(i32::MIN.c_div(-1), i32::MIN); }
526
527 #[test]
528 fn float_min_max_propagate_nan() {
529 assert!(f32::NAN.c_min(1.0).is_nan());
530 assert!(1.0f32.c_max(f32::NAN).is_nan());
531 assert_eq!(2.0f32.c_min(3.0), 2.0);
532 assert_eq!(2.0f32.c_max(3.0), 3.0);
533 }
534
535 #[test]
536 fn int_ops_wrap() {
537 assert_eq!(i8::MAX.c_add(1), i8::MIN);
538 assert_eq!(200u8.c_mul(2), 144); }
540
541 #[test]
542 fn unsupported_dtype_message_has_what_why_how() {
543 let e = unsupported_dtype("Add", DataType::Bool);
544 let s = format!("{e}");
545 assert!(s.contains("WHAT"));
546 assert!(s.contains("WHY"));
547 assert!(s.contains("HOW"));
548 }
549}