use crate::block::Block;
use crate::error::{Error, Result};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TensorDtype {
F32,
F16,
F64,
I8,
I32,
I64,
U8,
U32,
Bool,
}
impl TensorDtype {
#[inline]
pub fn size_bytes(&self) -> usize {
match self {
TensorDtype::F32 => 4,
TensorDtype::F16 => 2,
TensorDtype::F64 => 8,
TensorDtype::I8 => 1,
TensorDtype::I32 => 4,
TensorDtype::I64 => 8,
TensorDtype::U8 => 1,
TensorDtype::U32 => 4,
TensorDtype::Bool => 1,
}
}
#[inline]
pub fn name(&self) -> &'static str {
match self {
TensorDtype::F32 => "float32",
TensorDtype::F16 => "float16",
TensorDtype::F64 => "float64",
TensorDtype::I8 => "int8",
TensorDtype::I32 => "int32",
TensorDtype::I64 => "int64",
TensorDtype::U8 => "uint8",
TensorDtype::U32 => "uint32",
TensorDtype::Bool => "bool",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TensorShape {
dims: Vec<usize>,
}
impl TensorShape {
pub fn new(dims: Vec<usize>) -> Self {
Self { dims }
}
pub fn scalar() -> Self {
Self { dims: vec![] }
}
#[inline]
pub fn dims(&self) -> &[usize] {
&self.dims
}
#[inline]
pub fn rank(&self) -> usize {
self.dims.len()
}
#[inline]
pub fn element_count(&self) -> usize {
if self.dims.is_empty() {
1
} else {
self.dims.iter().product()
}
}
#[inline]
pub fn is_scalar(&self) -> bool {
self.dims.is_empty()
}
#[inline]
pub fn is_vector(&self) -> bool {
self.dims.len() == 1
}
#[inline]
pub fn is_matrix(&self) -> bool {
self.dims.len() == 2
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TensorMetadata {
pub shape: TensorShape,
pub dtype: TensorDtype,
pub name: Option<String>,
pub metadata: std::collections::BTreeMap<String, String>,
}
impl TensorMetadata {
pub fn new(shape: TensorShape, dtype: TensorDtype) -> Self {
Self {
shape,
dtype,
name: None,
metadata: std::collections::BTreeMap::new(),
}
}
pub fn with_name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
pub fn with_metadata(mut self, key: String, value: String) -> Self {
self.metadata.insert(key, value);
self
}
pub fn expected_size(&self) -> usize {
self.shape.element_count() * self.dtype.size_bytes()
}
}
#[derive(Debug, Clone)]
pub struct TensorBlock {
block: Block,
metadata: TensorMetadata,
}
impl TensorBlock {
pub fn new(data: Bytes, shape: TensorShape, dtype: TensorDtype) -> Result<Self> {
let metadata = TensorMetadata::new(shape, dtype);
let expected_size = metadata.expected_size();
if data.len() != expected_size {
return Err(Error::InvalidData(format!(
"Tensor data size mismatch: expected {} bytes, got {}",
expected_size,
data.len()
)));
}
let block = Block::new(data)?;
Ok(Self { block, metadata })
}
pub fn with_metadata(data: Bytes, metadata: TensorMetadata) -> Result<Self> {
let expected_size = metadata.expected_size();
if data.len() != expected_size {
return Err(Error::InvalidData(format!(
"Tensor data size mismatch: expected {} bytes, got {}",
expected_size,
data.len()
)));
}
let block = Block::new(data)?;
Ok(Self { block, metadata })
}
pub fn block(&self) -> &Block {
&self.block
}
pub fn metadata(&self) -> &TensorMetadata {
&self.metadata
}
pub fn shape(&self) -> &TensorShape {
&self.metadata.shape
}
pub fn dtype(&self) -> TensorDtype {
self.metadata.dtype
}
pub fn element_count(&self) -> usize {
self.metadata.shape.element_count()
}
pub fn cid(&self) -> &crate::cid::Cid {
self.block.cid()
}
pub fn data(&self) -> &Bytes {
self.block.data()
}
pub fn into_parts(self) -> (Block, TensorMetadata) {
(self.block, self.metadata)
}
pub fn verify(&self) -> Result<bool> {
self.block.verify()
}
pub fn reshape(&self, new_shape: TensorShape) -> Result<Self> {
if new_shape.element_count() != self.element_count() {
return Err(Error::InvalidInput(format!(
"Cannot reshape tensor with {} elements to shape with {} elements",
self.element_count(),
new_shape.element_count()
)));
}
let new_metadata = TensorMetadata {
shape: new_shape,
dtype: self.metadata.dtype,
name: self.metadata.name.clone(),
metadata: self.metadata.metadata.clone(),
};
Ok(Self {
block: self.block.clone(),
metadata: new_metadata,
})
}
pub fn size_bytes(&self) -> usize {
self.data().len()
}
pub fn is_scalar(&self) -> bool {
self.shape().is_scalar()
}
pub fn is_vector(&self) -> bool {
self.shape().is_vector()
}
pub fn is_matrix(&self) -> bool {
self.shape().is_matrix()
}
}
impl TensorBlock {
pub fn from_f32_slice(data: &[f32], shape: TensorShape) -> Result<Self> {
if data.len() != shape.element_count() {
return Err(Error::InvalidInput(format!(
"Data length {} doesn't match shape element count {}",
data.len(),
shape.element_count()
)));
}
let bytes: Vec<u8> = data.iter().flat_map(|&f| f.to_le_bytes()).collect();
Self::new(Bytes::from(bytes), shape, TensorDtype::F32)
}
pub fn from_f64_slice(data: &[f64], shape: TensorShape) -> Result<Self> {
if data.len() != shape.element_count() {
return Err(Error::InvalidInput(format!(
"Data length {} doesn't match shape element count {}",
data.len(),
shape.element_count()
)));
}
let bytes: Vec<u8> = data.iter().flat_map(|&f| f.to_le_bytes()).collect();
Self::new(Bytes::from(bytes), shape, TensorDtype::F64)
}
pub fn from_i32_slice(data: &[i32], shape: TensorShape) -> Result<Self> {
if data.len() != shape.element_count() {
return Err(Error::InvalidInput(format!(
"Data length {} doesn't match shape element count {}",
data.len(),
shape.element_count()
)));
}
let bytes: Vec<u8> = data.iter().flat_map(|&i| i.to_le_bytes()).collect();
Self::new(Bytes::from(bytes), shape, TensorDtype::I32)
}
pub fn from_i64_slice(data: &[i64], shape: TensorShape) -> Result<Self> {
if data.len() != shape.element_count() {
return Err(Error::InvalidInput(format!(
"Data length {} doesn't match shape element count {}",
data.len(),
shape.element_count()
)));
}
let bytes: Vec<u8> = data.iter().flat_map(|&i| i.to_le_bytes()).collect();
Self::new(Bytes::from(bytes), shape, TensorDtype::I64)
}
pub fn from_u8_slice(data: &[u8], shape: TensorShape) -> Result<Self> {
if data.len() != shape.element_count() {
return Err(Error::InvalidInput(format!(
"Data length {} doesn't match shape element count {}",
data.len(),
shape.element_count()
)));
}
Self::new(Bytes::copy_from_slice(data), shape, TensorDtype::U8)
}
pub fn to_f32_vec(&self) -> Result<Vec<f32>> {
if self.dtype() != TensorDtype::F32 {
return Err(Error::InvalidInput(format!(
"Cannot convert {} tensor to f32",
self.dtype().name()
)));
}
let data = self.data();
let mut result = Vec::with_capacity(self.element_count());
for chunk in data.chunks_exact(4) {
let bytes: [u8; 4] = chunk
.try_into()
.expect("chunks_exact(4) guarantees exactly 4 bytes");
result.push(f32::from_le_bytes(bytes));
}
Ok(result)
}
pub fn to_f64_vec(&self) -> Result<Vec<f64>> {
if self.dtype() != TensorDtype::F64 {
return Err(Error::InvalidInput(format!(
"Cannot convert {} tensor to f64",
self.dtype().name()
)));
}
let data = self.data();
let mut result = Vec::with_capacity(self.element_count());
for chunk in data.chunks_exact(8) {
let bytes: [u8; 8] = chunk
.try_into()
.expect("chunks_exact(8) guarantees exactly 8 bytes");
result.push(f64::from_le_bytes(bytes));
}
Ok(result)
}
pub fn to_i32_vec(&self) -> Result<Vec<i32>> {
if self.dtype() != TensorDtype::I32 {
return Err(Error::InvalidInput(format!(
"Cannot convert {} tensor to i32",
self.dtype().name()
)));
}
let data = self.data();
let mut result = Vec::with_capacity(self.element_count());
for chunk in data.chunks_exact(4) {
let bytes: [u8; 4] = chunk
.try_into()
.expect("chunks_exact(4) guarantees exactly 4 bytes");
result.push(i32::from_le_bytes(bytes));
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tensor_dtype_sizes() {
assert_eq!(TensorDtype::F32.size_bytes(), 4);
assert_eq!(TensorDtype::F16.size_bytes(), 2);
assert_eq!(TensorDtype::I8.size_bytes(), 1);
assert_eq!(TensorDtype::I32.size_bytes(), 4);
}
#[test]
fn test_tensor_shape() {
let shape = TensorShape::new(vec![2, 3, 4]);
assert_eq!(shape.rank(), 3);
assert_eq!(shape.element_count(), 24);
assert!(!shape.is_scalar());
assert!(!shape.is_vector());
assert!(!shape.is_matrix());
let scalar = TensorShape::scalar();
assert!(scalar.is_scalar());
assert_eq!(scalar.element_count(), 1);
}
#[test]
fn test_tensor_block_creation() {
let shape = TensorShape::new(vec![2, 2]);
let data: Vec<u8> = [1.0f32, 2.0, 3.0, 4.0]
.iter()
.flat_map(|f| f.to_le_bytes())
.collect();
let tensor = TensorBlock::new(Bytes::from(data), shape, TensorDtype::F32).unwrap();
assert_eq!(tensor.element_count(), 4);
assert_eq!(tensor.dtype(), TensorDtype::F32);
assert_eq!(tensor.shape().dims(), &[2, 2]);
}
#[test]
fn test_tensor_size_validation() {
let shape = TensorShape::new(vec![2, 2]);
let data: Vec<u8> = [1.0f32, 2.0, 3.0]
.iter()
.flat_map(|f| f.to_le_bytes())
.collect();
let result = TensorBlock::new(Bytes::from(data), shape, TensorDtype::F32);
assert!(result.is_err());
}
#[test]
fn test_tensor_metadata() {
let shape = TensorShape::new(vec![10, 20]);
let metadata = TensorMetadata::new(shape, TensorDtype::F32)
.with_name("layer1.weight".to_string())
.with_metadata("requires_grad".to_string(), "true".to_string());
assert_eq!(metadata.name, Some("layer1.weight".to_string()));
assert_eq!(metadata.expected_size(), 10 * 20 * 4); }
#[test]
fn test_tensor_from_f32_slice() {
let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let shape = TensorShape::new(vec![2, 3]);
let tensor = TensorBlock::from_f32_slice(&data, shape).unwrap();
assert_eq!(tensor.element_count(), 6);
assert_eq!(tensor.dtype(), TensorDtype::F32);
let recovered = tensor.to_f32_vec().unwrap();
assert_eq!(recovered, data);
}
#[test]
fn test_tensor_from_i32_slice() {
let data = vec![10i32, 20, 30, 40];
let shape = TensorShape::new(vec![2, 2]);
let tensor = TensorBlock::from_i32_slice(&data, shape).unwrap();
assert_eq!(tensor.element_count(), 4);
let recovered = tensor.to_i32_vec().unwrap();
assert_eq!(recovered, data);
}
#[test]
fn test_tensor_reshape() {
let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let shape = TensorShape::new(vec![2, 3]);
let tensor = TensorBlock::from_f32_slice(&data, shape).unwrap();
let reshaped = tensor.reshape(TensorShape::new(vec![3, 2])).unwrap();
assert_eq!(reshaped.shape().dims(), &[3, 2]);
assert_eq!(reshaped.element_count(), 6);
let recovered = reshaped.to_f32_vec().unwrap();
assert_eq!(recovered, data);
}
#[test]
fn test_tensor_reshape_invalid() {
let data = vec![1.0f32, 2.0, 3.0, 4.0];
let shape = TensorShape::new(vec![2, 2]);
let tensor = TensorBlock::from_f32_slice(&data, shape).unwrap();
let result = tensor.reshape(TensorShape::new(vec![3, 2])); assert!(result.is_err());
}
#[test]
fn test_tensor_type_checks() {
let data = vec![1.0f32, 2.0];
let tensor = TensorBlock::from_f32_slice(&data, TensorShape::new(vec![2])).unwrap();
assert!(tensor.is_vector());
assert!(!tensor.is_matrix());
assert!(!tensor.is_scalar());
let matrix = TensorBlock::from_f32_slice(&data, TensorShape::new(vec![1, 2])).unwrap();
assert!(matrix.is_matrix());
}
#[test]
fn test_tensor_to_vec_wrong_dtype() {
let data = vec![1i32, 2, 3];
let tensor = TensorBlock::from_i32_slice(&data, TensorShape::new(vec![3])).unwrap();
let result = tensor.to_f32_vec();
assert!(result.is_err());
}
}