use alloc::format;
use alloc::string::String;
#[cfg(target_has_atomic = "ptr")]
use alloc::sync::Arc;
#[cfg(not(target_has_atomic = "ptr"))]
use alloc::rc::Rc as Arc;
use burn_std::{Bytes, DType, Shape};
use crate::base::Error;
#[cfg(target_has_atomic = "ptr")]
type Provider = Arc<dyn Fn() -> Result<Bytes, Error> + Send + Sync>;
#[cfg(not(target_has_atomic = "ptr"))]
type Provider = Arc<dyn Fn() -> Result<Bytes, Error>>;
#[derive(Clone)]
enum Source {
Resident(Bytes),
Deferred { len: usize, provider: Provider },
}
impl Source {
fn len(&self) -> usize {
match self {
Self::Resident(bytes) => bytes.len(),
Self::Deferred { len, .. } => *len,
}
}
fn to_bytes(&self) -> Result<Bytes, Error> {
match self {
Self::Resident(bytes) => Ok(bytes.clone()),
Self::Deferred { len, provider } => Self::checked(*len, provider()?),
}
}
fn into_bytes(self) -> Result<Bytes, Error> {
match self {
Self::Resident(bytes) => Ok(bytes),
Self::Deferred { len, provider } => Self::checked(len, provider()?),
}
}
fn checked(expected: usize, bytes: Bytes) -> Result<Bytes, Error> {
if bytes.len() != expected {
return Err(Error::TensorBytesSizeMismatch(format!(
"deferred source has inconsistent length (expected {}, got {})",
expected,
bytes.len()
)));
}
Ok(bytes)
}
}
#[derive(Clone)]
pub struct Tensor {
pub name: String,
pub dtype: DType,
pub shape: Shape,
pub param_id: Option<u64>,
source: Source,
}
impl Tensor {
pub fn new(
name: String,
dtype: DType,
shape: impl Into<Shape>,
param_id: Option<u64>,
bytes: Bytes,
) -> Self {
Self {
name,
dtype,
shape: shape.into(),
param_id,
source: Source::Resident(bytes),
}
}
#[cfg(target_has_atomic = "ptr")]
pub fn deferred(
name: String,
dtype: DType,
shape: impl Into<Shape>,
param_id: Option<u64>,
byte_len: usize,
provider: impl Fn() -> Result<Bytes, Error> + Send + Sync + 'static,
) -> Self {
Self::with_provider(name, dtype, shape, param_id, byte_len, Arc::new(provider))
}
#[cfg(not(target_has_atomic = "ptr"))]
pub fn deferred(
name: String,
dtype: DType,
shape: impl Into<Shape>,
param_id: Option<u64>,
byte_len: usize,
provider: impl Fn() -> Result<Bytes, Error> + 'static,
) -> Self {
Self::with_provider(name, dtype, shape, param_id, byte_len, Arc::new(provider))
}
fn with_provider(
name: String,
dtype: DType,
shape: impl Into<Shape>,
param_id: Option<u64>,
byte_len: usize,
provider: Provider,
) -> Self {
Self {
name,
dtype,
shape: shape.into(),
param_id,
source: Source::Deferred {
len: byte_len,
provider,
},
}
}
pub fn byte_len(&self) -> usize {
self.source.len()
}
pub fn to_bytes(&self) -> Result<Bytes, Error> {
self.source.to_bytes()
}
pub fn into_bytes(self) -> Result<Bytes, Error> {
self.source.into_bytes()
}
pub fn into_parts(self) -> Result<(String, DType, Shape, Option<u64>, Bytes), Error> {
let Self {
name,
dtype,
shape,
param_id,
source,
} = self;
Ok((name, dtype, shape, param_id, source.into_bytes()?))
}
}
impl core::fmt::Debug for Tensor {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Tensor")
.field("name", &self.name)
.field("dtype", &self.dtype)
.field("shape", &self.shape)
.field("param_id", &self.param_id)
.field("byte_len", &self.byte_len())
.finish()
}
}
#[cfg(all(test, target_has_atomic = "ptr"))]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
use core::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn to_bytes_leaves_a_resident_tensor_intact() {
let tensor = Tensor::new(
"w".to_string(),
DType::U8,
vec![4],
None,
Bytes::from_bytes_vec(vec![1, 2, 3, 4]),
);
assert_eq!(&tensor.to_bytes().unwrap()[..], &[1, 2, 3, 4]);
assert_eq!(&tensor.into_bytes().unwrap()[..], &[1, 2, 3, 4]);
}
#[test]
fn to_bytes_reruns_a_deferred_provider_on_every_call() {
let calls = Arc::new(AtomicUsize::new(0));
let counter = calls.clone();
let tensor = Tensor::deferred("w".to_string(), DType::U8, vec![2], None, 2, move || {
counter.fetch_add(1, Ordering::Relaxed);
Ok(Bytes::from_bytes_vec(vec![7, 8]))
});
assert_eq!(&tensor.to_bytes().unwrap()[..], &[7, 8]);
assert_eq!(&tensor.to_bytes().unwrap()[..], &[7, 8]);
assert_eq!(calls.load(Ordering::Relaxed), 2);
assert_eq!(tensor.byte_len(), 2);
assert_eq!(&tensor.into_bytes().unwrap()[..], &[7, 8]);
assert_eq!(calls.load(Ordering::Relaxed), 3);
}
#[test]
fn to_bytes_surfaces_a_provider_failure_unannotated() {
let tensor = Tensor::deferred("w".to_string(), DType::U8, vec![1], None, 1, || {
Err(Error::IoError("device read failed".to_string()))
});
let err = tensor.to_bytes().unwrap_err();
assert!(
matches!(&err, Error::IoError(m) if m == "device read failed"),
"expected the provider's own error, got {err:?}"
);
}
#[test]
fn into_parts_yields_metadata_and_bytes_together() {
let tensor = Tensor::deferred(
"layer.w".to_string(),
DType::U8,
vec![2],
Some(7),
2,
|| Ok(Bytes::from_bytes_vec(vec![7, 8])),
);
let (name, dtype, shape, param_id, bytes) = tensor.into_parts().unwrap();
assert_eq!(name, "layer.w");
assert_eq!(dtype, DType::U8);
assert_eq!(shape.to_vec(), vec![2]);
assert_eq!(param_id, Some(7));
assert_eq!(&bytes[..], &[7, 8]);
}
#[test]
fn tensor_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Tensor>();
}
}