use super::ops::{cast_numeric_slice, expected_byte_len, numeric_to_le_bytes};
use crate::error::{Error, Result};
use std::sync::{Arc, OnceLock};
#[cfg(feature = "uring")]
use wireshift::{Buffer, Completed};
#[derive(Debug, Clone)]
pub struct Tensor {
pub(crate) data: Arc<TensorData>,
pub(crate) dtype: DType,
pub(crate) shape: Arc<[usize]>,
}
#[derive(Debug, Default)]
pub(crate) struct TensorData {
pub(crate) bytes: TensorBytes,
pub(crate) f32_cache: OnceLock<Vec<f32>>,
pub(crate) f64_cache: OnceLock<Vec<f64>>,
pub(crate) i32_cache: OnceLock<Vec<i32>>,
pub(crate) i64_cache: OnceLock<Vec<i64>>,
}
#[derive(Debug)]
pub(crate) enum TensorBytes {
Shared(Arc<[u8]>),
#[cfg(feature = "uring")]
Pooled(Buffer<Completed>),
}
impl Default for TensorBytes {
fn default() -> Self {
Self::Shared(Arc::from([]))
}
}
impl TensorBytes {
fn as_slice(&self) -> &[u8] {
match self {
Self::Shared(bytes) => bytes,
#[cfg(feature = "uring")]
Self::Pooled(buffer) => buffer.filled(),
}
}
fn len(&self) -> usize {
self.as_slice().len()
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DType {
F32,
F64,
U8,
I32,
I64,
Bytes,
}
impl DType {
pub fn element_size(self) -> usize {
match self {
Self::F32 | Self::I32 => 4,
Self::F64 | Self::I64 => 8,
Self::U8 | Self::Bytes => 1,
}
}
pub fn name(self) -> &'static str {
match self {
Self::F32 => "f32",
Self::F64 => "f64",
Self::U8 => "u8",
Self::I32 => "i32",
Self::I64 => "i64",
Self::Bytes => "bytes",
}
}
}
impl std::fmt::Display for DType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
impl Tensor {
pub fn f32(data: &[f32], shape: Vec<usize>) -> Self {
Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::F32, shape)
}
pub fn f64(data: &[f64], shape: Vec<usize>) -> Self {
Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::F64, shape)
}
pub fn u8(data: Vec<u8>, shape: Vec<usize>) -> Self {
Self::from_bytes_unchecked(data, DType::U8, shape)
}
pub fn i32(data: &[i32], shape: Vec<usize>) -> Self {
Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::I32, shape)
}
pub fn i64(data: &[i64], shape: Vec<usize>) -> Self {
Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::I64, shape)
}
pub fn bytes(data: Vec<u8>) -> Self {
let len = data.len();
Self::from_bytes_unchecked(data, DType::Bytes, vec![len])
}
#[cfg(feature = "uring")]
pub fn bytes_from_completed(buffer: Buffer<Completed>) -> Self {
let len = buffer.filled_len();
Self::from_storage_unchecked(TensorBytes::Pooled(buffer), DType::Bytes, vec![len])
}
pub fn from_bytes(data: Vec<u8>, dtype: DType, shape: Vec<usize>) -> Result<Self> {
let expected = expected_byte_len(dtype, &shape)?;
if data.len() != expected {
return Err(Error::InvalidConfig {
reason: format!(
"tensor byte length {} does not match dtype {:?} and shape {:?} (expected {expected})",
data.len(),
dtype,
shape
),
});
}
Ok(Self::from_bytes_unchecked(data, dtype, shape))
}
pub fn dtype(&self) -> DType {
self.dtype
}
pub fn shape(&self) -> &[usize] {
&self.shape
}
pub fn num_elements(&self) -> Option<usize> {
self.shape
.iter()
.try_fold(1_usize, |acc, &dim| acc.checked_mul(dim))
}
pub fn as_bytes(&self) -> &[u8] {
self.data.bytes.as_slice()
}
pub fn try_as_f32(&self) -> Result<&[f32]> {
if self.dtype != DType::F32 {
return Err(Error::InvalidConfig {
reason: format!("expected dtype f32, got {}", self.dtype),
});
}
cast_numeric_slice(
self.data.bytes.as_slice(),
&self.data.f32_cache,
f32::from_le_bytes,
)
}
pub fn try_as_f64(&self) -> Result<&[f64]> {
if self.dtype != DType::F64 {
return Err(Error::InvalidConfig {
reason: format!("expected dtype f64, got {}", self.dtype),
});
}
cast_numeric_slice(
self.data.bytes.as_slice(),
&self.data.f64_cache,
f64::from_le_bytes,
)
}
pub fn try_as_i32(&self) -> Result<&[i32]> {
if self.dtype != DType::I32 {
return Err(Error::InvalidConfig {
reason: format!("expected dtype i32, got {}", self.dtype),
});
}
cast_numeric_slice(
self.data.bytes.as_slice(),
&self.data.i32_cache,
i32::from_le_bytes,
)
}
pub fn try_as_i64(&self) -> Result<&[i64]> {
if self.dtype != DType::I64 {
return Err(Error::InvalidConfig {
reason: format!("expected dtype i64, got {}", self.dtype),
});
}
cast_numeric_slice(
self.data.bytes.as_slice(),
&self.data.i64_cache,
i64::from_le_bytes,
)
}
pub fn byte_len(&self) -> usize {
self.data.bytes.len()
}
pub(crate) fn from_bytes_unchecked(data: Vec<u8>, dtype: DType, shape: Vec<usize>) -> Self {
Self::from_storage_unchecked(TensorBytes::Shared(Arc::from(data)), dtype, shape)
}
pub(crate) fn from_storage_unchecked(
data: TensorBytes,
dtype: DType,
shape: Vec<usize>,
) -> Self {
Self {
data: Arc::new(TensorData {
bytes: data,
f32_cache: OnceLock::new(),
f64_cache: OnceLock::new(),
i32_cache: OnceLock::new(),
i64_cache: OnceLock::new(),
}),
dtype,
shape: Arc::from(shape),
}
}
}