1use onnx_runtime_ep_api::{EpError, Result, TensorMut, TensorView};
36use onnx_runtime_ir::DataType;
37
38use crate::strided::{elem_offset, next_index, numel};
39
40pub trait ComputeDomain: Copy + Default {
47 fn c_add(self, o: Self) -> Self;
48 fn c_sub(self, o: Self) -> Self;
49 fn c_mul(self, o: Self) -> Self;
50 fn c_div(self, o: Self) -> Self;
51 fn c_pow(self, o: Self) -> Self;
52 fn c_div_usize(self, divisor: usize) -> Self;
53 fn c_min(self, o: Self) -> Self;
55 fn c_max(self, o: Self) -> Self;
57}
58
59macro_rules! impl_float_compute {
60 ($($t:ty),*) => {$(
61 impl ComputeDomain for $t {
62 #[inline] fn c_add(self, o: Self) -> Self { self + o }
63 #[inline] fn c_sub(self, o: Self) -> Self { self - o }
64 #[inline] fn c_mul(self, o: Self) -> Self { self * o }
65 #[inline] fn c_div(self, o: Self) -> Self { self / o }
66 #[inline] fn c_pow(self, o: Self) -> Self { self.powf(o) }
67 #[inline] fn c_div_usize(self, divisor: usize) -> Self { self / divisor as $t }
68 #[inline] fn c_min(self, o: Self) -> Self {
71 if self.is_nan() || o.is_nan() { <$t>::NAN } else { self.min(o) }
72 }
73 #[inline] fn c_max(self, o: Self) -> Self {
74 if self.is_nan() || o.is_nan() { <$t>::NAN } else { self.max(o) }
75 }
76 }
77 )*};
78}
79impl_float_compute!(f32, f64);
80
81macro_rules! impl_int_compute {
82 ($($t:ty),*) => {$(
83 impl ComputeDomain for $t {
84 #[inline] fn c_add(self, o: Self) -> Self { self.wrapping_add(o) }
86 #[inline] fn c_sub(self, o: Self) -> Self { self.wrapping_sub(o) }
87 #[inline] fn c_mul(self, o: Self) -> Self { self.wrapping_mul(o) }
88 #[inline] fn c_div(self, o: Self) -> Self {
91 if o == 0 { 0 } else { self.wrapping_div(o) }
92 }
93 #[inline] fn c_pow(self, o: Self) -> Self { (self as f64).powf(o as f64) as $t }
96 #[inline] fn c_div_usize(self, divisor: usize) -> Self {
97 ((self as i128) / divisor as i128) as $t
98 }
99 #[inline] fn c_min(self, o: Self) -> Self { core::cmp::min(self, o) }
100 #[inline] fn c_max(self, o: Self) -> Self { core::cmp::max(self, o) }
101 }
102 )*};
103}
104impl_int_compute!(i8, i16, i32, i64, u8, u16, u32, u64);
105
106pub trait NumericElem: Copy {
114 const DTYPE: DataType;
116 type Acc: ComputeDomain;
118 fn to_acc(self) -> Self::Acc;
119 fn from_acc(a: Self::Acc) -> Self;
120}
121
122pub trait FloatElem: Copy {
125 const DTYPE: DataType;
126 fn to_f32(self) -> f32;
127 fn from_f32(f: f32) -> Self;
128}
129
130impl NumericElem for f32 {
132 const DTYPE: DataType = DataType::Float32;
133 type Acc = f32;
134 #[inline]
135 fn to_acc(self) -> f32 {
136 self
137 }
138 #[inline]
139 fn from_acc(a: f32) -> Self {
140 a
141 }
142}
143impl FloatElem for f32 {
144 const DTYPE: DataType = DataType::Float32;
145 #[inline]
146 fn to_f32(self) -> f32 {
147 self
148 }
149 #[inline]
150 fn from_f32(f: f32) -> Self {
151 f
152 }
153}
154
155impl NumericElem for f64 {
157 const DTYPE: DataType = DataType::Float64;
158 type Acc = f64;
159 #[inline]
160 fn to_acc(self) -> f64 {
161 self
162 }
163 #[inline]
164 fn from_acc(a: f64) -> Self {
165 a
166 }
167}
168impl FloatElem for f64 {
169 const DTYPE: DataType = DataType::Float64;
170 #[inline]
171 fn to_f32(self) -> f32 {
172 self as f32
173 }
174 #[inline]
175 fn from_f32(f: f32) -> Self {
176 f as f64
177 }
178}
179
180impl NumericElem for half::f16 {
182 const DTYPE: DataType = DataType::Float16;
183 type Acc = f32;
184 #[inline]
185 fn to_acc(self) -> f32 {
186 self.to_f32()
187 }
188 #[inline]
189 fn from_acc(a: f32) -> Self {
190 half::f16::from_f32(a)
191 }
192}
193impl FloatElem for half::f16 {
194 const DTYPE: DataType = DataType::Float16;
195 #[inline]
196 fn to_f32(self) -> f32 {
197 half::f16::to_f32(self)
198 }
199 #[inline]
200 fn from_f32(f: f32) -> Self {
201 half::f16::from_f32(f)
202 }
203}
204impl NumericElem for half::bf16 {
205 const DTYPE: DataType = DataType::BFloat16;
206 type Acc = f32;
207 #[inline]
208 fn to_acc(self) -> f32 {
209 self.to_f32()
210 }
211 #[inline]
212 fn from_acc(a: f32) -> Self {
213 half::bf16::from_f32(a)
214 }
215}
216impl FloatElem for half::bf16 {
217 const DTYPE: DataType = DataType::BFloat16;
218 #[inline]
219 fn to_f32(self) -> f32 {
220 half::bf16::to_f32(self)
221 }
222 #[inline]
223 fn from_f32(f: f32) -> Self {
224 half::bf16::from_f32(f)
225 }
226}
227
228macro_rules! impl_int_elem {
230 ($($t:ty => $dt:expr),* $(,)?) => {$(
231 impl NumericElem for $t {
232 const DTYPE: DataType = $dt;
233 type Acc = $t;
234 #[inline] fn to_acc(self) -> $t { self }
235 #[inline] fn from_acc(a: $t) -> Self { a }
236 }
237 )*};
238}
239impl_int_elem!(
240 i8 => DataType::Int8,
241 i16 => DataType::Int16,
242 i32 => DataType::Int32,
243 i64 => DataType::Int64,
244 u8 => DataType::Uint8,
245 u16 => DataType::Uint16,
246 u32 => DataType::Uint32,
247 u64 => DataType::Uint64,
248);
249
250pub fn to_dense<T: NumericElem>(view: &TensorView) -> Result<Vec<T>> {
257 read_strided::<T>(view, T::DTYPE)
258}
259
260pub fn to_dense_float<T: FloatElem>(view: &TensorView) -> Result<Vec<T>> {
262 read_strided::<T>(view, T::DTYPE)
263}
264
265fn read_strided<T: Copy>(view: &TensorView, want: DataType) -> Result<Vec<T>> {
266 view.validate()?;
267 debug_assert_eq!(
268 std::mem::size_of::<T>(),
269 want.byte_size(),
270 "read_strided element width must match dtype byte size"
271 );
272 if view.dtype != want {
273 return Err(EpError::InvalidTensorView {
274 reason: format!("expected {want:?} view, got {:?}", view.dtype),
275 });
276 }
277 let n = numel(view.shape);
278 let mut out = Vec::with_capacity(n);
279 if n == 0 {
280 return Ok(out);
281 }
282 let origin = view.data_ptr::<T>();
283 let mut idx = vec![0usize; view.shape.len()];
284 loop {
285 let off = elem_offset(view.strides, &idx);
286 out.push(unsafe { *origin.offset(off) });
292 if !next_index(view.shape, &mut idx) {
293 break;
294 }
295 }
296 Ok(out)
297}
298
299pub fn write_dense<T: NumericElem>(out: &mut TensorMut, data: &[T]) -> Result<()> {
303 write_strided::<T>(out, data, T::DTYPE)
304}
305
306pub fn write_dense_float<T: FloatElem>(out: &mut TensorMut, data: &[T]) -> Result<()> {
308 write_strided::<T>(out, data, T::DTYPE)
309}
310
311fn write_strided<T: Copy>(out: &mut TensorMut, data: &[T], want: DataType) -> Result<()> {
312 out.validate()?;
313 if out.dtype != want {
314 return Err(EpError::InvalidTensorView {
315 reason: format!("expected {want:?} output, got {:?}", out.dtype),
316 });
317 }
318 let n = numel(out.shape);
319 if data.len() != n {
320 return Err(EpError::KernelFailed(format!(
321 "output element count {n} does not match produced {}",
322 data.len()
323 )));
324 }
325 if n == 0 {
326 return Ok(());
327 }
328 let origin = out.data_ptr_mut::<T>();
329 let strides = out.strides;
330 let shape = out.shape;
331 let mut idx = vec![0usize; shape.len()];
332 let mut i = 0usize;
333 loop {
334 let off = elem_offset(strides, &idx);
335 unsafe {
340 *origin.offset(off) = data[i];
341 }
342 i += 1;
343 if !next_index(shape, &mut idx) {
344 break;
345 }
346 }
347 Ok(())
348}
349
350pub fn unsupported_dtype(op: &str, dtype: DataType) -> EpError {
353 EpError::KernelFailed(format!(
354 "{op}: unsupported element type {dtype:?} (WHAT: this CPU kernel was asked \
355 to run {op} on a {dtype:?} tensor). WHY: ONNX does not define {op} for \
356 {dtype:?}, or arithmetic on it is not implemented by this execution \
357 provider. HOW: insert a `Cast` to a supported numeric dtype (e.g. \
358 Float32) before {op}, or run the op on an EP that implements {dtype:?}."
359 ))
360}
361
362#[macro_export]
371macro_rules! dispatch_arith {
372 ($dtype:expr, $op:expr, $T:ident => $body:expr) => {{
373 match $dtype {
374 ::onnx_runtime_ir::DataType::Float32 => {
375 type $T = f32;
376 $body
377 }
378 ::onnx_runtime_ir::DataType::Float16 => {
379 type $T = half::f16;
380 $body
381 }
382 ::onnx_runtime_ir::DataType::BFloat16 => {
383 type $T = half::bf16;
384 $body
385 }
386 ::onnx_runtime_ir::DataType::Float64 => {
387 type $T = f64;
388 $body
389 }
390 ::onnx_runtime_ir::DataType::Int8 => {
391 type $T = i8;
392 $body
393 }
394 ::onnx_runtime_ir::DataType::Int16 => {
395 type $T = i16;
396 $body
397 }
398 ::onnx_runtime_ir::DataType::Int32 => {
399 type $T = i32;
400 $body
401 }
402 ::onnx_runtime_ir::DataType::Int64 => {
403 type $T = i64;
404 $body
405 }
406 ::onnx_runtime_ir::DataType::Uint8 => {
407 type $T = u8;
408 $body
409 }
410 ::onnx_runtime_ir::DataType::Uint16 => {
411 type $T = u16;
412 $body
413 }
414 ::onnx_runtime_ir::DataType::Uint32 => {
415 type $T = u32;
416 $body
417 }
418 ::onnx_runtime_ir::DataType::Uint64 => {
419 type $T = u64;
420 $body
421 }
422 other => Err($crate::dtype::unsupported_dtype($op, other)),
423 }
424 }};
425}
426
427#[macro_export]
430macro_rules! dispatch_float {
431 ($dtype:expr, $op:expr, $T:ident => $body:expr) => {{
432 match $dtype {
433 ::onnx_runtime_ir::DataType::Float32 => {
434 type $T = f32;
435 $body
436 }
437 ::onnx_runtime_ir::DataType::Float16 => {
438 type $T = half::f16;
439 $body
440 }
441 ::onnx_runtime_ir::DataType::BFloat16 => {
442 type $T = half::bf16;
443 $body
444 }
445 ::onnx_runtime_ir::DataType::Float64 => {
446 type $T = f64;
447 $body
448 }
449 other => Err($crate::dtype::unsupported_dtype($op, other)),
450 }
451 }};
452}
453
454pub fn to_dense_f32_widen(op: &str, view: &TensorView) -> Result<Vec<f32>> {
458 dispatch_float!(view.dtype, op, T => {
459 let raw = to_dense_float::<T>(view)?;
460 Ok(raw.into_iter().map(|v| v.to_f32()).collect())
461 })
462}
463
464pub fn write_dense_f32_narrow(op: &str, out: &mut TensorMut, data: &[f32]) -> Result<()> {
467 dispatch_float!(out.dtype, op, T => {
468 let narrowed: Vec<T> = data.iter().map(|&v| T::from_f32(v)).collect();
469 write_dense_float::<T>(out, &narrowed)
470 })
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn f16_roundtrips_through_f32_without_bit_reinterpret() {
479 let h = half::f16::from_f32(1.0);
482 assert_eq!(h.to_bits(), 0x3C00);
483 assert_eq!(NumericElem::to_acc(h), 1.0f32);
484 assert_eq!(half::f16::from_acc(1.0f32).to_bits(), 0x3C00);
485 }
486
487 #[test]
488 fn int_div_by_zero_is_zero_not_panic() {
489 assert_eq!(5i32.c_div(0), 0);
490 assert_eq!(i32::MIN.c_div(-1), i32::MIN); }
492
493 #[test]
494 fn float_min_max_propagate_nan() {
495 assert!(f32::NAN.c_min(1.0).is_nan());
496 assert!(1.0f32.c_max(f32::NAN).is_nan());
497 assert_eq!(2.0f32.c_min(3.0), 2.0);
498 assert_eq!(2.0f32.c_max(3.0), 3.0);
499 }
500
501 #[test]
502 fn int_ops_wrap() {
503 assert_eq!(i8::MAX.c_add(1), i8::MIN);
504 assert_eq!(200u8.c_mul(2), 144); }
506
507 #[test]
508 fn unsupported_dtype_message_has_what_why_how() {
509 let e = unsupported_dtype("Add", DataType::Bool);
510 let s = format!("{e}");
511 assert!(s.contains("WHAT"));
512 assert!(s.contains("WHY"));
513 assert!(s.contains("HOW"));
514 }
515}