1use std::fmt::{self, Display};
2use std::sync::OnceLock;
3
4use tract_core::internal::*;
5
6use super::DeviceTensor;
7use crate::device::get_context;
8
9#[derive(Debug)]
23pub struct LazyHostStorage {
24 device: Option<DeviceTensor>,
27 host: OnceLock<Arc<Tensor>>,
28}
29
30impl LazyHostStorage {
31 pub fn new(device: DeviceTensor) -> TractResult<Self> {
39 ensure!(
40 !device.is_exotic(),
41 "A lazy host tensor stands in for plain host bytes, and {device:?} would come back as its own storage"
42 );
43 get_context()?.synchronize()?;
44 Ok(LazyHostStorage { device: Some(device), host: OnceLock::new() })
45 }
46
47 pub fn device(&self) -> Option<&DeviceTensor> {
50 self.device.as_ref()
51 }
52
53 pub fn is_materialized(&self) -> bool {
55 self.host.get().is_some()
56 }
57
58 pub fn datum_type(&self) -> DatumType {
59 match (&self.device, self.host.get()) {
60 (Some(d), _) => d.datum_type(),
61 (None, Some(h)) => h.datum_type(),
62 (None, None) => DatumType::U8,
63 }
64 }
65
66 pub fn shape(&self) -> TVec<usize> {
67 match (&self.device, self.host.get()) {
68 (Some(d), _) => d.shape().into(),
69 (None, Some(h)) => h.shape().into(),
70 (None, None) => tvec![],
71 }
72 }
73
74 pub fn into_tensor(self) -> Tensor {
76 let dt = self.datum_type();
77 let shape = self.shape();
78 Tensor::from_storage(dt, &shape, self)
79 }
80
81 fn materialize(&self) -> TractResult<&Arc<Tensor>> {
91 if let Some(host) = self.host.get() {
92 return Ok(host);
93 }
94 let device = self
95 .device
96 .as_ref()
97 .context("Lazy host storage has neither a device tensor nor host bytes")?;
98 let host = device.to_host().context("While materializing a lazy host tensor")?;
99 Ok(self.host.get_or_init(|| host))
100 }
101}
102
103impl PartialEq for LazyHostStorage {
104 fn eq(&self, other: &Self) -> bool {
105 match (&self.device, &other.device) {
106 (Some(a), Some(b)) => a == b,
107 (None, None) => self.host.get() == other.host.get(),
108 _ => false,
109 }
110 }
111}
112
113impl Eq for LazyHostStorage {}
114
115impl Display for LazyHostStorage {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self.host.get() {
118 Some(host) => write!(f, "LazyHost(materialized): {{ {host:?} }}"),
119 None => {
120 write!(f, "LazyHost(on device): {{ {:?} {:?} }}", self.datum_type(), self.shape())
121 }
122 }
123 }
124}
125
126impl TensorStorage for LazyHostStorage {
127 fn byte_len(&self) -> usize {
128 match (&self.device, self.host.get()) {
129 (Some(d), _) => d.len() * d.datum_type().size_of(),
130 (None, Some(h)) => h.len() * h.datum_type().size_of(),
131 (None, None) => 0,
132 }
133 }
134
135 fn is_empty(&self) -> bool {
136 self.byte_len() == 0
137 }
138
139 fn deep_clone(&self) -> Box<dyn TensorStorage> {
140 let host = OnceLock::new();
143 if let Some(h) = self.host.get() {
144 let _ = host.set(Arc::clone(h));
145 }
146 Box::new(LazyHostStorage { device: self.device.clone(), host })
147 }
148
149 fn as_plain_ram(&self) -> Option<&PlainStorage> {
150 self.host.get().and_then(|h| h.as_plain_ram_storage())
152 }
153
154 fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
155 self.materialize().ok()?;
156 self.device = None;
161 Arc::make_mut(self.host.get_mut()?).as_plain_ram_storage_mut()
162 }
163
164 fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage> {
165 let me = *self;
166 me.materialize().ok()?;
167 let host = me.host.into_inner()?;
168 let host = Arc::try_unwrap(host).unwrap_or_else(|shared| (*shared).clone());
169 host.into_blob().ok().map(PlainStorage::from)
170 }
171
172 fn dyn_hash(&self, _state: &mut dyn std::hash::Hasher) {
173 }
175
176 fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {
177 Ok(None)
180 }
181
182 fn is_exotic(&self) -> bool {
183 false
184 }
185
186 fn in_ram(&self) -> bool {
187 self.is_materialized()
188 }
189
190 fn materialize_plain_ram(&self) -> TractResult<&PlainStorage> {
191 self.materialize()?
192 .as_plain_ram_storage()
193 .context("Device readback did not produce plain storage")
194 }
195
196 fn slice(
202 &self,
203 dt: DatumType,
204 shape: &[usize],
205 axis: usize,
206 start: usize,
207 end: usize,
208 ) -> TractResult<Option<Tensor>> {
209 if self.is_materialized() {
210 return Ok(None);
211 }
212 let Some(device) = self.device.as_ref() else { return Ok(None) };
213 let Some(sliced) = device.slice_on_device(dt, shape, axis, start, end)? else {
214 return Ok(None);
215 };
216 Ok(Some(Self::new(sliced)?.into_tensor()))
217 }
218}