use crate::dyn_backend::{DynBackendDevice, DynBackendStorage};
use crate::inplace_op::{InplaceOp1, InplaceOp2, InplaceOp3};
use fuel_ir::op::{self, BinaryOp, CmpOp, ReduceOp};
use fuel_ir::storage::{OutputView, OutputViewSpec, compose_bundle};
use fuel_ir::stype::SType;
use fuel_ir::{DType, Error, HostBuffer, Layout, Result, Scalar, Shape, conv};
use std::sync::Arc;
pub fn allocate_bundled_storage(
device: &dyn DynBackendDevice,
specs: &[OutputViewSpec],
) -> Result<Storage> {
let (total_bytes, views) = compose_bundle(specs)?;
let primary_dtype = specs[0].dtype;
let primary_size = primary_dtype.size_in_bytes().max(1);
let flat_elems = total_bytes.div_ceil(primary_size).max(1);
let inner = device.zeros_impl_dyn(&Shape::from_dims(&[flat_elems]), primary_dtype)?;
Storage::from_dyn_bundled(inner, Arc::from(views.into_boxed_slice()))
}
#[derive(Debug)]
pub struct Storage {
pub(crate) inner: Box<dyn DynBackendStorage>,
pub(crate) bundle: Option<Arc<[OutputView]>>,
pub(crate) stype: SType,
}
impl Storage {
pub fn new<B: DynBackendStorage + 'static>(b: B) -> Self {
Storage {
inner: Box::new(b),
bundle: None,
stype: SType::default(),
}
}
pub fn from_dyn(b: Box<dyn DynBackendStorage>) -> Self {
Storage {
inner: b,
bundle: None,
stype: SType::default(),
}
}
pub fn from_dyn_bundled(
b: Box<dyn DynBackendStorage>,
bundle: Arc<[OutputView]>,
) -> Result<Self> {
if bundle.is_empty() {
return Err(Error::Msg(
"Storage::from_dyn_bundled: bundle slice must be non-empty".into(),
)
.bt());
}
let primary = bundle[0].dtype;
let inner_dtype = b.dtype_dyn();
if primary != inner_dtype {
return Err(Error::Msg(format!(
"Storage::from_dyn_bundled: slot 0 dtype {primary:?} must \
match inner backend dtype {inner_dtype:?}",
))
.bt());
}
Ok(Storage {
inner: b,
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",
);
if bundle.is_empty() {
return Err(
Error::Msg("Storage::with_bundle: bundle slice must be non-empty".into()).bt(),
);
}
let primary = bundle[0].dtype;
let inner_dtype = self.inner.dtype_dyn();
if primary != inner_dtype {
return Err(Error::Msg(format!(
"Storage::with_bundle: slot 0 dtype {primary:?} must \
match inner backend dtype {inner_dtype:?}",
))
.bt());
}
self.bundle = Some(bundle);
Ok(self)
}
pub fn is_bundled(&self) -> bool {
self.bundle.is_some()
}
pub fn slot_count(&self) -> usize {
match &self.bundle {
Some(b) => b.len(),
None => 1,
}
}
pub fn bundle(&self) -> Option<&[OutputView]> {
self.bundle.as_deref()
}
pub fn bundle_arc(&self) -> Option<Arc<[OutputView]>> {
self.bundle.clone()
}
pub fn slot_view(&self, idx: usize) -> Option<&OutputView> {
self.bundle.as_deref().and_then(|b| b.get(idx))
}
pub fn primary_dtype(&self) -> DType {
self.inner.dtype_dyn()
}
pub fn slot_dtype(&self, idx: usize) -> Option<DType> {
self.slot_view(idx).map(|v| v.dtype)
}
pub fn as_dyn(&self) -> &dyn DynBackendStorage {
&*self.inner
}
pub fn as_dyn_mut(&mut self) -> &mut dyn DynBackendStorage {
&mut *self.inner
}
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.inner.as_any().downcast_ref::<T>()
}
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.inner.as_any_mut().downcast_mut::<T>()
}
pub fn try_clone(&self, layout: &Layout) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.try_clone_dyn(layout)?).with_stype(self.stype.clone()))
}
pub fn device(&self) -> Arc<dyn DynBackendDevice> {
self.inner.device_arc_dyn()
}
pub fn dtype(&self) -> DType {
self.inner.dtype_dyn()
}
pub fn with_stype(mut self, stype: SType) -> Self {
self.stype = stype;
self
}
pub fn stype(&self) -> &SType {
&self.stype
}
pub fn same_device(&self, rhs: &Self, op: &'static str) -> Result<()> {
let lhs_dev = self.inner.device_dyn();
let rhs_dev = rhs.inner.device_dyn();
let lhs_loc = lhs_dev.location_dyn();
let rhs_loc = rhs_dev.location_dyn();
let same = if matches!(lhs_loc, fuel_ir::DeviceLocation::Metal { .. }) {
lhs_dev.same_device_dyn(rhs_dev)
} else {
lhs_loc == rhs_loc
};
if !same {
Err(Error::DeviceMismatchBinaryOp {
lhs: lhs_loc,
rhs: rhs_loc,
op,
}
.bt())
} else {
Ok(())
}
}
pub fn same_dtype(&self, rhs: &Self, op: &'static str) -> Result<()> {
let lhs = self.dtype();
let rhs = rhs.dtype();
if lhs != rhs {
Err(Error::DTypeMismatchBinaryOp { lhs, rhs, op }.bt())
} else {
Ok(())
}
}
pub fn const_set(&mut self, v: Scalar, l: &Layout) -> Result<()> {
self.inner.const_set_dyn(v, l)
}
pub fn affine(&self, layout: &Layout, mul: f64, add: f64) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.affine_dyn(layout, mul, add)?))
}
pub fn powf(&self, layout: &Layout, e: f64) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.powf_dyn(layout, e)?))
}
pub fn elu(&self, layout: &Layout, alpha: f64) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.elu_dyn(layout, alpha)?))
}
pub fn cmp(
&self,
op: CmpOp,
rhs: &Self,
lhs_layout: &Layout,
rhs_layout: &Layout,
) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.cmp_dyn(
op,
&*rhs.inner,
lhs_layout,
rhs_layout,
)?))
}
pub fn reduce_op(&self, op: ReduceOp, layout: &Layout, reduce_dims: &[usize]) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.reduce_op_dyn(
op,
layout,
reduce_dims,
)?))
}
pub fn to_dtype(&self, layout: &Layout, dtype: DType) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.to_dtype_dyn(layout, dtype)?))
}
pub fn to_cpu_storage(&self) -> Result<HostBuffer> {
self.inner.to_host_buffer_dyn()
}
pub fn inplace_op1(&mut self, l: &Layout, c: &dyn InplaceOp1) -> Result<()> {
c.fwd(&mut *self.inner, l)
}
pub fn inplace_op2(
&mut self,
l1: &Layout,
t2: &Self,
l2: &Layout,
c: &dyn InplaceOp2,
) -> Result<()> {
self.same_device(t2, c.name())?;
c.fwd(&mut *self.inner, l1, &*t2.inner, l2)
}
pub fn inplace_op3(
&mut self,
l1: &Layout,
t2: &Self,
l2: &Layout,
t3: &Self,
l3: &Layout,
c: &dyn InplaceOp3,
) -> Result<()> {
self.same_device(t2, c.name())?;
self.same_device(t3, c.name())?;
c.fwd(&mut *self.inner, l1, &*t2.inner, l2, &*t3.inner, l3)
}
pub fn unary_impl<B: op::UnaryOpT>(&self, layout: &Layout) -> Result<Self> {
let op = op::UnaryOp::from_name(B::NAME)
.ok_or_else(|| Error::Msg(format!("unknown unary op '{}'", B::NAME)))?;
Ok(Storage::from_dyn(self.inner.unary_op_dyn(layout, op)?))
}
pub fn binary_impl<B: op::BinaryOpT>(
&self,
rhs: &Self,
lhs_layout: &Layout,
rhs_layout: &Layout,
) -> Result<Self> {
self.same_device(rhs, B::NAME)?;
self.same_dtype(rhs, B::NAME)?;
let op = BinaryOp::from_name(B::NAME)
.ok_or_else(|| Error::Msg(format!("unknown binary op '{}'", B::NAME)))?;
Ok(Storage::from_dyn(self.inner.binary_op_dyn(
&*rhs.inner,
lhs_layout,
rhs_layout,
op,
)?))
}
pub fn conv1d(
&self,
l: &Layout,
kernel: &Self,
kernel_l: &Layout,
params: &conv::ParamsConv1D,
) -> Result<Self> {
self.same_device(kernel, "conv1d")?;
self.same_dtype(kernel, "conv1d")?;
Ok(Storage::from_dyn(self.inner.conv1d_dyn(
l,
&*kernel.inner,
kernel_l,
params,
)?))
}
pub fn conv_transpose1d(
&self,
l: &Layout,
kernel: &Self,
kernel_l: &Layout,
params: &conv::ParamsConvTranspose1D,
) -> Result<Self> {
self.same_device(kernel, "conv-transpose1d")?;
self.same_dtype(kernel, "conv-transpose1d")?;
Ok(Storage::from_dyn(self.inner.conv_transpose1d_dyn(
l,
&*kernel.inner,
kernel_l,
params,
)?))
}
pub fn conv2d(
&self,
l: &Layout,
kernel: &Self,
kernel_l: &Layout,
params: &conv::ParamsConv2D,
) -> Result<Self> {
self.same_device(kernel, "conv2d")?;
self.same_dtype(kernel, "conv2d")?;
Ok(Storage::from_dyn(self.inner.conv2d_dyn(
l,
&*kernel.inner,
kernel_l,
params,
)?))
}
pub fn conv_transpose2d(
&self,
l: &Layout,
kernel: &Self,
kernel_l: &Layout,
params: &conv::ParamsConvTranspose2D,
) -> Result<Self> {
self.same_device(kernel, "conv_transpose2d")?;
self.same_dtype(kernel, "conv_transpose2d")?;
Ok(Storage::from_dyn(self.inner.conv_transpose2d_dyn(
l,
&*kernel.inner,
kernel_l,
params,
)?))
}
pub fn avg_pool2d(
&self,
layout: &Layout,
kernel_size: (usize, usize),
stride: (usize, usize),
) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.avg_pool2d_dyn(
layout,
kernel_size,
stride,
)?))
}
pub fn max_pool2d(
&self,
layout: &Layout,
kernel_size: (usize, usize),
stride: (usize, usize),
) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.max_pool2d_dyn(
layout,
kernel_size,
stride,
)?))
}
pub fn upsample_nearest1d(&self, layout: &Layout, sz: usize) -> Result<Self> {
Ok(Storage::from_dyn(
self.inner.upsample_nearest1d_dyn(layout, sz)?,
))
}
pub fn upsample_nearest2d(&self, layout: &Layout, h: usize, w: usize) -> Result<Self> {
Ok(Storage::from_dyn(
self.inner.upsample_nearest2d_dyn(layout, h, w)?,
))
}
pub fn upsample_bilinear2d(
&self,
layout: &Layout,
h: usize,
w: usize,
align_corners: bool,
scale_h: Option<f64>,
scale_w: Option<f64>,
) -> Result<Self> {
Ok(Storage::from_dyn(self.inner.upsample_bilinear2d_dyn(
layout,
h,
w,
align_corners,
scale_h,
scale_w,
)?))
}
pub fn where_cond(
&self,
layout: &Layout,
t: &Self,
layout_t: &Layout,
f: &Self,
layout_f: &Layout,
) -> Result<Self> {
self.same_device(t, "where")?;
self.same_device(f, "where")?;
t.same_dtype(f, "where")?;
Ok(Storage::from_dyn(self.inner.where_cond_dyn(
layout, &*t.inner, layout_t, &*f.inner, layout_f,
)?))
}
pub fn gather(&self, l: &Layout, indexes: &Self, indexes_l: &Layout, d: usize) -> Result<Self> {
self.same_device(indexes, "index-add")?;
Ok(Storage::from_dyn(self.inner.gather_dyn(
l,
&*indexes.inner,
indexes_l,
d,
)?))
}
pub fn scatter_set(
&mut self,
l: &Layout,
indexes: &Self,
indexes_l: &Layout,
source: &Self,
source_l: &Layout,
d: usize,
) -> Result<()> {
self.same_device(indexes, "scatter-set")?;
self.same_device(source, "scatter-set")?;
self.inner
.scatter_set_dyn(l, &*source.inner, source_l, &*indexes.inner, indexes_l, d)
}
pub fn scatter_add(
&mut self,
l: &Layout,
indexes: &Self,
indexes_l: &Layout,
source: &Self,
source_l: &Layout,
d: usize,
) -> Result<()> {
self.same_device(indexes, "scatter-add")?;
self.same_device(source, "scatter-add")?;
self.inner
.scatter_add_set_dyn(l, &*source.inner, source_l, &*indexes.inner, indexes_l, d)
}
pub fn index_add(
&self,
l: &Layout,
indexes: &Self,
indexes_l: &Layout,
source: &Self,
source_l: &Layout,
d: usize,
) -> Result<Self> {
self.same_device(indexes, "index-add")?;
self.same_device(source, "index-add")?;
Ok(Storage::from_dyn(self.inner.index_add_dyn(
l,
&*indexes.inner,
indexes_l,
&*source.inner,
source_l,
d,
)?))
}
pub fn index_select(
&self,
rhs: &Self,
lhs_l: &Layout,
rhs_l: &Layout,
d: usize,
) -> Result<Self> {
self.same_device(rhs, "index-select")?;
Ok(Storage::from_dyn(self.inner.index_select_dyn(
&*rhs.inner,
lhs_l,
rhs_l,
d,
)?))
}
pub fn matmul(
&self,
rhs: &Self,
bmnk: (usize, usize, usize, usize),
lhs_layout: &Layout,
rhs_layout: &Layout,
) -> Result<Self> {
self.same_device(rhs, "matmul")?;
self.same_dtype(rhs, "matmul")?;
Ok(Storage::from_dyn(self.inner.matmul_dyn(
&*rhs.inner,
bmnk,
lhs_layout,
rhs_layout,
)?))
}
pub fn copy_strided_src(
&self,
dst: &mut Self,
dst_offset: usize,
src_l: &Layout,
) -> Result<()> {
self.inner
.copy_strided_src_dyn(&mut *dst.inner, dst_offset, src_l)
}
pub fn copy2d(
&self,
dst: &mut Self,
d1: usize,
d2: usize,
src_s: usize,
dst_s: usize,
src_o: usize,
dst_o: usize,
) -> Result<()> {
self.inner
.copy2d_dyn(&mut *dst.inner, d1, d2, src_s, dst_s, src_o, dst_o)
}
}
#[cfg(test)]
mod multi_output_specs {
use super::*;
fn f32_spec(dims: &[usize]) -> OutputViewSpec {
OutputViewSpec::contiguous(DType::F32, Shape::from_dims(dims))
}
fn f64_spec(dims: &[usize]) -> OutputViewSpec {
OutputViewSpec::contiguous(DType::F64, Shape::from_dims(dims))
}
#[test]
fn compose_bundle_single_slot() {
let specs = vec![f32_spec(&[2, 3])];
let (total, views) = compose_bundle(&specs).expect("single slot composes");
assert_eq!(views.len(), 1);
assert_eq!(views[0].byte_offset, 0);
assert_eq!(views[0].len_elements, 6);
assert_eq!(views[0].dtype, DType::F32);
assert_eq!(total, 24); }
#[test]
fn compose_bundle_two_slot_aligned() {
let specs = vec![f32_spec(&[2, 3]), f64_spec(&[3])];
let (total, views) = compose_bundle(&specs).expect("aligned compose");
assert_eq!(views.len(), 2);
assert_eq!(views[0].byte_offset, 0);
assert_eq!(views[0].len_elements, 6);
assert_eq!(views[1].byte_offset, 24);
assert_eq!(views[1].len_elements, 3);
assert_eq!(views[1].dtype, DType::F64);
assert_eq!(total, 48); }
#[test]
fn compose_bundle_pads_for_alignment() {
let specs = vec![f32_spec(&[1]), f64_spec(&[1])];
let (total, views) = compose_bundle(&specs).expect("padded compose");
assert_eq!(views[0].byte_offset, 0);
assert_eq!(views[1].byte_offset, 8); assert_eq!(total, 16); }
#[test]
fn compose_bundle_rejects_empty() {
let err = compose_bundle(&[]).expect_err("empty spec list must error");
assert!(format!("{err}").contains("non-empty"));
}
#[test]
fn compose_bundle_rejects_shape_layout_mismatch() {
let s = Shape::from_dims(&[2, 3]);
let bogus_layout = Layout::contiguous(Shape::from_dims(&[3, 2]));
let bad = OutputViewSpec {
dtype: DType::F32,
shape: s,
layout: bogus_layout,
name: None,
};
let err = compose_bundle(&[bad]).expect_err("shape/layout mismatch must error");
assert!(format!("{err}").contains("disagrees"));
}
#[test]
fn output_view_spec_contiguous_helper() {
let s = f32_spec(&[4, 5]);
assert_eq!(s.dtype, DType::F32);
assert_eq!(s.shape, Shape::from_dims(&[4, 5]));
assert_eq!(s.layout.shape(), &Shape::from_dims(&[4, 5]));
assert_eq!(s.elem_count(), 20);
assert_eq!(s.len_bytes(), 80);
assert!(s.name.is_none());
}
}
#[cfg(test)]
mod stype_attach {
use super::*;
use fuel_ir::op::UnaryOp;
use std::any::Any;
#[derive(Debug)]
struct MockStorage;
#[allow(unused_variables)]
impl DynBackendStorage for MockStorage {
fn try_clone_dyn(&self, _layout: &Layout) -> Result<Box<dyn DynBackendStorage>> {
Ok(Box::new(MockStorage))
}
fn dtype_dyn(&self) -> DType {
DType::F32
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn device_dyn(&self) -> &dyn DynBackendDevice {
unimplemented!()
}
fn device_arc_dyn(&self) -> Arc<dyn DynBackendDevice> {
unimplemented!()
}
fn to_host_buffer_dyn(&self) -> Result<HostBuffer> {
unimplemented!()
}
fn affine_dyn(&self, l: &Layout, mul: f64, add: f64) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn powf_dyn(&self, l: &Layout, e: f64) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn elu_dyn(&self, l: &Layout, alpha: f64) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn reduce_op_dyn(
&self,
op: ReduceOp,
l: &Layout,
axes: &[usize],
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn cmp_dyn(
&self,
op: CmpOp,
rhs: &dyn DynBackendStorage,
ll: &Layout,
rl: &Layout,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn to_dtype_dyn(&self, l: &Layout, dtype: DType) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn unary_op_dyn(&self, l: &Layout, op: UnaryOp) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn binary_op_dyn(
&self,
rhs: &dyn DynBackendStorage,
ll: &Layout,
rl: &Layout,
op: BinaryOp,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn where_cond_dyn(
&self,
cl: &Layout,
t: &dyn DynBackendStorage,
tl: &Layout,
f: &dyn DynBackendStorage,
fl: &Layout,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn conv1d_dyn(
&self,
l: &Layout,
k: &dyn DynBackendStorage,
kl: &Layout,
p: &conv::ParamsConv1D,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn conv_transpose1d_dyn(
&self,
l: &Layout,
k: &dyn DynBackendStorage,
kl: &Layout,
p: &conv::ParamsConvTranspose1D,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn conv2d_dyn(
&self,
l: &Layout,
k: &dyn DynBackendStorage,
kl: &Layout,
p: &conv::ParamsConv2D,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn conv_transpose2d_dyn(
&self,
l: &Layout,
k: &dyn DynBackendStorage,
kl: &Layout,
p: &conv::ParamsConvTranspose2D,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn avg_pool2d_dyn(
&self,
l: &Layout,
k: (usize, usize),
s: (usize, usize),
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn max_pool2d_dyn(
&self,
l: &Layout,
k: (usize, usize),
s: (usize, usize),
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn upsample_nearest1d_dyn(
&self,
l: &Layout,
t: usize,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn upsample_nearest2d_dyn(
&self,
l: &Layout,
h: usize,
w: usize,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn upsample_bilinear2d_dyn(
&self,
l: &Layout,
h: usize,
w: usize,
ac: bool,
sh: Option<f64>,
sw: Option<f64>,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn gather_dyn(
&self,
sl: &Layout,
ids: &dyn DynBackendStorage,
il: &Layout,
dim: usize,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn scatter_set_dyn(
&mut self,
sl: &Layout,
src: &dyn DynBackendStorage,
srl: &Layout,
ids: &dyn DynBackendStorage,
il: &Layout,
dim: usize,
) -> Result<()> {
unimplemented!()
}
fn scatter_add_set_dyn(
&mut self,
sl: &Layout,
src: &dyn DynBackendStorage,
srl: &Layout,
ids: &dyn DynBackendStorage,
il: &Layout,
dim: usize,
) -> Result<()> {
unimplemented!()
}
fn index_select_dyn(
&self,
ids: &dyn DynBackendStorage,
sl: &Layout,
il: &Layout,
dim: usize,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn index_add_dyn(
&self,
sl: &Layout,
ids: &dyn DynBackendStorage,
il: &Layout,
src: &dyn DynBackendStorage,
srl: &Layout,
dim: usize,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn matmul_dyn(
&self,
rhs: &dyn DynBackendStorage,
bmnk: (usize, usize, usize, usize),
ll: &Layout,
rl: &Layout,
) -> Result<Box<dyn DynBackendStorage>> {
unimplemented!()
}
fn copy_strided_src_dyn(
&self,
dst: &mut dyn DynBackendStorage,
off: usize,
sl: &Layout,
) -> Result<()> {
unimplemented!()
}
fn copy2d_dyn(
&self,
dst: &mut dyn DynBackendStorage,
d1: usize,
d2: usize,
ss1: usize,
ds1: usize,
so: usize,
dofs: usize,
) -> Result<()> {
unimplemented!()
}
fn const_set_dyn(&mut self, value: Scalar, l: &Layout) -> Result<()> {
unimplemented!()
}
}
#[test]
fn trait_object_storage_defaults_plain() {
let s = Storage::new(MockStorage);
assert!(
s.stype().is_plain(),
"default Storage must carry a plain SType"
);
assert_eq!(s.stype().layers().len(), 0);
}
#[test]
fn try_clone_preserves_stype() {
use fuel_ir::GgmlDType;
use fuel_ir::stype::Encoding;
let s = Storage::new(MockStorage).with_stype(SType::from_layer(Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4_0,
}));
let layout = Layout::contiguous(Shape::from_dims(&[4]));
let cloned = s.try_clone(&layout).expect("clone");
assert_eq!(cloned.stype(), s.stype(), "try_clone must preserve stype");
assert!(!cloned.stype().is_plain());
}
}