Skip to main content

tenferro_runtime/runtime/
signature.rs

1use std::mem::align_of;
2use std::mem::size_of;
3
4use tenferro_tensor::{
5    AllocationDomainId, DType, Placement, ShapeVec, StrideVec, Tensor, TensorRead, TensorScalar,
6    TensorView, TypedTensor, TypedTensorView,
7};
8
9use super::{InputSignatureError, LayoutClass, PrepareError};
10
11const COMPACT_COL_MAJOR_LAYOUT: &str = "tenferro.layout.compact-col-major.v1";
12const STRIDED_LAYOUT: &str = "tenferro.layout.strided.v1";
13
14/// Value-free metadata signature for a tensor input.
15///
16/// # Examples
17///
18/// ```
19/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
20/// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
21/// use tenferro_tensor::Placement;
22///
23/// let entry = InputSignatureEntry::new(
24///     DType::F64,
25///     [2_usize].into_iter().collect(),
26///     Placement::default(),
27///     LayoutClass::new("tenferro.layout.strided")?,
28///     [1_isize].into_iter().collect(),
29///     Some(3),
30/// )?;
31/// assert_eq!(entry.dtype(), DType::F64);
32/// # Ok(())
33/// # }
34/// ```
35#[derive(Clone, Debug, Eq, Hash, PartialEq)]
36pub struct InputSignatureEntry {
37    dtype: DType,
38    shape: ShapeVec,
39    placement: Placement,
40    layout_class: LayoutClass,
41    strides: StrideVec,
42    alignment_log2: Option<u8>,
43    backend_family: Option<&'static str>,
44    allocation_domain: Option<AllocationDomainId>,
45}
46
47#[derive(Clone, Copy)]
48struct InputPhysicalIdentity {
49    backend_family: Option<&'static str>,
50    allocation_domain: Option<AllocationDomainId>,
51}
52
53impl InputSignatureEntry {
54    /// Build one value-free input signature entry.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
60    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
61    /// use tenferro_tensor::Placement;
62    ///
63    /// let entry = InputSignatureEntry::new(
64    ///     DType::I32,
65    ///     [4_usize].into_iter().collect(),
66    ///     Placement::default(),
67    ///     LayoutClass::new("tenferro.layout.compact")?,
68    ///     [1_isize].into_iter().collect(),
69    ///     None,
70    /// )?;
71    /// assert_eq!(entry.shape(), &[4]);
72    /// # Ok(())
73    /// # }
74    /// ```
75    ///
76    /// # Errors
77    ///
78    /// Returns [`InputSignatureError::ShapeStrideRankMismatch`] when shape and
79    /// stride ranks differ, or [`InputSignatureError::InvalidAlignmentClass`]
80    /// when `alignment_log2` is outside the finite `usize` alignment lattice.
81    pub fn new(
82        dtype: DType,
83        shape: ShapeVec,
84        placement: Placement,
85        layout_class: LayoutClass,
86        strides: StrideVec,
87        alignment_log2: Option<u8>,
88    ) -> Result<Self, InputSignatureError> {
89        validate_entry(&shape, &strides, alignment_log2)?;
90        Ok(Self {
91            dtype,
92            shape,
93            placement,
94            layout_class,
95            strides,
96            alignment_log2,
97            backend_family: None,
98            allocation_domain: None,
99        })
100    }
101
102    fn from_validated_metadata(
103        dtype: DType,
104        shape: ShapeVec,
105        placement: Placement,
106        layout_class: LayoutClass,
107        strides: StrideVec,
108        alignment_log2: Option<u8>,
109        physical_identity: InputPhysicalIdentity,
110    ) -> Self {
111        Self {
112            dtype,
113            shape,
114            placement,
115            layout_class,
116            strides,
117            alignment_log2,
118            backend_family: physical_identity.backend_family,
119            allocation_domain: physical_identity.allocation_domain,
120        }
121    }
122
123    /// Return the dtype component.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
129    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
130    /// use tenferro_tensor::Placement;
131    ///
132    /// let entry = InputSignatureEntry::new(
133    ///     DType::Bool,
134    ///     [1_usize].into_iter().collect(),
135    ///     Placement::default(),
136    ///     LayoutClass::new("tenferro.layout.strided")?,
137    ///     [1_isize].into_iter().collect(),
138    ///     None,
139    /// )?;
140    /// assert_eq!(entry.dtype(), DType::Bool);
141    /// # Ok(())
142    /// # }
143    /// ```
144    pub fn dtype(&self) -> DType {
145        self.dtype
146    }
147
148    /// Return the shape component.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
154    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
155    /// use tenferro_tensor::Placement;
156    ///
157    /// let entry = InputSignatureEntry::new(
158    ///     DType::F64,
159    ///     [2_usize, 3].into_iter().collect(),
160    ///     Placement::default(),
161    ///     LayoutClass::new("tenferro.layout.strided")?,
162    ///     [1_isize, 2].into_iter().collect(),
163    ///     None,
164    /// )?;
165    /// assert_eq!(entry.shape(), &[2, 3]);
166    /// # Ok(())
167    /// # }
168    /// ```
169    pub fn shape(&self) -> &[usize] {
170        &self.shape
171    }
172
173    /// Return the placement metadata component.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
179    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
180    /// use tenferro_tensor::{MemoryKind, Placement};
181    ///
182    /// let entry = InputSignatureEntry::new(
183    ///     DType::F64,
184    ///     [1_usize].into_iter().collect(),
185    ///     Placement::default(),
186    ///     LayoutClass::new("tenferro.layout.strided")?,
187    ///     [1_isize].into_iter().collect(),
188    ///     None,
189    /// )?;
190    /// assert_eq!(entry.placement().memory_kind, MemoryKind::UnpinnedHost);
191    /// # Ok(())
192    /// # }
193    /// ```
194    pub fn placement(&self) -> &Placement {
195        &self.placement
196    }
197
198    /// Return the layout class component.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
204    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
205    /// use tenferro_tensor::Placement;
206    ///
207    /// let layout = LayoutClass::new("tenferro.layout.strided")?;
208    /// let entry = InputSignatureEntry::new(
209    ///     DType::F64,
210    ///     [1_usize].into_iter().collect(),
211    ///     Placement::default(),
212    ///     layout.clone(),
213    ///     [1_isize].into_iter().collect(),
214    ///     None,
215    /// )?;
216    /// assert_eq!(entry.layout_class(), &layout);
217    /// # Ok(())
218    /// # }
219    /// ```
220    pub fn layout_class(&self) -> &LayoutClass {
221        &self.layout_class
222    }
223
224    /// Return the stride metadata component.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
230    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
231    /// use tenferro_tensor::Placement;
232    ///
233    /// let entry = InputSignatureEntry::new(
234    ///     DType::F64,
235    ///     [2_usize].into_iter().collect(),
236    ///     Placement::default(),
237    ///     LayoutClass::new("tenferro.layout.strided")?,
238    ///     [2_isize].into_iter().collect(),
239    ///     None,
240    /// )?;
241    /// assert_eq!(entry.strides(), &[2]);
242    /// # Ok(())
243    /// # }
244    /// ```
245    pub fn strides(&self) -> &[isize] {
246        &self.strides
247    }
248
249    /// Return the known alignment class, if available.
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
255    /// use tenferro_runtime::{DType, InputSignatureEntry, LayoutClass};
256    /// use tenferro_tensor::Placement;
257    ///
258    /// let entry = InputSignatureEntry::new(
259    ///     DType::F64,
260    ///     [1_usize].into_iter().collect(),
261    ///     Placement::default(),
262    ///     LayoutClass::new("tenferro.layout.strided")?,
263    ///     [1_isize].into_iter().collect(),
264    ///     Some(3),
265    /// )?;
266    /// assert_eq!(entry.alignment_log2(), Some(3));
267    /// # Ok(())
268    /// # }
269    /// ```
270    pub fn alignment_log2(&self) -> Option<u8> {
271        self.alignment_log2
272    }
273
274    pub(super) fn backend_family(&self) -> Option<&'static str> {
275        self.backend_family
276    }
277
278    pub(super) fn allocation_domain(&self) -> Option<AllocationDomainId> {
279        self.allocation_domain
280    }
281
282    pub(crate) fn logical_retained_bytes(&self) -> Option<usize> {
283        checked_sum([
284            spilled_bytes::<usize>(self.shape.spilled(), self.shape.len())?,
285            spilled_bytes::<isize>(self.strides.spilled(), self.strides.len())?,
286        ])
287    }
288}
289
290/// Value-free signature of all tensor inputs for one prepare request.
291///
292/// # Examples
293///
294/// ```
295/// use tenferro_runtime::InputSignature;
296///
297/// let signature = InputSignature::new(Vec::new());
298/// assert!(signature.entries().is_empty());
299/// ```
300#[derive(Clone, Debug, Eq, Hash, PartialEq)]
301pub struct InputSignature {
302    entries: Vec<InputSignatureEntry>,
303}
304
305impl InputSignature {
306    /// Build a signature from already prepared entries.
307    ///
308    /// # Examples
309    ///
310    /// ```
311    /// use tenferro_runtime::InputSignature;
312    ///
313    /// let signature = InputSignature::new(Vec::new());
314    /// assert_eq!(signature.entries().len(), 0);
315    /// ```
316    pub fn new(entries: Vec<InputSignatureEntry>) -> Self {
317        Self { entries }
318    }
319
320    /// Build a value-free signature from borrowed tensor reads.
321    ///
322    /// # Examples
323    ///
324    /// ```
325    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
326    /// use tenferro_runtime::{InputSignature, TensorRead, Tensor};
327    ///
328    /// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
329    /// let signature = InputSignature::from_reads(&[TensorRead::from_tensor(&tensor)])?;
330    /// assert_eq!(signature.entries()[0].shape(), &[2]);
331    /// # Ok(())
332    /// # }
333    /// ```
334    ///
335    /// # Errors
336    ///
337    /// Returns [`PrepareError::InputSignature`] with the original typed tensor
338    /// metadata error when shape, stride, or compactness metadata cannot be read.
339    pub fn from_reads(reads: &[TensorRead<'_>]) -> Result<Self, PrepareError> {
340        let mut entries = Vec::with_capacity(reads.len());
341        for (input, read) in reads.iter().enumerate() {
342            let strides = read
343                .strides()
344                .map_err(|source| PrepareError::InputSignature {
345                    source: InputSignatureError::TensorMetadata { input, source },
346                })?;
347            let compact =
348                read.is_col_major_contiguous()
349                    .map_err(|source| PrepareError::InputSignature {
350                        source: InputSignatureError::TensorMetadata { input, source },
351                    })?;
352            let shape = read.shape().iter().copied().collect();
353            entries.push(InputSignatureEntry::from_validated_metadata(
354                read.dtype(),
355                shape,
356                read_placement(read),
357                layout_class(compact),
358                strides.into_iter().collect(),
359                read_alignment_log2(read),
360                InputPhysicalIdentity {
361                    backend_family: read.backend_family(),
362                    allocation_domain: read.allocation_domain(),
363                },
364            ));
365        }
366        Ok(Self { entries })
367    }
368
369    /// Return the per-input entries.
370    ///
371    /// # Examples
372    ///
373    /// ```
374    /// use tenferro_runtime::InputSignature;
375    ///
376    /// assert!(InputSignature::new(Vec::new()).entries().is_empty());
377    /// ```
378    pub fn entries(&self) -> &[InputSignatureEntry] {
379        &self.entries
380    }
381
382    pub(crate) fn logical_retained_bytes(&self) -> Option<usize> {
383        checked_sum([
384            self.entries
385                .len()
386                .checked_mul(size_of::<InputSignatureEntry>())?,
387            checked_sum_options(
388                self.entries
389                    .iter()
390                    .map(InputSignatureEntry::logical_retained_bytes),
391            )?,
392        ])
393    }
394}
395
396fn spilled_bytes<T>(spilled: bool, len: usize) -> Option<usize> {
397    if spilled {
398        len.checked_mul(size_of::<T>())
399    } else {
400        Some(0)
401    }
402}
403
404fn checked_sum(values: impl IntoIterator<Item = usize>) -> Option<usize> {
405    values
406        .into_iter()
407        .try_fold(0usize, |sum, value| sum.checked_add(value))
408}
409
410fn checked_sum_options(values: impl IntoIterator<Item = Option<usize>>) -> Option<usize> {
411    values
412        .into_iter()
413        .try_fold(0usize, |sum, value| sum.checked_add(value?))
414}
415
416fn validate_entry(
417    shape: &[usize],
418    strides: &[isize],
419    alignment_log2: Option<u8>,
420) -> Result<(), InputSignatureError> {
421    if shape.len() != strides.len() {
422        return Err(InputSignatureError::ShapeStrideRankMismatch {
423            rank: shape.len(),
424            stride_count: strides.len(),
425        });
426    }
427    if let Some(alignment_log2) = alignment_log2 {
428        if u32::from(alignment_log2) >= usize::BITS {
429            return Err(InputSignatureError::InvalidAlignmentClass { alignment_log2 });
430        }
431    }
432    Ok(())
433}
434
435pub(super) fn read_placement(read: &TensorRead<'_>) -> Placement {
436    match read {
437        TensorRead::Tensor(tensor) => tensor.placement().clone(),
438        TensorRead::View(view) => view_placement(view),
439    }
440}
441
442fn view_placement(view: &TensorView<'_>) -> Placement {
443    match view {
444        TensorView::F32(view) => view.placement().clone(),
445        TensorView::F64(view) => view.placement().clone(),
446        TensorView::I32(view) => view.placement().clone(),
447        TensorView::I64(view) => view.placement().clone(),
448        TensorView::Bool(view) => view.placement().clone(),
449        TensorView::C32(view) => view.placement().clone(),
450        TensorView::C64(view) => view.placement().clone(),
451    }
452}
453
454fn layout_class(compact: bool) -> LayoutClass {
455    let value = if compact {
456        COMPACT_COL_MAJOR_LAYOUT
457    } else {
458        STRIDED_LAYOUT
459    };
460    LayoutClass::runtime_created(value)
461}
462
463fn read_alignment_log2(read: &TensorRead<'_>) -> Option<u8> {
464    match read {
465        TensorRead::Tensor(tensor) => tensor_alignment_log2(tensor),
466        TensorRead::View(view) => view_alignment_log2(view),
467    }
468}
469
470fn tensor_alignment_log2(tensor: &Tensor) -> Option<u8> {
471    match tensor {
472        Tensor::F32(tensor) => typed_tensor_alignment_log2(tensor),
473        Tensor::F64(tensor) => typed_tensor_alignment_log2(tensor),
474        Tensor::I32(tensor) => typed_tensor_alignment_log2(tensor),
475        Tensor::I64(tensor) => typed_tensor_alignment_log2(tensor),
476        Tensor::Bool(tensor) => typed_tensor_alignment_log2(tensor),
477        Tensor::C32(tensor) => typed_tensor_alignment_log2(tensor),
478        Tensor::C64(tensor) => typed_tensor_alignment_log2(tensor),
479    }
480}
481
482fn typed_tensor_alignment_log2<T: TensorScalar>(tensor: &TypedTensor<T>) -> Option<u8> {
483    if tensor.buffer().is_backend() {
484        return None;
485    }
486    if shape_is_empty(tensor.shape()) {
487        return Some(type_alignment_log2::<T>());
488    }
489    tensor
490        .host_data()
491        .ok()
492        .map(|data| pointer_alignment_log2::<T>(data.as_ptr()))
493}
494
495fn view_alignment_log2(view: &TensorView<'_>) -> Option<u8> {
496    match view {
497        TensorView::F32(view) => typed_view_alignment_log2(view),
498        TensorView::F64(view) => typed_view_alignment_log2(view),
499        TensorView::I32(view) => typed_view_alignment_log2(view),
500        TensorView::I64(view) => typed_view_alignment_log2(view),
501        TensorView::Bool(view) => typed_view_alignment_log2(view),
502        TensorView::C32(view) => typed_view_alignment_log2(view),
503        TensorView::C64(view) => typed_view_alignment_log2(view),
504    }
505}
506
507fn typed_view_alignment_log2<T: 'static>(view: &TypedTensorView<'_, T>) -> Option<u8> {
508    if view.backend_buffer().is_some() {
509        return None;
510    }
511    if shape_is_empty(view.shape()) {
512        return Some(type_alignment_log2::<T>());
513    }
514    view.host_storage().ok().map(|data| {
515        let pointer = data.as_ptr().wrapping_offset(view.offset());
516        pointer_alignment_log2::<T>(pointer)
517    })
518}
519
520fn shape_is_empty(shape: &[usize]) -> bool {
521    shape.contains(&0)
522}
523
524fn type_alignment_log2<T>() -> u8 {
525    align_of::<T>().trailing_zeros().min(usize::BITS - 1) as u8
526}
527
528fn pointer_alignment_log2<T>(pointer: *const T) -> u8 {
529    (pointer as usize)
530        .trailing_zeros()
531        .min(align_of::<T>().trailing_zeros())
532        .min(usize::BITS - 1) as u8
533}