use super::Client;
use crate::server::{CopyDescriptor, Handle};
use alloc::boxed::Box;
use alloc::format;
use core::mem::MaybeUninit;
use cubecl_common::bytes::{
AccessError, AccessPolicy, AllocationController, AllocationProperty, Bytes,
};
use cubecl_environment::sync::{Mutex, Once};
use cubecl_zspace::striding::has_contiguous_row_major_strides;
pub struct LazyDeviceController {
client: Client,
source: Mutex<Option<CopyDescriptor<Handle>>>,
len: usize,
materialized: Once<Bytes>,
}
impl LazyDeviceController {
pub(super) fn new(client: Client, source: CopyDescriptor<Handle>) -> Self {
Self {
client,
len: source.shape.iter().product::<usize>() * source.elem_size,
source: Mutex::new(Some(source)),
materialized: Once::new(),
}
}
pub(super) fn into_bytes(self) -> Bytes {
let len = self.len;
unsafe { Bytes::from_controller(Box::new(self), len) }
}
fn ensure_init(&self, policy: AccessPolicy) -> Result<&Bytes, AccessError> {
if let Some(bytes) = self.materialized.get() {
return Ok(bytes);
}
if !policy.copy_allowed() {
return Err(AccessError::WouldCopy);
}
let bytes = self
.materialized
.try_call_once(|| -> Result<Bytes, AccessError> {
let source = self
.source
.lock()
.clone()
.expect("The source is held until its bytes are on the host");
cubecl_environment::future::reader::read_sync(
self.client.read_one_tensor_async(source.binding()),
)
.map_err(|err| AccessError::Read(format!("{err:?}")))
})?;
if !bytes.may_alias_mutable_storage() {
self.source.lock().take();
}
Ok(bytes)
}
}
impl AllocationController for LazyDeviceController {
fn alloc_align(&self) -> usize {
match self.materialized.get() {
Some(bytes) => bytes.align(),
None => core::mem::align_of::<u128>(),
}
}
fn property(&self) -> AllocationProperty {
match self.materialized.get() {
Some(bytes) => bytes.property(),
None => AllocationProperty::Device,
}
}
fn may_alias_mutable_storage(&self) -> bool {
match self.materialized.get() {
Some(bytes) => bytes.may_alias_mutable_storage(),
None => true,
}
}
fn capacity(&self) -> usize {
self.len
}
fn view(&self, start: usize, end: usize) -> Option<Box<dyn AllocationController>> {
if self.materialized.get().is_some() {
return None;
}
let source = self.source.lock().clone()?;
if start > end || end > self.len {
return None;
}
if !has_contiguous_row_major_strides(&source.shape, &source.strides) {
return None;
}
let mut handle = source.handle;
let base = handle.offset_start.unwrap_or(0);
handle.offset_end = Some(handle.size - (base + end as u64));
handle.offset_start = Some(base + start as u64);
let descriptor = CopyDescriptor::new(handle, [end - start].into(), [1].into(), 1);
Some(Box::new(LazyDeviceController::new(
self.client.clone(),
descriptor,
)))
}
fn memory(&self, policy: AccessPolicy) -> Result<&[MaybeUninit<u8>], AccessError> {
let bytes = self.ensure_init(policy)?;
let slice: &[u8] = bytes;
Ok(unsafe { core::slice::from_raw_parts(slice.as_ptr().cast(), slice.len()) })
}
unsafe fn memory_mut(
&mut self,
policy: AccessPolicy,
) -> Result<&mut [MaybeUninit<u8>], AccessError> {
self.ensure_init(policy)?;
let bytes = self
.materialized
.get_mut()
.expect("materialized must be set after init");
let slice: &mut [u8] = bytes;
let (ptr, len) = (slice.as_mut_ptr(), slice.len());
if !bytes.may_alias_mutable_storage() {
self.source.lock().take();
}
Ok(unsafe { core::slice::from_raw_parts_mut(ptr.cast(), len) })
}
unsafe fn copy_into(&self, buf: &mut [u8]) {
let bytes = self
.ensure_init(AccessPolicy::default())
.expect("device: host access failed");
let len = buf.len().min(bytes.len());
buf[..len].copy_from_slice(&bytes[..len]);
}
}