use std::fmt::{self, Display};
use std::sync::OnceLock;
use tract_core::internal::*;
use super::DeviceTensor;
use crate::device::get_context;
#[derive(Debug)]
pub struct LazyHostStorage {
device: Option<DeviceTensor>,
host: OnceLock<Arc<Tensor>>,
}
impl LazyHostStorage {
pub fn new(device: DeviceTensor) -> TractResult<Self> {
ensure!(
!device.is_exotic(),
"A lazy host tensor stands in for plain host bytes, and {device:?} would come back as its own storage"
);
get_context()?.synchronize()?;
Ok(LazyHostStorage { device: Some(device), host: OnceLock::new() })
}
pub fn device(&self) -> Option<&DeviceTensor> {
self.device.as_ref()
}
pub fn is_materialized(&self) -> bool {
self.host.get().is_some()
}
pub fn datum_type(&self) -> DatumType {
match (&self.device, self.host.get()) {
(Some(d), _) => d.datum_type(),
(None, Some(h)) => h.datum_type(),
(None, None) => DatumType::U8,
}
}
pub fn shape(&self) -> TVec<usize> {
match (&self.device, self.host.get()) {
(Some(d), _) => d.shape().into(),
(None, Some(h)) => h.shape().into(),
(None, None) => tvec![],
}
}
pub fn into_tensor(self) -> Tensor {
let dt = self.datum_type();
let shape = self.shape();
Tensor::from_storage(dt, &shape, self)
}
fn materialize(&self) -> TractResult<&Arc<Tensor>> {
if let Some(host) = self.host.get() {
return Ok(host);
}
let device = self
.device
.as_ref()
.context("Lazy host storage has neither a device tensor nor host bytes")?;
let host = device.to_host().context("While materializing a lazy host tensor")?;
Ok(self.host.get_or_init(|| host))
}
}
impl PartialEq for LazyHostStorage {
fn eq(&self, other: &Self) -> bool {
match (&self.device, &other.device) {
(Some(a), Some(b)) => a == b,
(None, None) => self.host.get() == other.host.get(),
_ => false,
}
}
}
impl Eq for LazyHostStorage {}
impl Display for LazyHostStorage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.host.get() {
Some(host) => write!(f, "LazyHost(materialized): {{ {host:?} }}"),
None => {
write!(f, "LazyHost(on device): {{ {:?} {:?} }}", self.datum_type(), self.shape())
}
}
}
}
impl TensorStorage for LazyHostStorage {
fn byte_len(&self) -> usize {
match (&self.device, self.host.get()) {
(Some(d), _) => d.len() * d.datum_type().size_of(),
(None, Some(h)) => h.len() * h.datum_type().size_of(),
(None, None) => 0,
}
}
fn is_empty(&self) -> bool {
self.byte_len() == 0
}
fn deep_clone(&self) -> Box<dyn TensorStorage> {
let host = OnceLock::new();
if let Some(h) = self.host.get() {
let _ = host.set(Arc::clone(h));
}
Box::new(LazyHostStorage { device: self.device.clone(), host })
}
fn as_plain_ram(&self) -> Option<&PlainStorage> {
self.host.get().and_then(|h| h.as_plain_ram_storage())
}
fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
self.materialize().ok()?;
self.device = None;
Arc::make_mut(self.host.get_mut()?).as_plain_ram_storage_mut()
}
fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage> {
let me = *self;
me.materialize().ok()?;
let host = me.host.into_inner()?;
let host = Arc::try_unwrap(host).unwrap_or_else(|shared| (*shared).clone());
host.into_blob().ok().map(PlainStorage::from)
}
fn dyn_hash(&self, _state: &mut dyn std::hash::Hasher) {
}
fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {
Ok(None)
}
fn is_exotic(&self) -> bool {
false
}
fn in_ram(&self) -> bool {
self.is_materialized()
}
fn materialize_plain_ram(&self) -> TractResult<&PlainStorage> {
self.materialize()?
.as_plain_ram_storage()
.context("Device readback did not produce plain storage")
}
fn slice(
&self,
dt: DatumType,
shape: &[usize],
axis: usize,
start: usize,
end: usize,
) -> TractResult<Option<Tensor>> {
if self.is_materialized() {
return Ok(None);
}
let Some(device) = self.device.as_ref() else { return Ok(None) };
let Some(sliced) = device.slice_on_device(dt, shape, axis, start, end)? else {
return Ok(None);
};
Ok(Some(Self::new(sliced)?.into_tensor()))
}
}