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 serve it in its own memory --
71    /// for free where the slice is already a range it holds, by a copy it can
72    /// make where it is otherwise.
73    ///
74    /// `None` means "not capable" and the caller falls back to a generic copy
75    /// through host memory, so an implementation is free to refuse any case it
76    /// cannot serve -- but storage whose bytes are not host bytes has no such
77    /// fallback to decline to, and refusing there is a panic. What it must not
78    /// do is return a tensor that is not a valid dense one: `Some` is a claim
79    /// that the result stands on its own everywhere a tensor is accepted.
80    fn slice(
81        &self,
82        _dt: DatumType,
83        _shape: &[usize],
84        _axis: usize,
85        _start: usize,
86        _end: usize,
87    ) -> TractResult<Option<Tensor>> {
88        Ok(None)
89    }
90}
91impl_downcast!(TensorStorage);
92crate::eq_trait_object!(TensorStorage);
93
94/// Plain, contiguous storage backed by a `Blob`: plain in layout and in ram,
95/// which is what every other storage is measured against.
96#[derive(Eq)]
97pub struct PlainStorage(pub(crate) Blob);
98
99impl PlainStorage {
100    #[inline]
101    pub fn layout(&self) -> &Layout {
102        self.0.layout()
103    }
104
105    #[inline]
106    pub fn as_bytes(&self) -> &[u8] {
107        self.0.as_bytes()
108    }
109
110    #[inline]
111    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
112        self.0.as_bytes_mut()
113    }
114
115    #[inline]
116    pub fn as_ptr(&self) -> *const u8 {
117        self.0.as_bytes().as_ptr()
118    }
119
120    #[inline]
121    pub fn as_mut_ptr(&mut self) -> *mut u8 {
122        self.0.as_bytes_mut().as_mut_ptr()
123    }
124
125    #[inline]
126    pub fn into_blob(self) -> Blob {
127        self.0
128    }
129}
130
131impl Default for PlainStorage {
132    #[inline]
133    fn default() -> Self {
134        PlainStorage(Blob::default())
135    }
136}
137
138impl Clone for PlainStorage {
139    #[inline]
140    fn clone(&self) -> Self {
141        PlainStorage(self.0.clone())
142    }
143}
144
145impl Hash for PlainStorage {
146    #[inline]
147    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
148        self.0.hash(state);
149    }
150}
151
152impl PartialEq for PlainStorage {
153    #[inline]
154    fn eq(&self, other: &Self) -> bool {
155        self.0 == other.0
156    }
157}
158
159impl From<Blob> for PlainStorage {
160    #[inline]
161    fn from(blob: Blob) -> Self {
162        PlainStorage(blob)
163    }
164}
165
166impl std::ops::Deref for PlainStorage {
167    type Target = [u8];
168    #[inline]
169    fn deref(&self) -> &[u8] {
170        self.0.as_bytes()
171    }
172}
173
174impl std::ops::DerefMut for PlainStorage {
175    #[inline]
176    fn deref_mut(&mut self) -> &mut [u8] {
177        self.0.as_bytes_mut()
178    }
179}
180
181impl fmt::Debug for PlainStorage {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        fmt::Debug::fmt(&self.0, f)
184    }
185}
186
187impl fmt::Display for PlainStorage {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        fmt::Display::fmt(&self.0, f)
190    }
191}
192
193impl TensorStorage for PlainStorage {
194    #[inline]
195    fn is_empty(&self) -> bool {
196        self.0.is_empty()
197    }
198
199    #[inline]
200    fn byte_len(&self) -> usize {
201        self.0.len()
202    }
203
204    fn deep_clone(&self) -> Box<dyn TensorStorage> {
205        Box::new(PlainStorage(self.0.clone()))
206    }
207
208    fn as_plain_ram(&self) -> Option<&PlainStorage> {
209        Some(self)
210    }
211
212    fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
213        Some(self)
214    }
215
216    fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage> {
217        Some(*self)
218    }
219
220    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {
221        state.write_u8(0);
222        state.write(self.0.as_bytes());
223    }
224
225    fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {
226        Ok(None)
227    }
228
229    fn is_exotic(&self) -> bool {
230        false
231    }
232}
233
234/// Inline enum replacing `Box<dyn TensorStorage>`.
235///
236/// The common `Plain` case stays inline (no heap alloc, no vtable indirection).
237/// `Exotic` covers every other backend behind a single Box indirection, whether
238/// or not it is exotic in the fact sense -- `is_exotic` answers that.
239#[derive(Debug, PartialEq, Eq)]
240#[allow(dead_code)]
241pub(crate) enum StorageKind {
242    Plain(PlainStorage),
243    Exotic(Box<dyn TensorStorage>),
244}
245
246impl StorageKind {
247    #[inline]
248    pub fn as_plain_ram(&self) -> Option<&PlainStorage> {
249        match self {
250            StorageKind::Plain(d) => Some(d),
251            StorageKind::Exotic(o) => o.as_plain_ram(),
252        }
253    }
254
255    #[inline]
256    pub fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
257        match self {
258            StorageKind::Plain(d) => Some(d),
259            StorageKind::Exotic(o) => o.as_plain_ram_mut(),
260        }
261    }
262
263    #[inline]
264    pub fn into_plain_ram(self) -> Option<PlainStorage> {
265        match self {
266            StorageKind::Plain(d) => Some(d),
267            StorageKind::Exotic(o) => o.into_plain_ram(),
268        }
269    }
270
271    #[inline]
272    pub fn byte_len(&self) -> usize {
273        match self {
274            StorageKind::Plain(d) => d.0.len(),
275            StorageKind::Exotic(o) => o.byte_len(),
276        }
277    }
278
279    #[inline]
280    pub fn is_empty(&self) -> bool {
281        match self {
282            StorageKind::Plain(d) => d.0.is_empty(),
283            StorageKind::Exotic(o) => o.is_empty(),
284        }
285    }
286
287    #[inline]
288    #[allow(dead_code)]
289    pub fn deep_clone(&self) -> StorageKind {
290        match self {
291            StorageKind::Plain(d) => StorageKind::Plain(d.clone()),
292            StorageKind::Exotic(o) => StorageKind::Exotic(o.deep_clone()),
293        }
294    }
295
296    #[inline]
297    pub fn is_exotic(&self) -> bool {
298        match self {
299            StorageKind::Plain(_) => false,
300            StorageKind::Exotic(o) => o.is_exotic(),
301        }
302    }
303
304    #[inline]
305    pub fn in_ram(&self) -> bool {
306        match self {
307            StorageKind::Plain(_) => true,
308            StorageKind::Exotic(o) => o.in_ram(),
309        }
310    }
311
312    #[inline]
313    pub fn materialize_plain_ram(&self) -> TractResult<&PlainStorage> {
314        match self {
315            StorageKind::Plain(d) => Ok(d),
316            StorageKind::Exotic(o) => o.materialize_plain_ram(),
317        }
318    }
319
320    #[inline]
321    pub fn as_storage(&self) -> &dyn TensorStorage {
322        match self {
323            StorageKind::Plain(d) => d,
324            StorageKind::Exotic(o) => o.as_ref(),
325        }
326    }
327
328    #[inline]
329    #[allow(dead_code)]
330    pub fn as_storage_mut(&mut self) -> &mut dyn TensorStorage {
331        match self {
332            StorageKind::Plain(d) => d,
333            StorageKind::Exotic(o) => o.as_mut(),
334        }
335    }
336
337    pub fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {
338        match self {
339            StorageKind::Plain(d) => {
340                state.write_u8(0);
341                state.write(d.as_bytes())
342            }
343            StorageKind::Exotic(o) => o.dyn_hash(state),
344        }
345    }
346}