#[cfg(target_has_atomic = "ptr")]
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
#[cfg(not(target_has_atomic = "ptr"))]
use portable_atomic_util::Arc;
use crate::tensor::{BoolStore, DType, TensorMetadata, data::TensorData, element::Element};
use crate::{bytes::Bytes, tensor::Shape};
use half::{bf16, f16};
use super::layout::Layout;
#[derive(Clone)]
pub struct HostTensor {
data: Arc<Bytes>,
layout: Layout,
dtype: DType,
}
impl fmt::Debug for HostTensor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FlexTensor")
.field("shape", self.layout.shape())
.field("dtype", &self.dtype)
.field("contiguous", &self.layout.is_contiguous())
.field("unique", &self.is_unique())
.finish()
}
}
impl HostTensor {
pub fn new(data: Bytes, layout: Layout, dtype: DType) -> Self {
Self {
data: Arc::new(data),
layout,
dtype,
}
}
pub fn from_data(data: TensorData) -> Self {
let shape = data.shape.clone();
let layout = Layout::contiguous(shape);
let dtype = data.dtype;
Self {
data: Arc::new(data.bytes),
layout,
dtype,
}
}
pub fn into_data(self) -> TensorData {
if self.layout.is_contiguous() && self.layout.start_offset() == 0 {
let expected_bytes = self.layout.num_elements() * dtype_size(self.dtype);
assert!(
expected_bytes <= self.data.len(),
"into_data: buffer ({} bytes) too small for {} elements of {:?}",
self.data.len(),
self.layout.num_elements(),
self.dtype
);
if self.data.len() == expected_bytes {
match Arc::try_unwrap(self.data) {
Ok(bytes) => TensorData {
bytes,
shape: self.layout.shape().clone(),
dtype: self.dtype,
},
Err(arc) => {
let bytes = Bytes::from_bytes_vec((*arc)[..expected_bytes].to_vec());
TensorData {
bytes,
shape: self.layout.shape().clone(),
dtype: self.dtype,
}
}
}
} else {
let bytes = Bytes::from_bytes_vec(self.data[..expected_bytes].to_vec());
TensorData {
bytes,
shape: self.layout.shape().clone(),
dtype: self.dtype,
}
}
} else {
self.to_contiguous().into_data()
}
}
#[inline]
pub fn is_unique(&self) -> bool {
Arc::strong_count(&self.data) == 1
}
pub fn layout(&self) -> &Layout {
&self.layout
}
pub fn with_layout(self, layout: Layout) -> Self {
Self {
data: self.data,
layout,
dtype: self.dtype,
}
}
pub fn dtype(&self) -> DType {
self.dtype
}
pub fn is_contiguous(&self) -> bool {
self.layout.is_contiguous()
}
pub fn bytes(&self) -> &[u8] {
&self.data
}
pub fn data_arc(&self) -> Arc<Bytes> {
Arc::clone(&self.data)
}
pub fn from_arc(data: Arc<Bytes>, layout: Layout, dtype: DType) -> Self {
Self {
data,
layout,
dtype,
}
}
pub fn storage<E: Element + bytemuck::Pod>(&self) -> &[E] {
assert!(
E::dtype() == self.dtype
|| (matches!(
self.dtype,
DType::Bool(BoolStore::Native | BoolStore::U8)
) && E::dtype() == DType::U8),
"storage: dtype mismatch (expected {:?}, got {:?})",
self.dtype,
E::dtype()
);
bytemuck::cast_slice(&self.data)
}
pub fn storage_mut<E: Element + bytemuck::Pod>(&mut self) -> &mut [E] {
assert!(
E::dtype() == self.dtype
|| (matches!(
self.dtype,
DType::Bool(BoolStore::Native | BoolStore::U8)
) && E::dtype() == DType::U8),
"storage_mut: dtype mismatch (expected {:?}, got {:?})",
self.dtype,
E::dtype()
);
let bytes = Arc::make_mut(&mut self.data);
bytemuck::cast_slice_mut(bytes)
}
pub fn try_storage_mut<E: Element + bytemuck::Pod>(&mut self) -> Option<&mut [E]> {
assert!(
E::dtype() == self.dtype
|| (matches!(
self.dtype,
DType::Bool(BoolStore::Native | BoolStore::U8)
) && E::dtype() == DType::U8),
"try_storage_mut: dtype mismatch (expected {:?}, got {:?})",
self.dtype,
E::dtype()
);
if self.is_unique() {
let bytes = Arc::get_mut(&mut self.data)?;
Some(bytemuck::cast_slice_mut(bytes))
} else {
None
}
}
pub fn as_slice<E: Element + bytemuck::Pod>(&self) -> Option<&[E]> {
if E::dtype() != self.dtype {
return None;
}
let storage: &[E] = self.storage();
self.layout
.contiguous_offsets()
.map(|(start, end)| &storage[start..end])
}
pub fn empty(shape: Shape, dtype: DType) -> Self {
let num_elements = shape.num_elements();
let elem_size = dtype_size(dtype);
let bytes = Bytes::from_bytes_vec(alloc::vec![0u8; num_elements * elem_size]);
let layout = Layout::contiguous(shape);
Self {
data: Arc::new(bytes),
layout,
dtype,
}
}
pub fn zeros(shape: Shape, dtype: DType) -> Self {
Self::empty(shape, dtype)
}
pub fn filled_typed<E: bytemuck::Pod + Send + Sync>(
shape: Shape,
dtype: DType,
value: E,
) -> Self {
assert_eq!(
dtype_size(dtype),
core::mem::size_of::<E>(),
"filled_typed: dtype size mismatch"
);
let n = shape.num_elements();
let data = alloc::vec![value; n];
let bytes = Bytes::from_elems(data);
Self {
data: Arc::new(bytes),
layout: Layout::contiguous(shape),
dtype,
}
}
pub fn to_contiguous(&self) -> Self {
if self.is_contiguous()
&& self.layout.start_offset() == 0
&& self.data.len() == self.layout.num_elements() * dtype_size(self.dtype)
{
return self.clone();
}
match self.dtype {
DType::F64 => self.copy_contiguous::<f64>(),
DType::F32 => self.copy_contiguous::<f32>(),
DType::F16 => self.copy_contiguous::<f16>(),
DType::BF16 => self.copy_contiguous::<bf16>(),
DType::I64 => self.copy_contiguous::<i64>(),
DType::I32 => self.copy_contiguous::<i32>(),
DType::I16 => self.copy_contiguous::<i16>(),
DType::I8 => self.copy_contiguous::<i8>(),
DType::U64 => self.copy_contiguous::<u64>(),
DType::U32 => self.copy_contiguous::<u32>(),
DType::U16 => self.copy_contiguous::<u16>(),
DType::U8 => self.copy_contiguous::<u8>(),
DType::Bool(BoolStore::Native | BoolStore::U8) => {
self.copy_contiguous::<u8>()
}
DType::Bool(BoolStore::U32) => {
panic!("ruda-tensor-host: Bool(U32) storage is not yet supported")
}
_ => panic!("Unsupported dtype for contiguous copy: {:?}", self.dtype),
}
}
fn copy_contiguous<E: Element + bytemuck::Pod>(&self) -> Self {
let src: &[E] = bytemuck::cast_slice(&self.data);
let n = self.layout.num_elements();
let mut dst = Vec::with_capacity(n);
let collapsed = collapse_for_copy(self.layout.shape(), self.layout.strides());
let (shape, strides) = collapsed.as_slices();
let offset = self.layout.start_offset() as isize;
let all_positive = strides.iter().all(|&s| s >= 0);
if shape.len() <= 1 && all_positive {
let collapsed_numel = if shape.is_empty() { 1 } else { shape[0] };
debug_assert_eq!(n, collapsed_numel);
if shape.is_empty() {
if n > 0 {
dst.push(src[offset as usize]);
}
} else {
let len = shape[0];
let stride = strides[0];
if stride == 1 {
dst.extend_from_slice(&src[offset as usize..offset as usize + len]);
} else {
for i in 0..len {
let idx = (offset + i as isize * stride) as usize;
dst.push(src[idx]);
}
}
}
} else if shape.len() == 2 && all_positive {
debug_assert_eq!(shape[0] * shape[1], n, "2D strides must cover all elements");
dst.resize(n, <E as bytemuck::Zeroable>::zeroed());
copy_2d_tiled(
&mut dst, src, offset, shape[0], shape[1], strides[0], strides[1],
);
} else {
for idx in super::strided_index::StridedIter::new(&self.layout) {
dst.push(src[idx]);
}
}
let bytes = Bytes::from_elems(dst);
let layout = Layout::contiguous(self.layout.shape().clone());
Self {
data: Arc::new(bytes),
layout,
dtype: self.dtype,
}
}
pub fn reshape(&self, new_shape: Shape) -> Self {
assert_eq!(
self.layout.num_elements(),
new_shape.num_elements(),
"reshape must preserve total elements"
);
if let Some(new_layout) = self.layout.reshape(new_shape.clone()) {
Self {
data: Arc::clone(&self.data),
layout: new_layout,
dtype: self.dtype,
}
} else {
self.to_contiguous().reshape(new_shape)
}
}
pub fn transpose(&self, dim1: usize, dim2: usize) -> Self {
Self {
data: Arc::clone(&self.data),
layout: self.layout.transpose(dim1, dim2),
dtype: self.dtype,
}
}
pub fn narrow(&self, dim: usize, start: usize, len: usize) -> Self {
Self {
data: Arc::clone(&self.data),
layout: self.layout.narrow(dim, start, len),
dtype: self.dtype,
}
}
pub fn permute(&self, axes: &[usize]) -> Self {
Self {
data: Arc::clone(&self.data),
layout: self.layout.permute(axes),
dtype: self.dtype,
}
}
}
impl TensorMetadata for HostTensor {
fn dtype(&self) -> DType {
self.dtype
}
fn shape(&self) -> Shape {
self.layout.shape().clone()
}
fn rank(&self) -> usize {
self.layout.num_dims()
}
}
const COLLAPSE_MAX_RANK: usize = 8;
#[derive(Debug, Clone, Copy)]
struct CollapsedLayout {
ndim: usize,
shape: [usize; COLLAPSE_MAX_RANK],
strides: [isize; COLLAPSE_MAX_RANK],
}
impl CollapsedLayout {
#[inline]
fn as_slices(&self) -> (&[usize], &[isize]) {
(&self.shape[..self.ndim], &self.strides[..self.ndim])
}
}
fn collapse_for_copy(shape: &[usize], strides: &[isize]) -> CollapsedLayout {
let mut out = CollapsedLayout {
ndim: 0,
shape: [0; COLLAPSE_MAX_RANK],
strides: [0; COLLAPSE_MAX_RANK],
};
if shape.len() > COLLAPSE_MAX_RANK {
out.ndim = shape.len().min(COLLAPSE_MAX_RANK);
return out;
}
for (&s, &st) in shape.iter().zip(strides.iter()) {
if s == 1 {
continue;
}
let merge = out.ndim > 0
&& (s as isize)
.checked_mul(st)
.is_some_and(|run| out.strides[out.ndim - 1] == run);
if merge {
out.shape[out.ndim - 1] *= s;
out.strides[out.ndim - 1] = st;
} else {
out.shape[out.ndim] = s;
out.strides[out.ndim] = st;
out.ndim += 1;
}
}
out
}
#[inline]
fn copy_2d_tiled<E: Copy>(
dst: &mut [E],
src: &[E],
offset: isize,
rows: usize,
cols: usize,
row_stride: isize,
col_stride: isize,
) {
const TILE: usize = 16;
if row_stride <= col_stride {
for col_tile in (0..cols).step_by(TILE) {
let col_end = (col_tile + TILE).min(cols);
for row_tile in (0..rows).step_by(TILE) {
let row_end = (row_tile + TILE).min(rows);
for col in col_tile..col_end {
let col_base = offset + col as isize * col_stride;
for row in row_tile..row_end {
let idx = (col_base + row as isize * row_stride) as usize;
unsafe {
*dst.get_unchecked_mut(row * cols + col) = src[idx];
}
}
}
}
}
} else {
for row_tile in (0..rows).step_by(TILE) {
let row_end = (row_tile + TILE).min(rows);
for col_tile in (0..cols).step_by(TILE) {
let col_end = (col_tile + TILE).min(cols);
for row in row_tile..row_end {
let row_base =
offset + row as isize * row_stride + col_tile as isize * col_stride;
let dst_base = row * cols + col_tile;
for c in 0..(col_end - col_tile) {
let idx = (row_base + c as isize * col_stride) as usize;
unsafe {
*dst.get_unchecked_mut(dst_base + c) = src[idx];
}
}
}
}
}
}
}
pub fn dtype_size(dtype: DType) -> usize {
let size = dtype.size();
assert!(
size > 0,
"ruda-tensor-host: dtype {:?} has zero-byte element size (sub-byte packed \
quantization is not yet supported)",
dtype
);
size
}
#[cfg(test)]
mod tests;