use std::ffi::c_void;
use std::sync::Arc;
use std::sync::OnceLock;
use onnx_runtime_ep_api::{DeviceBuffer, ExecutionProvider};
use onnx_runtime_ep_cpu::CpuExecutionProvider;
use onnx_runtime_ir::{DataType, DeviceId, TensorLayout};
use crate::error::{EagerError, Result};
fn shared_cpu_ep() -> Arc<dyn ExecutionProvider> {
static EP: OnceLock<Arc<CpuExecutionProvider>> = OnceLock::new();
EP.get_or_init(|| {
let mut ep = CpuExecutionProvider::new();
let _ = ep.initialize(&Default::default());
Arc::new(ep)
})
.clone()
}
fn host_bytes(buffer: &DeviceBuffer) -> &[u8] {
assert!(
buffer.device().is_host_accessible(),
"host_bytes on non-host device {:?}",
buffer.device()
);
if buffer.is_empty() {
return &[];
}
unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len()) }
}
fn write_host(buffer: &mut DeviceBuffer, src: &[u8]) -> Result<()> {
assert!(
buffer.device().is_host_accessible(),
"write_host on non-host device {:?}",
buffer.device()
);
if src.len() > buffer.len() {
return Err(EagerError::Kernel(onnx_runtime_ep_api::EpError::KernelFailed(
format!(
"write_host: source {} bytes exceeds buffer {} bytes",
src.len(),
buffer.len()
),
)));
}
if src.is_empty() {
return Ok(());
}
let dst = buffer.as_mut_ptr() as *mut u8;
unsafe {
std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len());
}
Ok(())
}
pub struct Tensor {
dtype: DataType,
shape: Vec<usize>,
layout: TensorLayout,
device: DeviceId,
buffer: Option<DeviceBuffer>,
allocator: Arc<dyn ExecutionProvider>,
}
impl Tensor {
pub fn from_raw_in(
allocator: Arc<dyn ExecutionProvider>,
dtype: DataType,
shape: Vec<usize>,
bytes: &[u8],
) -> Result<Self> {
let numel: usize = shape.iter().product();
let expected = dtype.storage_bytes(numel);
if bytes.len() != expected {
return Err(EagerError::Kernel(onnx_runtime_ep_api::EpError::KernelFailed(
format!(
"Tensor::from_raw_in: {} bytes for shape {shape:?} dtype {dtype:?}, expected {expected}",
bytes.len()
),
)));
}
let layout = TensorLayout::contiguous();
let mut buffer = allocator.allocate(expected.max(1), layout.alignment)?;
write_host(&mut buffer, bytes)?;
Ok(Self {
dtype,
shape,
layout,
device: buffer.device(),
buffer: Some(buffer),
allocator,
})
}
pub fn zeros_in(
allocator: Arc<dyn ExecutionProvider>,
dtype: DataType,
shape: Vec<usize>,
) -> Result<Self> {
let numel: usize = shape.iter().product();
let bytes = vec![0u8; dtype.storage_bytes(numel)];
Self::from_raw_in(allocator, dtype, shape, &bytes)
}
pub fn from_raw(dtype: DataType, shape: Vec<usize>, bytes: &[u8]) -> Result<Self> {
Self::from_raw_in(shared_cpu_ep(), dtype, shape, bytes)
}
pub fn zeros(dtype: DataType, shape: Vec<usize>) -> Result<Self> {
Self::zeros_in(shared_cpu_ep(), dtype, shape)
}
pub fn from_f32(shape: &[usize], data: &[f32]) -> Result<Self> {
let mut bytes = Vec::with_capacity(data.len() * 4);
for v in data {
bytes.extend_from_slice(&v.to_le_bytes());
}
Self::from_raw(DataType::Float32, shape.to_vec(), &bytes)
}
pub fn from_i64(shape: &[usize], data: &[i64]) -> Result<Self> {
let mut bytes = Vec::with_capacity(data.len() * 8);
for v in data {
bytes.extend_from_slice(&v.to_le_bytes());
}
Self::from_raw(DataType::Int64, shape.to_vec(), &bytes)
}
pub fn dtype(&self) -> DataType {
self.dtype
}
pub fn shape(&self) -> &[usize] {
&self.shape
}
pub fn layout(&self) -> &TensorLayout {
&self.layout
}
pub fn device(&self) -> DeviceId {
self.device
}
pub fn numel(&self) -> usize {
self.shape.iter().product()
}
fn buffer(&self) -> &DeviceBuffer {
self.buffer
.as_ref()
.expect("Tensor buffer taken only in Drop")
}
pub(crate) fn device_ptr(&self) -> *const c_void {
self.buffer().as_ptr()
}
pub(crate) fn device_ptr_mut(&mut self) -> *mut c_void {
self.buffer
.as_mut()
.expect("Tensor buffer taken only in Drop")
.as_mut_ptr()
}
pub fn as_bytes(&self) -> &[u8] {
let n = self.dtype.storage_bytes(self.numel());
&host_bytes(self.buffer())[..n]
}
pub fn to_vec_f32(&self) -> Vec<f32> {
assert_eq!(self.dtype, DataType::Float32, "to_vec_f32 on non-f32 tensor");
self.as_bytes()
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
}
pub fn to_vec_i64(&self) -> Vec<i64> {
assert_eq!(self.dtype, DataType::Int64, "to_vec_i64 on non-i64 tensor");
self.as_bytes()
.chunks_exact(8)
.map(|c| i64::from_le_bytes(c.try_into().unwrap()))
.collect()
}
}
impl Clone for Tensor {
fn clone(&self) -> Self {
Self::from_raw_in(
self.allocator.clone(),
self.dtype,
self.shape.clone(),
self.as_bytes(),
)
.expect("Tensor::clone: re-allocation of identical bytes")
}
}
impl std::fmt::Debug for Tensor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tensor")
.field("dtype", &self.dtype)
.field("shape", &self.shape)
.field("device", &self.device)
.finish()
}
}
impl Drop for Tensor {
fn drop(&mut self) {
if let Some(buffer) = self.buffer.take() {
let _ = self.allocator.deallocate(buffer);
}
}
}