Skip to main content

tract_data/tensor/
storage.rs

1use std::alloc::Layout;
2use std::fmt;
3use std::hash::Hash;
4
5use crate::TractResult;
6use crate::blob::Blob;
7use crate::datum::DatumType;
8use crate::dyn_eq::DynEq;
9use crate::exotic::ExoticFact;
10use crate::tensor::Tensor;
11use downcast_rs::{Downcast, impl_downcast};
12
13/// Trait abstracting over tensor storage backends.
14///
15/// Two independent axes describe one: layout, `is_exotic`, and placement,
16/// `in_ram`. `PlainStorage` is the primary implementation, plain and in ram by
17/// construction; every other backend is held behind
18/// `StorageKind::Exotic(Box<dyn TensorStorage>)`, whichever pair of answers it
19/// gives.
20pub trait TensorStorage: Send + Sync + fmt::Debug + fmt::Display + DynEq + Downcast {
21    fn byte_len(&self) -> usize;
22    fn is_empty(&self) -> bool;
23    fn deep_clone(&self) -> Box<dyn TensorStorage>;
24    fn as_plain_ram(&self) -> Option<&PlainStorage>;
25    fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage>;
26    fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage>;
27    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher);
28    /// Build the `ExoticFact` that describes this storage for use in `TypedFact`.
29    ///
30    /// Plain storage returns `None`. Exotic storages should return the
31    /// appropriate fact so that `From<Arc<Tensor>> for TypedFact` preserves
32    /// exotic-ness.
33    fn exotic_fact(&self, shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>>;
34
35    /// True when the tensor's datum type and shape do not describe it on their
36    /// own, so a fact over it carries an `ExoticFact`.
37    ///
38    /// The layout axis, orthogonal to `in_ram`: all four combinations exist,
39    /// a dense tensor in device memory being plain and out of ram,
40    /// block-quant weights in device memory exotic and out of ram.
41    ///
42    /// Defaults to true: a storage is exotic until it says otherwise, so one
43    /// that forgets is refused where a fact is required rather than silently
44    /// mistyped.
45    fn is_exotic(&self) -> bool {
46        true
47    }
48
49    /// True when the bytes are in host memory, readable without a transfer.
50    ///
51    /// The placement axis. Defaults to true: storage holds its own bytes unless
52    /// it says otherwise. Answers for the bytes in whatever layout the storage
53    /// keeps them -- block-quant storage is in ram while packed -- so it takes
54    /// both axes, `as_plain_ram`, for a plain read to be sure to work.
55    fn in_ram(&self) -> bool {
56        true
57    }
58
59    /// Plain storage for the tensor's bytes, producing it if this storage can.
60    ///
61    /// This is the accessor path: `Tensor::as_bytes` and friends go through it,
62    /// so a storage that holds its bytes somewhere else (on a device, say) gets
63    /// a chance to bring them back here, and to keep the result so the next
64    /// access is free. `as_plain_ram` stays the cheap accessor: it answers with
65    /// what is available right now and never produces anything.
66    fn materialize_plain_ram(&self) -> TractResult<&PlainStorage> {
67        self.as_plain_ram().ok_or_else(|| anyhow::anyhow!("Tensor storage is not plain"))
68    }
69
70    /// Slice along `axis`, if this storage can do it without copying.
71    ///
72    /// `None` means "not capable" and the caller falls back to a generic copy,
73    /// so an implementation is free to refuse any case it cannot serve. What it
74    /// must not do is return a tensor that is not a valid dense one: `Some` is a
75    /// claim that the result stands on its own everywhere a tensor is accepted.
76    fn slice(
77        &self,
78        _dt: DatumType,
79        _shape: &[usize],
80        _axis: usize,
81        _start: usize,
82        _end: usize,
83    ) -> TractResult<Option<Tensor>> {
84        Ok(None)
85    }
86}
87impl_downcast!(TensorStorage);
88crate::eq_trait_object!(TensorStorage);
89
90/// Plain, contiguous storage backed by a `Blob`: plain in layout and in ram,
91/// which is what every other storage is measured against.
92#[derive(Eq)]
93pub struct PlainStorage(pub(crate) Blob);
94
95impl PlainStorage {
96    #[inline]
97    pub fn layout(&self) -> &Layout {
98        self.0.layout()
99    }
100
101    #[inline]
102    pub fn as_bytes(&self) -> &[u8] {
103        self.0.as_bytes()
104    }
105
106    #[inline]
107    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
108        self.0.as_bytes_mut()
109    }
110
111    #[inline]
112    pub fn as_ptr(&self) -> *const u8 {
113        self.0.as_bytes().as_ptr()
114    }
115
116    #[inline]
117    pub fn as_mut_ptr(&mut self) -> *mut u8 {
118        self.0.as_bytes_mut().as_mut_ptr()
119    }
120
121    #[inline]
122    pub fn into_blob(self) -> Blob {
123        self.0
124    }
125}
126
127impl Default for PlainStorage {
128    #[inline]
129    fn default() -> Self {
130        PlainStorage(Blob::default())
131    }
132}
133
134impl Clone for PlainStorage {
135    #[inline]
136    fn clone(&self) -> Self {
137        PlainStorage(self.0.clone())
138    }
139}
140
141impl Hash for PlainStorage {
142    #[inline]
143    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
144        self.0.hash(state);
145    }
146}
147
148impl PartialEq for PlainStorage {
149    #[inline]
150    fn eq(&self, other: &Self) -> bool {
151        self.0 == other.0
152    }
153}
154
155impl From<Blob> for PlainStorage {
156    #[inline]
157    fn from(blob: Blob) -> Self {
158        PlainStorage(blob)
159    }
160}
161
162impl std::ops::Deref for PlainStorage {
163    type Target = [u8];
164    #[inline]
165    fn deref(&self) -> &[u8] {
166        self.0.as_bytes()
167    }
168}
169
170impl std::ops::DerefMut for PlainStorage {
171    #[inline]
172    fn deref_mut(&mut self) -> &mut [u8] {
173        self.0.as_bytes_mut()
174    }
175}
176
177impl fmt::Debug for PlainStorage {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        fmt::Debug::fmt(&self.0, f)
180    }
181}
182
183impl fmt::Display for PlainStorage {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        fmt::Display::fmt(&self.0, f)
186    }
187}
188
189impl TensorStorage for PlainStorage {
190    #[inline]
191    fn is_empty(&self) -> bool {
192        self.0.is_empty()
193    }
194
195    #[inline]
196    fn byte_len(&self) -> usize {
197        self.0.len()
198    }
199
200    fn deep_clone(&self) -> Box<dyn TensorStorage> {
201        Box::new(PlainStorage(self.0.clone()))
202    }
203
204    fn as_plain_ram(&self) -> Option<&PlainStorage> {
205        Some(self)
206    }
207
208    fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
209        Some(self)
210    }
211
212    fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage> {
213        Some(*self)
214    }
215
216    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {
217        state.write_u8(0);
218        state.write(self.0.as_bytes());
219    }
220
221    fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {
222        Ok(None)
223    }
224
225    fn is_exotic(&self) -> bool {
226        false
227    }
228}
229
230/// Inline enum replacing `Box<dyn TensorStorage>`.
231///
232/// The common `Plain` case stays inline (no heap alloc, no vtable indirection).
233/// `Exotic` covers every other backend behind a single Box indirection, whether
234/// or not it is exotic in the fact sense -- `is_exotic` answers that.
235#[derive(Debug, PartialEq, Eq)]
236#[allow(dead_code)]
237pub(crate) enum StorageKind {
238    Plain(PlainStorage),
239    Exotic(Box<dyn TensorStorage>),
240}
241
242impl StorageKind {
243    #[inline]
244    pub fn as_plain_ram(&self) -> Option<&PlainStorage> {
245        match self {
246            StorageKind::Plain(d) => Some(d),
247            StorageKind::Exotic(o) => o.as_plain_ram(),
248        }
249    }
250
251    #[inline]
252    pub fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
253        match self {
254            StorageKind::Plain(d) => Some(d),
255            StorageKind::Exotic(o) => o.as_plain_ram_mut(),
256        }
257    }
258
259    #[inline]
260    pub fn into_plain_ram(self) -> Option<PlainStorage> {
261        match self {
262            StorageKind::Plain(d) => Some(d),
263            StorageKind::Exotic(o) => o.into_plain_ram(),
264        }
265    }
266
267    #[inline]
268    pub fn byte_len(&self) -> usize {
269        match self {
270            StorageKind::Plain(d) => d.0.len(),
271            StorageKind::Exotic(o) => o.byte_len(),
272        }
273    }
274
275    #[inline]
276    pub fn is_empty(&self) -> bool {
277        match self {
278            StorageKind::Plain(d) => d.0.is_empty(),
279            StorageKind::Exotic(o) => o.is_empty(),
280        }
281    }
282
283    #[inline]
284    #[allow(dead_code)]
285    pub fn deep_clone(&self) -> StorageKind {
286        match self {
287            StorageKind::Plain(d) => StorageKind::Plain(d.clone()),
288            StorageKind::Exotic(o) => StorageKind::Exotic(o.deep_clone()),
289        }
290    }
291
292    #[inline]
293    pub fn is_exotic(&self) -> bool {
294        match self {
295            StorageKind::Plain(_) => false,
296            StorageKind::Exotic(o) => o.is_exotic(),
297        }
298    }
299
300    #[inline]
301    pub fn in_ram(&self) -> bool {
302        match self {
303            StorageKind::Plain(_) => true,
304            StorageKind::Exotic(o) => o.in_ram(),
305        }
306    }
307
308    #[inline]
309    pub fn materialize_plain_ram(&self) -> TractResult<&PlainStorage> {
310        match self {
311            StorageKind::Plain(d) => Ok(d),
312            StorageKind::Exotic(o) => o.materialize_plain_ram(),
313        }
314    }
315
316    #[inline]
317    pub fn as_storage(&self) -> &dyn TensorStorage {
318        match self {
319            StorageKind::Plain(d) => d,
320            StorageKind::Exotic(o) => o.as_ref(),
321        }
322    }
323
324    #[inline]
325    #[allow(dead_code)]
326    pub fn as_storage_mut(&mut self) -> &mut dyn TensorStorage {
327        match self {
328            StorageKind::Plain(d) => d,
329            StorageKind::Exotic(o) => o.as_mut(),
330        }
331    }
332
333    pub fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {
334        match self {
335            StorageKind::Plain(d) => {
336                state.write_u8(0);
337                state.write(d.as_bytes())
338            }
339            StorageKind::Exotic(o) => o.dyn_hash(state),
340        }
341    }
342}