use super::{Layout, Storage, TensorId};
use crate::dtype::{DType, DataType, Element};
use crate::error::{Error, Result};
use crate::runtime::Runtime;
use std::fmt;
pub struct Tensor<R: Runtime> {
id: TensorId,
storage: Storage<R>,
layout: Layout,
}
impl<R: Runtime> Tensor<R> {
pub fn from_parts(storage: Storage<R>, layout: Layout) -> Self {
Self {
id: TensorId::new(),
storage,
layout,
}
}
#[track_caller]
pub fn empty(shape: &[usize], dtype: R::DType, device: &R::Device) -> Self {
Self::try_empty(shape, dtype, device)
.unwrap_or_else(|e| panic!("Tensor::empty failed: {e}"))
}
pub fn try_empty(shape: &[usize], dtype: R::DType, device: &R::Device) -> Result<Self> {
let len: usize = shape.iter().product();
let storage = Storage::new(len, dtype, device)?;
let layout = Layout::contiguous(shape);
Ok(Self {
id: TensorId::new(),
storage,
layout,
})
}
#[inline]
pub fn id(&self) -> TensorId {
self.id
}
#[inline]
pub fn storage(&self) -> &Storage<R> {
&self.storage
}
#[inline]
pub fn layout(&self) -> &Layout {
&self.layout
}
#[inline]
pub fn shape(&self) -> &[usize] {
self.layout.shape()
}
#[inline]
pub fn strides(&self) -> &[isize] {
self.layout.strides()
}
#[inline]
pub fn ndim(&self) -> usize {
self.layout.ndim()
}
#[inline]
pub fn numel(&self) -> usize {
self.layout.elem_count()
}
#[inline]
pub fn dtype(&self) -> R::DType {
self.storage.dtype()
}
#[inline]
pub fn device(&self) -> &R::Device {
self.storage.device()
}
#[inline]
pub fn is_contiguous(&self) -> bool {
self.layout.is_contiguous()
}
#[inline]
pub fn is_scalar(&self) -> bool {
self.layout.is_scalar()
}
pub fn size(&self, dim: isize) -> Option<usize> {
self.layout.dim(dim)
}
pub fn dim(&self, index: isize) -> Result<usize> {
self.layout.dim(index).ok_or(Error::InvalidDimension {
dim: index,
ndim: self.ndim(),
})
}
#[inline]
pub fn rank(&self) -> usize {
self.layout.ndim()
}
#[inline]
pub fn elem_count(&self) -> usize {
self.layout.elem_count()
}
#[inline]
pub fn dims(&self) -> &[usize] {
self.layout.shape()
}
#[inline]
pub fn len(&self) -> usize {
self.layout.elem_count()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.layout.elem_count() == 0
}
#[inline]
pub fn offset(&self) -> usize {
self.layout.offset()
}
#[inline]
pub fn ptr(&self) -> u64 {
self.storage.ptr() + (self.layout.offset() * self.dtype().size_in_bytes()) as u64
}
#[inline]
pub fn owns_memory(&self) -> bool {
self.storage.is_owned()
}
pub fn shares_storage_with(&self, other: &Tensor<R>) -> bool {
self.storage.ptr() == other.storage.ptr()
}
pub fn ref_count(&self) -> usize {
self.storage.ref_count()
}
pub fn dims1(&self) -> Result<usize> {
let s = self.shape();
if s.len() == 1 {
Ok(s[0])
} else {
Err(Error::ShapeMismatch {
expected: vec![0],
got: s.to_vec(),
})
}
}
pub fn dims2(&self) -> Result<(usize, usize)> {
let s = self.shape();
if s.len() == 2 {
Ok((s[0], s[1]))
} else {
Err(Error::ShapeMismatch {
expected: vec![0, 0],
got: s.to_vec(),
})
}
}
pub fn dims3(&self) -> Result<(usize, usize, usize)> {
let s = self.shape();
if s.len() == 3 {
Ok((s[0], s[1], s[2]))
} else {
Err(Error::ShapeMismatch {
expected: vec![0, 0, 0],
got: s.to_vec(),
})
}
}
pub fn dims4(&self) -> Result<(usize, usize, usize, usize)> {
let s = self.shape();
if s.len() == 4 {
Ok((s[0], s[1], s[2], s[3]))
} else {
Err(Error::ShapeMismatch {
expected: vec![0, 0, 0, 0],
got: s.to_vec(),
})
}
}
pub fn dims5(&self) -> Result<(usize, usize, usize, usize, usize)> {
let s = self.shape();
if s.len() == 5 {
Ok((s[0], s[1], s[2], s[3], s[4]))
} else {
Err(Error::ShapeMismatch {
expected: vec![0, 0, 0, 0, 0],
got: s.to_vec(),
})
}
}
pub fn from_storage_contiguous(storage: Storage<R>, shape: &[usize]) -> Self {
Self {
id: TensorId::new(),
storage,
layout: Layout::contiguous(shape),
}
}
pub fn transpose(&self, dim0: isize, dim1: isize) -> Result<Self> {
let new_layout =
self.layout
.transpose(dim0, dim1)
.ok_or_else(|| Error::InvalidDimension {
dim: dim0,
ndim: self.ndim(),
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn t(&self) -> Result<Self> {
self.transpose(-2, -1)
}
pub fn reshape(&self, shape: &[usize]) -> Result<Self> {
let new_layout = self.layout.reshape(shape).ok_or(Error::NotContiguous)?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn flatten(&self) -> Result<Self> {
self.reshape(&[self.numel()])
}
pub fn squeeze(&self, dim: Option<isize>) -> Self {
Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: self.layout.squeeze(dim),
}
}
pub fn unsqueeze(&self, dim: isize) -> Result<Self> {
let new_layout = self
.layout
.unsqueeze(dim)
.ok_or_else(|| Error::InvalidDimension {
dim,
ndim: self.ndim(),
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn view(&self, shape: &[usize]) -> Result<Self> {
self.reshape(shape)
}
pub fn permute(&self, dims: &[usize]) -> Result<Self> {
let new_layout = self
.layout
.permute(dims)
.ok_or_else(|| Error::InvalidDimension {
dim: dims.first().copied().unwrap_or(0) as isize,
ndim: self.ndim(),
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn narrow(&self, dim: isize, start: usize, length: usize) -> Result<Self> {
let dim_idx = self
.layout
.normalize_dim(dim)
.ok_or(Error::InvalidDimension {
dim,
ndim: self.ndim(),
})?;
let new_layout =
self.layout
.narrow(dim_idx, start, length)
.ok_or_else(|| Error::ShapeMismatch {
expected: vec![self.shape()[dim_idx]],
got: vec![start, length],
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn broadcast_to(&self, shape: &[usize]) -> Result<Self> {
let new_layout = self
.layout
.broadcast_to(shape)
.ok_or_else(|| Error::BroadcastError {
lhs: self.shape().to_vec(),
rhs: shape.to_vec(),
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn flip(&self, dim: isize) -> Result<Self> {
let new_layout = self.layout.flip(dim).ok_or(Error::InvalidDimension {
dim,
ndim: self.ndim(),
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn flip_dims(&self, dims: &[isize]) -> Result<Self> {
let new_layout = self
.layout
.flip_dims(dims)
.ok_or_else(|| Error::InvalidDimension {
dim: dims.first().copied().unwrap_or(0),
ndim: self.ndim(),
})?;
Ok(Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: new_layout,
})
}
pub fn contiguous(&self) -> Self {
if self.is_contiguous() && self.layout.offset() == 0 {
self.clone()
} else {
let dtype = self.dtype();
let device = self.storage.device();
let numel = self.numel();
let new_storage =
Storage::new(numel, dtype, device).expect("Tensor::contiguous allocation failed");
let new_layout = Layout::contiguous(self.shape());
let elem_size = dtype.size_in_bytes();
let src_handle = self.storage.ptr();
let dst_handle = new_storage.ptr();
let src_byte_offset = self.layout.offset() * elem_size;
R::copy_strided(
src_handle,
src_byte_offset,
dst_handle,
self.shape(),
self.strides(),
elem_size,
device,
)
.expect("copy_strided failed in contiguous()");
Self {
id: TensorId::new(),
storage: new_storage,
layout: new_layout,
}
}
}
pub fn detach(&self) -> Self {
Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: self.layout.clone(),
}
}
pub fn to_vec<T: bytemuck::Pod>(&self) -> Vec<T> {
assert!(
self.is_contiguous(),
"Tensor must be contiguous to copy to vec"
);
let numel = self.numel();
let offset = self.layout.offset();
let elem_size = std::mem::size_of::<T>();
let byte_offset = offset * elem_size;
let mut result = vec![T::zeroed(); numel];
let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut result);
let src_ptr = self.storage.ptr() as usize + byte_offset;
R::copy_from_device(src_ptr as u64, bytes, self.storage.device())
.expect("copy_from_device failed in to_vec()");
result
}
pub fn record_event(&self) -> crate::error::Result<u64> {
R::record_compute_event(self.storage.device())
}
pub fn to_vec_pipelined<T: bytemuck::Pod>(&self, event: u64) -> crate::error::Result<Vec<T>> {
if !self.is_contiguous() {
return Err(crate::error::Error::ShapeMismatch {
expected: vec![self.numel()],
got: self.shape().to_vec(),
});
}
let numel = self.numel();
let offset = self.layout.offset();
let elem_size = std::mem::size_of::<T>();
let byte_offset = offset * elem_size;
let mut result = vec![T::zeroed(); numel];
let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut result);
let src_ptr = self.storage.ptr() as usize + byte_offset;
R::copy_from_device_pipelined(src_ptr as u64, bytes, self.storage.device(), event)?;
Ok(result)
}
pub fn item<T: bytemuck::Pod + Copy>(&self) -> Result<T> {
if self.numel() != 1 {
return Err(Error::ShapeMismatch {
expected: vec![1],
got: self.shape().to_vec(),
});
}
let tensor = if self.is_contiguous() {
std::borrow::Cow::Borrowed(self)
} else {
std::borrow::Cow::Owned(self.contiguous())
};
let offset = tensor.layout.offset();
let elem_size = std::mem::size_of::<T>();
let byte_offset = offset * elem_size;
let src_ptr = (tensor.storage.ptr() as usize + byte_offset) as u64;
let mut result = T::zeroed();
let bytes: &mut [u8] = bytemuck::bytes_of_mut(&mut result);
R::copy_from_device(src_ptr, bytes, tensor.storage.device())?;
Ok(result)
}
}
impl<R: Runtime> Tensor<R> {
pub fn try_zeros_generic(shape: &[usize], dtype: R::DType, device: &R::Device) -> Result<Self> {
Self::try_full_scalar_generic(shape, dtype, 0.0, device)
}
pub fn try_ones_generic(shape: &[usize], dtype: R::DType, device: &R::Device) -> Result<Self> {
Self::try_full_scalar_generic(shape, dtype, 1.0, device)
}
pub fn try_full_scalar_generic(
shape: &[usize],
dtype: R::DType,
value: f64,
device: &R::Device,
) -> Result<Self> {
let len: usize = shape.iter().product();
if len == 0 {
return Self::try_empty(shape, dtype, device);
}
let bytes = dtype.fill_bytes(value, len).ok_or_else(|| {
Error::Msg(format!(
"fill not supported for dtype {}",
dtype.short_name()
))
})?;
let storage = Storage::from_bytes(&bytes, dtype, device)?;
let layout = Layout::contiguous(shape);
Ok(Self {
id: TensorId::new(),
storage,
layout,
})
}
pub fn try_from_bytes(
bytes: &[u8],
shape: &[usize],
dtype: R::DType,
device: &R::Device,
) -> Result<Self> {
let storage = Storage::from_bytes(bytes, dtype, device)?;
let layout = Layout::contiguous(shape);
Ok(Self {
id: TensorId::new(),
storage,
layout,
})
}
}
impl<R: Runtime<DType = DType>> Tensor<R> {
#[track_caller]
pub fn from_slice<T: Element>(data: &[T], shape: &[usize], device: &R::Device) -> Self {
Self::try_from_slice(data, shape, device)
.unwrap_or_else(|e| panic!("Tensor::from_slice failed: {e}"))
}
pub fn try_from_slice<T: Element>(
data: &[T],
shape: &[usize],
device: &R::Device,
) -> Result<Self> {
let expected_len: usize = shape.iter().product();
if data.len() != expected_len {
return Err(Error::ShapeMismatch {
expected: shape.to_vec(),
got: vec![data.len()],
});
}
let storage = Storage::from_slice(data, device)?;
let layout = Layout::contiguous(shape);
Ok(Self {
id: TensorId::new(),
storage,
layout,
})
}
#[track_caller]
pub fn zeros(shape: &[usize], dtype: DType, device: &R::Device) -> Self {
Self::try_zeros(shape, dtype, device)
.unwrap_or_else(|e| panic!("Tensor::zeros failed: {e}"))
}
pub fn try_zeros(shape: &[usize], dtype: DType, device: &R::Device) -> Result<Self> {
Self::try_full_scalar(shape, dtype, 0.0, device)
}
#[track_caller]
pub fn ones(shape: &[usize], dtype: DType, device: &R::Device) -> Self {
Self::try_ones(shape, dtype, device).unwrap_or_else(|e| panic!("Tensor::ones failed: {e}"))
}
pub fn try_ones(shape: &[usize], dtype: DType, device: &R::Device) -> Result<Self> {
Self::try_full_scalar(shape, dtype, 1.0, device)
}
#[track_caller]
pub fn full_scalar(shape: &[usize], dtype: DType, value: f64, device: &R::Device) -> Self {
Self::try_full_scalar(shape, dtype, value, device)
.unwrap_or_else(|e| panic!("Tensor::full_scalar failed: {e}"))
}
pub fn try_full_scalar(
shape: &[usize],
dtype: DType,
value: f64,
device: &R::Device,
) -> Result<Self> {
#[inline]
fn typed_to_bytes<T: bytemuck::NoUninit>(v: Vec<T>) -> Vec<u8> {
bytemuck::cast_slice::<T, u8>(&v).to_vec()
}
let len: usize = shape.iter().product();
if len == 0 {
return Self::try_empty(shape, dtype, device);
}
let bytes: Vec<u8> = match dtype {
DType::F64 => typed_to_bytes(vec![value; len]),
DType::F32 => typed_to_bytes(vec![value as f32; len]),
DType::F16 => {
#[cfg(feature = "f16")]
{
use half::f16;
typed_to_bytes(vec![f16::from_f64(value); len])
}
#[cfg(not(feature = "f16"))]
{
let half_bits = half_from_f32(value as f32, dtype);
typed_to_bytes(vec![half_bits; len])
}
}
DType::BF16 => {
#[cfg(feature = "f16")]
{
use half::bf16;
typed_to_bytes(vec![bf16::from_f64(value); len])
}
#[cfg(not(feature = "f16"))]
{
let half_bits = half_from_f32(value as f32, dtype);
typed_to_bytes(vec![half_bits; len])
}
}
DType::FP8E4M3 => {
vec![crate::dtype::FP8E4M3::from_f32(value as f32).to_bits(); len]
}
DType::FP8E5M2 => {
vec![crate::dtype::FP8E5M2::from_f32(value as f32).to_bits(); len]
}
DType::I64 => typed_to_bytes(vec![value as i64; len]),
DType::I32 => typed_to_bytes(vec![value as i32; len]),
DType::I16 => typed_to_bytes(vec![value as i16; len]),
DType::I8 => typed_to_bytes(vec![value as i8; len]),
DType::U64 => typed_to_bytes(vec![value as u64; len]),
DType::U32 => typed_to_bytes(vec![value as u32; len]),
DType::U16 => typed_to_bytes(vec![value as u16; len]),
DType::U8 => vec![value as u8; len],
DType::Bool => vec![if value != 0.0 { 1u8 } else { 0u8 }; len],
DType::Complex64 => {
typed_to_bytes(vec![crate::dtype::Complex64::new(value as f32, 0.0); len])
}
DType::Complex128 => {
typed_to_bytes(vec![crate::dtype::Complex128::new(value, 0.0); len])
}
};
let storage = Storage::from_bytes(&bytes, dtype, device)?;
let layout = Layout::contiguous(shape);
Ok(Self {
id: TensorId::new(),
storage,
layout,
})
}
}
impl<R: Runtime> Tensor<R> {
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let tensor = if self.is_contiguous() && self.offset() == 0 {
std::borrow::Cow::Borrowed(self)
} else {
std::borrow::Cow::Owned(self.contiguous())
};
let size = tensor.numel() * tensor.dtype().size_in_bytes();
let mut data = vec![0u8; size];
R::copy_from_device(tensor.storage().ptr(), &mut data, tensor.storage().device())
.map_err(|e| Error::Msg(format!("to_bytes copy failed: {}", e)))?;
Ok(data)
}
pub fn clone_deep(&self) -> Result<Self> {
let bytes = self.to_bytes()?;
Self::try_from_bytes(&bytes, self.shape(), self.dtype(), self.device())
}
}
impl<R: Runtime> Clone for Tensor<R> {
fn clone(&self) -> Self {
Self {
id: TensorId::new(),
storage: self.storage.clone(),
layout: self.layout.clone(),
}
}
}
impl<R: Runtime> fmt::Debug for Tensor<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tensor")
.field("id", &self.id)
.field("shape", &self.shape())
.field("dtype", &self.dtype())
.field("contiguous", &self.is_contiguous())
.finish()
}
}
impl<R: Runtime> fmt::Display for Tensor<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Tensor({:?}, dtype={})",
self.shape(),
self.dtype().short_name()
)
}
}
#[cfg(not(feature = "f16"))]
fn half_from_f32(value: f32, dtype: DType) -> u16 {
let bits = value.to_bits();
let sign = (bits >> 31) & 1;
let exp = ((bits >> 23) & 0xFF) as i32;
let frac = bits & 0x7FFFFF;
if dtype == DType::BF16 {
((bits >> 16) & 0xFFFF) as u16
} else {
if exp == 0 {
(sign << 15) as u16
} else if exp == 0xFF {
((sign << 15) | 0x7C00 | if frac != 0 { 0x200 } else { 0 }) as u16
} else {
let new_exp = exp - 127 + 15;
if new_exp <= 0 {
(sign << 15) as u16
} else if new_exp >= 31 {
((sign << 15) | 0x7C00) as u16
} else {
((sign << 15) | ((new_exp as u32) << 10) | (frac >> 13)) as u16
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::cpu::{CpuDevice, CpuRuntime};
#[test]
fn test_from_slice() {
let device = CpuDevice::new();
let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let tensor = Tensor::<CpuRuntime>::from_slice(&data, &[2, 3], &device);
assert_eq!(tensor.shape(), &[2, 3]);
assert_eq!(tensor.dtype(), DType::F32);
assert!(tensor.is_contiguous());
assert_eq!(tensor.numel(), 6);
let result: Vec<f32> = tensor.to_vec();
assert_eq!(result, data);
}
#[test]
fn test_transpose() {
let device = CpuDevice::new();
let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let tensor = Tensor::<CpuRuntime>::from_slice(&data, &[2, 3], &device);
let transposed = tensor.transpose(0, 1).unwrap();
assert_eq!(transposed.shape(), &[3, 2]);
assert!(!transposed.is_contiguous()); assert_eq!(transposed.numel(), 6);
}
#[test]
fn test_contiguous_already_contiguous() {
let device = CpuDevice::new();
let data = [1.0f32, 2.0, 3.0, 4.0];
let tensor = Tensor::<CpuRuntime>::from_slice(&data, &[2, 2], &device);
assert!(tensor.is_contiguous());
let contiguous = tensor.contiguous();
assert!(contiguous.is_contiguous());
let result: Vec<f32> = contiguous.to_vec();
assert_eq!(result, data);
}
#[test]
fn test_contiguous_from_transpose() {
let device = CpuDevice::new();
let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let tensor = Tensor::<CpuRuntime>::from_slice(&data, &[2, 3], &device);
let transposed = tensor.transpose(0, 1).unwrap();
assert!(!transposed.is_contiguous());
let contiguous = transposed.contiguous();
assert!(contiguous.is_contiguous());
assert_eq!(contiguous.shape(), &[3, 2]);
let result: Vec<f32> = contiguous.to_vec();
assert_eq!(result, [1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
}
#[test]
fn test_reshape() {
let device = CpuDevice::new();
let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let tensor = Tensor::<CpuRuntime>::from_slice(&data, &[2, 3], &device);
let reshaped = tensor.reshape(&[3, 2]).unwrap();
assert_eq!(reshaped.shape(), &[3, 2]);
assert!(reshaped.is_contiguous());
let result: Vec<f32> = reshaped.to_vec();
assert_eq!(result, data); }
#[test]
fn test_squeeze_unsqueeze() {
let device = CpuDevice::new();
let data = [1.0f32, 2.0, 3.0];
let tensor = Tensor::<CpuRuntime>::from_slice(&data, &[1, 3, 1], &device);
let squeezed = tensor.squeeze(None);
assert_eq!(squeezed.shape(), &[3]);
let unsqueezed = squeezed.unsqueeze(0).unwrap();
assert_eq!(unsqueezed.shape(), &[1, 3]);
}
#[test]
fn test_zeros() {
let device = CpuDevice::new();
let tensor = Tensor::<CpuRuntime>::zeros(&[2, 3], DType::F32, &device);
assert_eq!(tensor.shape(), &[2, 3]);
assert_eq!(tensor.dtype(), DType::F32);
assert!(tensor.is_contiguous());
let result: Vec<f32> = tensor.to_vec();
assert_eq!(result, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
}
#[test]
fn test_ones() {
let device = CpuDevice::new();
let tensor = Tensor::<CpuRuntime>::ones(&[2, 3], DType::F32, &device);
assert_eq!(tensor.shape(), &[2, 3]);
assert_eq!(tensor.dtype(), DType::F32);
assert!(tensor.is_contiguous());
let result: Vec<f32> = tensor.to_vec();
assert_eq!(result, [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]);
}
#[test]
fn test_full_scalar() {
let device = CpuDevice::new();
let tensor = Tensor::<CpuRuntime>::full_scalar(&[2, 2], DType::I32, 42.0, &device);
assert_eq!(tensor.shape(), &[2, 2]);
assert_eq!(tensor.dtype(), DType::I32);
let result: Vec<i32> = tensor.to_vec();
assert_eq!(result, [42, 42, 42, 42]);
}
#[test]
fn test_item_scalar() {
let device = CpuDevice::new();
let tensor = Tensor::<CpuRuntime>::from_slice(&[std::f32::consts::PI], &[], &device);
let val: f32 = tensor.item().unwrap();
assert!((val - std::f32::consts::PI).abs() < 1e-6);
let tensor = Tensor::<CpuRuntime>::from_slice(&[42.0f64], &[1], &device);
let val: f64 = tensor.item().unwrap();
assert!((val - 42.0).abs() < 1e-10);
let tensor = Tensor::<CpuRuntime>::from_slice(&[7i32], &[1, 1, 1], &device);
let val: i32 = tensor.item().unwrap();
assert_eq!(val, 7);
}
#[test]
fn test_item_error_on_multi_element() {
let device = CpuDevice::new();
let tensor = Tensor::<CpuRuntime>::from_slice(&[1.0f32, 2.0], &[2], &device);
let result: Result<f32> = tensor.item();
assert!(result.is_err());
}
}