use std::borrow::Cow;
use onnx_runtime_ep_api::{EpError, Result, TensorMut, TensorView};
use onnx_runtime_ir::DataType;
use crate::strided::{elem_offset, next_index, numel};
pub trait ComputeDomain: Copy + Default {
fn c_add(self, o: Self) -> Self;
fn c_sub(self, o: Self) -> Self;
fn c_mul(self, o: Self) -> Self;
fn c_div(self, o: Self) -> Self;
fn c_pow(self, o: Self) -> Self;
fn c_div_usize(self, divisor: usize) -> Self;
fn c_min(self, o: Self) -> Self;
fn c_max(self, o: Self) -> Self;
}
macro_rules! impl_float_compute {
($($t:ty),*) => {$(
impl ComputeDomain for $t {
#[inline] fn c_add(self, o: Self) -> Self { self + o }
#[inline] fn c_sub(self, o: Self) -> Self { self - o }
#[inline] fn c_mul(self, o: Self) -> Self { self * o }
#[inline] fn c_div(self, o: Self) -> Self { self / o }
#[inline] fn c_pow(self, o: Self) -> Self { self.powf(o) }
#[inline] fn c_div_usize(self, divisor: usize) -> Self { self / divisor as $t }
#[inline] fn c_min(self, o: Self) -> Self {
if self.is_nan() || o.is_nan() { <$t>::NAN } else { self.min(o) }
}
#[inline] fn c_max(self, o: Self) -> Self {
if self.is_nan() || o.is_nan() { <$t>::NAN } else { self.max(o) }
}
}
)*};
}
impl_float_compute!(f32, f64);
macro_rules! impl_int_compute {
($($t:ty),*) => {$(
impl ComputeDomain for $t {
#[inline] fn c_add(self, o: Self) -> Self { self.wrapping_add(o) }
#[inline] fn c_sub(self, o: Self) -> Self { self.wrapping_sub(o) }
#[inline] fn c_mul(self, o: Self) -> Self { self.wrapping_mul(o) }
#[inline] fn c_div(self, o: Self) -> Self {
if o == 0 { 0 } else { self.wrapping_div(o) }
}
#[inline] fn c_pow(self, o: Self) -> Self { (self as f64).powf(o as f64) as $t }
#[inline] fn c_div_usize(self, divisor: usize) -> Self {
((self as i128) / divisor as i128) as $t
}
#[inline] fn c_min(self, o: Self) -> Self { core::cmp::min(self, o) }
#[inline] fn c_max(self, o: Self) -> Self { core::cmp::max(self, o) }
}
)*};
}
impl_int_compute!(i8, i16, i32, i64, u8, u16, u32, u64);
pub trait NumericElem: Copy {
const DTYPE: DataType;
type Acc: ComputeDomain;
fn to_acc(self) -> Self::Acc;
fn from_acc(a: Self::Acc) -> Self;
fn from_f32_scalar(f: f32) -> Self;
}
pub trait FloatElem: Copy {
const DTYPE: DataType;
fn to_f32(self) -> f32;
fn from_f32(f: f32) -> Self;
}
impl NumericElem for f32 {
const DTYPE: DataType = DataType::Float32;
type Acc = f32;
#[inline]
fn to_acc(self) -> f32 {
self
}
#[inline]
fn from_acc(a: f32) -> Self {
a
}
#[inline]
fn from_f32_scalar(f: f32) -> Self {
f
}
}
impl FloatElem for f32 {
const DTYPE: DataType = DataType::Float32;
#[inline]
fn to_f32(self) -> f32 {
self
}
#[inline]
fn from_f32(f: f32) -> Self {
f
}
}
impl NumericElem for f64 {
const DTYPE: DataType = DataType::Float64;
type Acc = f64;
#[inline]
fn to_acc(self) -> f64 {
self
}
#[inline]
fn from_acc(a: f64) -> Self {
a
}
#[inline]
fn from_f32_scalar(f: f32) -> Self {
f as f64
}
}
impl FloatElem for f64 {
const DTYPE: DataType = DataType::Float64;
#[inline]
fn to_f32(self) -> f32 {
self as f32
}
#[inline]
fn from_f32(f: f32) -> Self {
f as f64
}
}
impl NumericElem for half::f16 {
const DTYPE: DataType = DataType::Float16;
type Acc = f32;
#[inline]
fn to_acc(self) -> f32 {
self.to_f32()
}
#[inline]
fn from_acc(a: f32) -> Self {
half::f16::from_f32(a)
}
#[inline]
fn from_f32_scalar(f: f32) -> Self {
half::f16::from_f32(f)
}
}
impl FloatElem for half::f16 {
const DTYPE: DataType = DataType::Float16;
#[inline]
fn to_f32(self) -> f32 {
half::f16::to_f32(self)
}
#[inline]
fn from_f32(f: f32) -> Self {
half::f16::from_f32(f)
}
}
impl NumericElem for half::bf16 {
const DTYPE: DataType = DataType::BFloat16;
type Acc = f32;
#[inline]
fn to_acc(self) -> f32 {
self.to_f32()
}
#[inline]
fn from_acc(a: f32) -> Self {
half::bf16::from_f32(a)
}
#[inline]
fn from_f32_scalar(f: f32) -> Self {
half::bf16::from_f32(f)
}
}
impl FloatElem for half::bf16 {
const DTYPE: DataType = DataType::BFloat16;
#[inline]
fn to_f32(self) -> f32 {
half::bf16::to_f32(self)
}
#[inline]
fn from_f32(f: f32) -> Self {
half::bf16::from_f32(f)
}
}
macro_rules! impl_int_elem {
($($t:ty => $dt:expr),* $(,)?) => {$(
impl NumericElem for $t {
const DTYPE: DataType = $dt;
type Acc = $t;
#[inline] fn to_acc(self) -> $t { self }
#[inline] fn from_acc(a: $t) -> Self { a }
#[inline] fn from_f32_scalar(f: f32) -> Self { f as $t }
}
)*};
}
impl_int_elem!(
i8 => DataType::Int8,
i16 => DataType::Int16,
i32 => DataType::Int32,
i64 => DataType::Int64,
u8 => DataType::Uint8,
u16 => DataType::Uint16,
u32 => DataType::Uint32,
u64 => DataType::Uint64,
);
pub fn to_dense<T: NumericElem>(view: &TensorView) -> Result<Vec<T>> {
read_strided::<T>(view, T::DTYPE)
}
pub fn to_dense_float<T: FloatElem>(view: &TensorView) -> Result<Vec<T>> {
read_strided::<T>(view, T::DTYPE)
}
fn read_strided<T: Copy>(view: &TensorView, want: DataType) -> Result<Vec<T>> {
view.validate()?;
debug_assert_eq!(
std::mem::size_of::<T>(),
want.byte_size(),
"read_strided element width must match dtype byte size"
);
if view.dtype != want {
return Err(EpError::InvalidTensorView {
reason: format!("expected {want:?} view, got {:?}", view.dtype),
});
}
let n = numel(view.shape);
let mut out = Vec::with_capacity(n);
if n == 0 {
return Ok(out);
}
let origin = view.data_ptr::<T>();
let mut idx = vec![0usize; view.shape.len()];
loop {
let off = elem_offset(view.strides, &idx);
out.push(unsafe { *origin.offset(off) });
if !next_index(view.shape, &mut idx) {
break;
}
}
Ok(out)
}
pub fn write_dense<T: NumericElem>(out: &mut TensorMut, data: &[T]) -> Result<()> {
write_strided::<T>(out, data, T::DTYPE)
}
pub fn write_dense_float<T: FloatElem>(out: &mut TensorMut, data: &[T]) -> Result<()> {
write_strided::<T>(out, data, T::DTYPE)
}
fn write_strided<T: Copy>(out: &mut TensorMut, data: &[T], want: DataType) -> Result<()> {
out.validate()?;
if out.dtype != want {
return Err(EpError::InvalidTensorView {
reason: format!("expected {want:?} output, got {:?}", out.dtype),
});
}
let n = numel(out.shape);
if data.len() != n {
return Err(EpError::KernelFailed(format!(
"output element count {n} does not match produced {}",
data.len()
)));
}
if n == 0 {
return Ok(());
}
let origin = out.data_ptr_mut::<T>();
let strides = out.strides;
let shape = out.shape;
let mut idx = vec![0usize; shape.len()];
let mut i = 0usize;
loop {
let off = elem_offset(strides, &idx);
unsafe {
*origin.offset(off) = data[i];
}
i += 1;
if !next_index(shape, &mut idx) {
break;
}
}
Ok(())
}
pub fn unsupported_dtype(op: &str, dtype: DataType) -> EpError {
EpError::KernelFailed(format!(
"{op}: unsupported element type {dtype:?} (WHAT: this CPU kernel was asked \
to run {op} on a {dtype:?} tensor). WHY: ONNX does not define {op} for \
{dtype:?}, or arithmetic on it is not implemented by this execution \
provider. HOW: insert a `Cast` to a supported numeric dtype (e.g. \
Float32) before {op}, or run the op on an EP that implements {dtype:?}."
))
}
#[macro_export]
macro_rules! dispatch_arith {
($dtype:expr, $op:expr, $T:ident => $body:expr) => {{
match $dtype {
::onnx_runtime_ir::DataType::Float32 => {
type $T = f32;
$body
}
::onnx_runtime_ir::DataType::Float16 => {
type $T = half::f16;
$body
}
::onnx_runtime_ir::DataType::BFloat16 => {
type $T = half::bf16;
$body
}
::onnx_runtime_ir::DataType::Float64 => {
type $T = f64;
$body
}
::onnx_runtime_ir::DataType::Int8 => {
type $T = i8;
$body
}
::onnx_runtime_ir::DataType::Int16 => {
type $T = i16;
$body
}
::onnx_runtime_ir::DataType::Int32 => {
type $T = i32;
$body
}
::onnx_runtime_ir::DataType::Int64 => {
type $T = i64;
$body
}
::onnx_runtime_ir::DataType::Uint8 => {
type $T = u8;
$body
}
::onnx_runtime_ir::DataType::Uint16 => {
type $T = u16;
$body
}
::onnx_runtime_ir::DataType::Uint32 => {
type $T = u32;
$body
}
::onnx_runtime_ir::DataType::Uint64 => {
type $T = u64;
$body
}
other => Err($crate::dtype::unsupported_dtype($op, other)),
}
}};
}
#[macro_export]
macro_rules! dispatch_float {
($dtype:expr, $op:expr, $T:ident => $body:expr) => {{
match $dtype {
::onnx_runtime_ir::DataType::Float32 => {
type $T = f32;
$body
}
::onnx_runtime_ir::DataType::Float16 => {
type $T = half::f16;
$body
}
::onnx_runtime_ir::DataType::BFloat16 => {
type $T = half::bf16;
$body
}
::onnx_runtime_ir::DataType::Float64 => {
type $T = f64;
$body
}
other => Err($crate::dtype::unsupported_dtype($op, other)),
}
}};
}
pub fn to_dense_f32_widen<'a>(op: &str, view: &'a TensorView<'_>) -> Result<Cow<'a, [f32]>> {
if view.dtype == DataType::Float32 && view.is_contiguous() {
view.validate()?;
let len = view.numel();
if len == 0 {
return Ok(Cow::Borrowed(&[]));
}
let data = unsafe { std::slice::from_raw_parts(view.data_ptr::<f32>(), len) };
return Ok(Cow::Borrowed(data));
}
dispatch_float!(view.dtype, op, T => {
let raw = to_dense_float::<T>(view)?;
Ok(Cow::Owned(
raw.into_iter().map(|v| v.to_f32()).collect(),
))
})
}
pub fn write_dense_f32_narrow(op: &str, out: &mut TensorMut, data: &[f32]) -> Result<()> {
dispatch_float!(out.dtype, op, T => {
let narrowed: Vec<T> = data.iter().map(|&v| T::from_f32(v)).collect();
write_dense_float::<T>(out, &narrowed)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f16_roundtrips_through_f32_without_bit_reinterpret() {
let h = half::f16::from_f32(1.0);
assert_eq!(h.to_bits(), 0x3C00);
assert_eq!(NumericElem::to_acc(h), 1.0f32);
assert_eq!(half::f16::from_acc(1.0f32).to_bits(), 0x3C00);
}
#[test]
fn int_div_by_zero_is_zero_not_panic() {
assert_eq!(5i32.c_div(0), 0);
assert_eq!(i32::MIN.c_div(-1), i32::MIN); }
#[test]
fn float_min_max_propagate_nan() {
assert!(f32::NAN.c_min(1.0).is_nan());
assert!(1.0f32.c_max(f32::NAN).is_nan());
assert_eq!(2.0f32.c_min(3.0), 2.0);
assert_eq!(2.0f32.c_max(3.0), 3.0);
}
#[test]
fn int_ops_wrap() {
assert_eq!(i8::MAX.c_add(1), i8::MIN);
assert_eq!(200u8.c_mul(2), 144); }
#[test]
fn unsupported_dtype_message_has_what_why_how() {
let e = unsupported_dtype("Add", DataType::Bool);
let s = format!("{e}");
assert!(s.contains("WHAT"));
assert!(s.contains("WHY"));
assert!(s.contains("HOW"));
}
}