#[cfg(feature = "vulkan")]
pub use fuel_vulkan_backend::VulkanStorageBytes as VulkanStorage;
#[cfg(feature = "cuda")]
pub use fuel_cuda_backend::CudaStorageBytes as CudaStorage;
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
pub use fuel_metal_backend::MetalStorageBytes as MetalStorage;
use fuel_cpu_backend::CpuStorageBytes;
use fuel_ir::storage::OutputView;
use fuel_ir::{DType, Result, SType};
use std::sync::Arc;
#[cfg(feature = "dlpack")]
pub mod dlpack_view;
#[derive(Debug)]
pub enum BackendStorage {
Cpu(CpuStorageBytes),
#[cfg(feature = "cuda")]
Cuda(CudaStorage),
#[cfg(feature = "vulkan")]
Vulkan(VulkanStorage),
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
Metal(MetalStorage),
}
#[derive(Debug)]
pub struct Storage {
pub inner: BackendStorage,
pub dtype: DType,
pub bundle: Option<Arc<[OutputView]>>,
pub stype: SType,
}
#[macro_export]
macro_rules! dispatch_storage {
($s:expr, $name:ident => $body:expr) => {
match $s {
$crate::BackendStorage::Cpu($name) => $body,
#[cfg(feature = "cuda")]
$crate::BackendStorage::Cuda($name) => $body,
#[cfg(feature = "vulkan")]
$crate::BackendStorage::Vulkan($name) => $body,
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
$crate::BackendStorage::Metal($name) => $body,
}
};
}
impl BackendStorage {
pub fn len_bytes(&self) -> usize {
dispatch_storage!(self, inner => inner.len_bytes())
}
}
impl Storage {
pub fn new(inner: BackendStorage, dtype: DType) -> Self {
Self {
inner,
dtype,
bundle: None,
stype: SType::default(),
}
}
pub fn new_bundled(
inner: BackendStorage,
dtype: DType,
bundle: Arc<[OutputView]>,
) -> Result<Self> {
Self::validate_bundle(dtype, &bundle)?;
Ok(Self {
inner,
dtype,
bundle: Some(bundle),
stype: SType::default(),
})
}
pub fn with_bundle(mut self, bundle: Arc<[OutputView]>) -> Result<Self> {
debug_assert!(
self.bundle.is_none(),
"Storage::with_bundle: bundle already attached",
);
Self::validate_bundle(self.dtype, &bundle)?;
self.bundle = Some(bundle);
Ok(self)
}
fn validate_bundle(primary_dtype: DType, bundle: &Arc<[OutputView]>) -> Result<()> {
if bundle.is_empty() {
return Err(fuel_ir::Error::Msg(
"Storage::with_bundle: bundle slice must be non-empty".into(),
)
.bt());
}
let slot0 = &bundle[0];
if slot0.dtype != primary_dtype {
return Err(fuel_ir::Error::Msg(format!(
"Storage::with_bundle: slot 0 dtype {:?} must match \
Storage's primary dtype {:?}",
slot0.dtype, primary_dtype,
))
.bt());
}
Ok(())
}
pub fn is_bundled(&self) -> bool {
self.bundle.is_some()
}
pub fn slot_count(&self) -> usize {
self.bundle.as_ref().map_or(1, |b| b.len())
}
pub fn bundle(&self) -> Option<&[OutputView]> {
self.bundle.as_deref()
}
pub fn slot_view(&self, idx: usize) -> Option<&OutputView> {
self.bundle.as_deref().and_then(|b| b.get(idx))
}
pub fn dtype(&self) -> DType {
self.dtype
}
pub fn len_bytes(&self) -> usize {
self.inner.len_bytes()
}
pub fn elem_count(&self) -> usize {
let bps = self.dtype.size_in_bytes();
self.len_bytes().checked_div(bps).unwrap_or(0)
}
pub fn with_stype(mut self, stype: SType) -> Self {
self.stype = stype;
self
}
pub fn stype(&self) -> &SType {
&self.stype
}
}
pub fn alloc_cpu_zeroed(dtype: DType, elem_count: usize) -> Result<Storage> {
let len_bytes = elem_count.saturating_mul(dtype.size_in_bytes());
Ok(Storage::new(
BackendStorage::Cpu(CpuStorageBytes::from_zero_bytes(len_bytes)),
dtype,
))
}
pub fn from_slice_cpu<T: bytemuck::Pod + fuel_ir::WithDType>(data: &[T]) -> Storage {
Storage::new(
BackendStorage::Cpu(CpuStorageBytes::from_slice(data)),
T::DTYPE,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpu_storage_basic_shape() {
let s = alloc_cpu_zeroed(DType::F32, 4).expect("alloc");
assert_eq!(s.dtype(), DType::F32);
assert_eq!(s.len_bytes(), 16);
assert_eq!(s.elem_count(), 4);
}
#[test]
fn dispatch_macro_routes_to_variant() {
let bs = BackendStorage::Cpu(CpuStorageBytes::from_zero_bytes(8));
let n = dispatch_storage!(&bs, inner => inner.len_bytes());
assert_eq!(n, 8);
}
#[test]
fn backend_storage_len_bytes_dispatches() {
let bs = BackendStorage::Cpu(CpuStorageBytes::from_zero_bytes(32));
assert_eq!(bs.len_bytes(), 32);
}
#[test]
fn zero_element_allocation() {
let s = alloc_cpu_zeroed(DType::F64, 0).expect("alloc");
assert_eq!(s.dtype(), DType::F64);
assert_eq!(s.len_bytes(), 0);
assert_eq!(s.elem_count(), 0);
}
#[test]
fn from_slice_cpu_round_trip() {
let data = vec![1.0_f32, 2.0, 3.0, 4.0];
let s = from_slice_cpu(&data);
assert_eq!(s.dtype(), DType::F32);
assert_eq!(s.elem_count(), 4);
assert_eq!(s.len_bytes(), 16);
}
#[test]
fn plain_storage_has_empty_stype() {
let s = alloc_cpu_zeroed(DType::F32, 4).expect("alloc");
assert!(
s.stype().is_plain(),
"default Storage must carry a plain SType"
);
assert_eq!(s.stype().layers().len(), 0);
}
#[test]
fn storage_with_stype_round_trips() {
use fuel_ir::{Encoding, GgmlDType, SType};
let s = alloc_cpu_zeroed(DType::F32, 8)
.expect("alloc")
.with_stype(SType::from_layer(Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4_0,
}));
assert!(!s.stype().is_plain());
assert_eq!(s.dtype(), DType::F32); assert_eq!(s.len_bytes(), 32); }
#[test]
fn cpu_storage_alloc_alias() {
let a = fuel_cpu_backend::CpuStorageBytes::alloc(24);
let b = fuel_cpu_backend::CpuStorageBytes::from_zero_bytes(24);
assert_eq!(a.len_bytes(), b.len_bytes());
assert_eq!(a.bytes(), b.bytes());
}
}