tenferro_runtime/runtime/specialization.rs
1use tenferro_tensor::{DType, MemoryKind, Placement, StrideVec};
2
3use super::{
4 InputSignature, InputSpecializationRequirementsError, PrepareError, RankRequirement,
5 SpecializationError, StorageClass,
6};
7
8/// Selects how much placement metadata enters a specialization key.
9///
10/// # Examples
11///
12/// ```
13/// use tenferro_runtime::PlacementSpecialization;
14///
15/// assert_eq!(PlacementSpecialization::None, PlacementSpecialization::None);
16/// ```
17#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub enum PlacementSpecialization {
19 /// Do not specialize on placement.
20 #[default]
21 None,
22 /// Specialize only on the storage class derived from memory kind.
23 StorageClass,
24 /// Specialize on full device placement metadata.
25 Device,
26}
27
28/// Selects how much layout metadata enters a specialization key.
29///
30/// # Examples
31///
32/// ```
33/// use tenferro_runtime::LayoutSpecialization;
34///
35/// assert_eq!(LayoutSpecialization::Class, LayoutSpecialization::Class);
36/// ```
37#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
38pub enum LayoutSpecialization {
39 /// Do not specialize on layout.
40 #[default]
41 None,
42 /// Specialize on layout class.
43 Class,
44 /// Specialize on exact strides.
45 ExactStrides,
46}
47
48/// Per-input specialization requirements.
49///
50/// # Examples
51///
52/// ```
53/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
54/// use tenferro_runtime::InputSpecializationRequirements;
55///
56/// let requirements = InputSpecializationRequirements::builder().build()?;
57/// assert!(!requirements.specializes_dtype());
58/// # Ok(())
59/// # }
60/// ```
61#[derive(Clone, Debug, Eq, Hash, PartialEq)]
62pub struct InputSpecializationRequirements {
63 dtype: bool,
64 rank: bool,
65 concrete_dimensions: Vec<u32>,
66 placement: PlacementSpecialization,
67 layout: LayoutSpecialization,
68 alignment_log2: Option<u8>,
69}
70
71impl InputSpecializationRequirements {
72 /// Return a new requirements builder.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
78 /// use tenferro_runtime::InputSpecializationRequirements;
79 ///
80 /// let mut builder = InputSpecializationRequirements::builder();
81 /// builder.dtype(true);
82 /// assert!(builder.build()?.specializes_dtype());
83 /// # Ok(())
84 /// # }
85 /// ```
86 pub fn builder() -> InputSpecializationRequirementsBuilder {
87 InputSpecializationRequirementsBuilder::new()
88 }
89
90 /// Return whether dtype is specialized.
91 ///
92 /// # Examples
93 ///
94 /// ```
95 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
96 /// use tenferro_runtime::InputSpecializationRequirements;
97 ///
98 /// let mut builder = InputSpecializationRequirements::builder();
99 /// builder.dtype(true);
100 /// assert!(builder.build()?.specializes_dtype());
101 /// # Ok(())
102 /// # }
103 /// ```
104 pub fn specializes_dtype(&self) -> bool {
105 self.dtype
106 }
107
108 /// Return whether rank is specialized.
109 ///
110 /// # Examples
111 ///
112 /// ```
113 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
114 /// use tenferro_runtime::InputSpecializationRequirements;
115 ///
116 /// let mut builder = InputSpecializationRequirements::builder();
117 /// builder.rank(true);
118 /// assert!(builder.build()?.specializes_rank());
119 /// # Ok(())
120 /// # }
121 /// ```
122 pub fn specializes_rank(&self) -> bool {
123 self.rank
124 }
125
126 /// Return concrete-dimension axes.
127 ///
128 /// # Examples
129 ///
130 /// ```
131 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
132 /// use tenferro_runtime::InputSpecializationRequirements;
133 ///
134 /// let mut builder = InputSpecializationRequirements::builder();
135 /// builder.rank(true).concrete_dimensions(vec![1]);
136 /// assert_eq!(builder.build()?.concrete_dimensions(), &[1]);
137 /// # Ok(())
138 /// # }
139 /// ```
140 pub fn concrete_dimensions(&self) -> &[u32] {
141 &self.concrete_dimensions
142 }
143
144 /// Return the placement specialization mode.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
150 /// use tenferro_runtime::{InputSpecializationRequirements, PlacementSpecialization};
151 ///
152 /// let mut builder = InputSpecializationRequirements::builder();
153 /// builder.placement(PlacementSpecialization::Device);
154 /// assert_eq!(builder.build()?.placement(), PlacementSpecialization::Device);
155 /// # Ok(())
156 /// # }
157 /// ```
158 pub fn placement(&self) -> PlacementSpecialization {
159 self.placement
160 }
161
162 /// Return the layout specialization mode.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
168 /// use tenferro_runtime::{InputSpecializationRequirements, LayoutSpecialization};
169 ///
170 /// let mut builder = InputSpecializationRequirements::builder();
171 /// builder.layout(LayoutSpecialization::Class);
172 /// assert_eq!(builder.build()?.layout(), LayoutSpecialization::Class);
173 /// # Ok(())
174 /// # }
175 /// ```
176 pub fn layout(&self) -> LayoutSpecialization {
177 self.layout
178 }
179
180 /// Return the required alignment class, if any.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
186 /// use tenferro_runtime::InputSpecializationRequirements;
187 ///
188 /// let mut builder = InputSpecializationRequirements::builder();
189 /// builder.alignment_log2(Some(2));
190 /// assert_eq!(builder.build()?.alignment_log2(), Some(2));
191 /// # Ok(())
192 /// # }
193 /// ```
194 pub fn alignment_log2(&self) -> Option<u8> {
195 self.alignment_log2
196 }
197}
198
199/// Builder for per-input specialization requirements.
200///
201/// # Examples
202///
203/// ```
204/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
205/// use tenferro_runtime::InputSpecializationRequirementsBuilder;
206///
207/// let mut builder = InputSpecializationRequirementsBuilder::new();
208/// builder.dtype(true);
209/// assert!(builder.build()?.specializes_dtype());
210/// # Ok(())
211/// # }
212/// ```
213#[derive(Clone, Debug, Default, Eq, PartialEq)]
214pub struct InputSpecializationRequirementsBuilder {
215 dtype: bool,
216 rank: bool,
217 concrete_dimensions: Vec<u32>,
218 placement: PlacementSpecialization,
219 layout: LayoutSpecialization,
220 alignment_log2: Option<u8>,
221}
222
223impl InputSpecializationRequirementsBuilder {
224 /// Return a builder with no specialization fields enabled.
225 ///
226 /// # Examples
227 ///
228 /// ```
229 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
230 /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
231 ///
232 /// assert!(!InputSpecializationRequirementsBuilder::new()
233 /// .build()
234 /// ?
235 /// .specializes_dtype());
236 /// # Ok(())
237 /// # }
238 /// ```
239 pub fn new() -> Self {
240 Self::default()
241 }
242
243 /// Set whether dtype should be specialized.
244 ///
245 /// # Examples
246 ///
247 /// ```
248 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
249 /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
250 ///
251 /// let mut builder = InputSpecializationRequirementsBuilder::new();
252 /// builder.dtype(true);
253 /// assert!(builder.build()?.specializes_dtype());
254 /// # Ok(())
255 /// # }
256 /// ```
257 pub fn dtype(&mut self, dtype: bool) -> &mut Self {
258 self.dtype = dtype;
259 self
260 }
261
262 /// Set whether rank should be specialized.
263 ///
264 /// # Examples
265 ///
266 /// ```
267 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
268 /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
269 ///
270 /// let mut builder = InputSpecializationRequirementsBuilder::new();
271 /// builder.rank(true);
272 /// assert!(builder.build()?.specializes_rank());
273 /// # Ok(())
274 /// # }
275 /// ```
276 pub fn rank(&mut self, rank: bool) -> &mut Self {
277 self.rank = rank;
278 self
279 }
280
281 /// Set concrete axes whose dimensions should be specialized.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
287 /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
288 ///
289 /// let mut builder = InputSpecializationRequirementsBuilder::new();
290 /// builder.rank(true).concrete_dimensions(vec![0]);
291 /// assert_eq!(builder.build()?.concrete_dimensions(), &[0]);
292 /// # Ok(())
293 /// # }
294 /// ```
295 pub fn concrete_dimensions(&mut self, axes: impl Into<Vec<u32>>) -> &mut Self {
296 self.concrete_dimensions = axes.into();
297 self
298 }
299
300 /// Set placement specialization.
301 ///
302 /// # Examples
303 ///
304 /// ```
305 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
306 /// use tenferro_runtime::{
307 /// InputSpecializationRequirementsBuilder, PlacementSpecialization,
308 /// };
309 ///
310 /// let mut builder = InputSpecializationRequirementsBuilder::new();
311 /// builder.placement(PlacementSpecialization::StorageClass);
312 /// assert_eq!(builder.build()?.placement(), PlacementSpecialization::StorageClass);
313 /// # Ok(())
314 /// # }
315 /// ```
316 pub fn placement(&mut self, placement: PlacementSpecialization) -> &mut Self {
317 self.placement = placement;
318 self
319 }
320
321 /// Set layout specialization.
322 ///
323 /// # Examples
324 ///
325 /// ```
326 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
327 /// use tenferro_runtime::{InputSpecializationRequirementsBuilder, LayoutSpecialization};
328 ///
329 /// let mut builder = InputSpecializationRequirementsBuilder::new();
330 /// builder.rank(true).layout(LayoutSpecialization::ExactStrides);
331 /// assert_eq!(builder.build()?.layout(), LayoutSpecialization::ExactStrides);
332 /// # Ok(())
333 /// # }
334 /// ```
335 pub fn layout(&mut self, layout: LayoutSpecialization) -> &mut Self {
336 self.layout = layout;
337 self
338 }
339
340 /// Set a required alignment class.
341 ///
342 /// # Examples
343 ///
344 /// ```
345 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
346 /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
347 ///
348 /// let mut builder = InputSpecializationRequirementsBuilder::new();
349 /// builder.alignment_log2(Some(1));
350 /// assert_eq!(builder.build()?.alignment_log2(), Some(1));
351 /// # Ok(())
352 /// # }
353 /// ```
354 pub fn alignment_log2(&mut self, alignment_log2: Option<u8>) -> &mut Self {
355 self.alignment_log2 = alignment_log2;
356 self
357 }
358
359 /// Validate and build per-input requirements.
360 ///
361 /// # Examples
362 ///
363 /// ```
364 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
365 /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
366 ///
367 /// let requirements = InputSpecializationRequirementsBuilder::new().build()?;
368 /// assert!(requirements.concrete_dimensions().is_empty());
369 /// # Ok(())
370 /// # }
371 /// ```
372 ///
373 /// # Errors
374 ///
375 /// Returns [`InputSpecializationRequirementsError`] when the requested
376 /// fields are internally inconsistent or outside finite lattices.
377 pub fn build(
378 self,
379 ) -> Result<InputSpecializationRequirements, InputSpecializationRequirementsError> {
380 validate_unique_axes(&self.concrete_dimensions)?;
381 if !self.rank {
382 if let Some(&axis) = self.concrete_dimensions.first() {
383 return Err(InputSpecializationRequirementsError::RankRequired {
384 reason: RankRequirement::ConcreteAxis { axis },
385 });
386 }
387 if self.layout == LayoutSpecialization::ExactStrides {
388 return Err(InputSpecializationRequirementsError::RankRequired {
389 reason: RankRequirement::ExactStrides,
390 });
391 }
392 }
393 if let Some(alignment_log2) = self.alignment_log2 {
394 if u32::from(alignment_log2) >= usize::BITS {
395 return Err(
396 InputSpecializationRequirementsError::InvalidAlignmentClass { alignment_log2 },
397 );
398 }
399 }
400 Ok(InputSpecializationRequirements {
401 dtype: self.dtype,
402 rank: self.rank,
403 concrete_dimensions: self.concrete_dimensions,
404 placement: self.placement,
405 layout: self.layout,
406 alignment_log2: self.alignment_log2,
407 })
408 }
409}
410
411/// Aggregate specialization requirements for all inputs.
412///
413/// # Examples
414///
415/// ```
416/// use tenferro_runtime::SpecializationRequirements;
417///
418/// assert_eq!(SpecializationRequirements::polymorphic(2).inputs().len(), 2);
419/// ```
420#[derive(Clone, Debug, Eq, Hash, PartialEq)]
421pub struct SpecializationRequirements {
422 inputs: Vec<InputSpecializationRequirements>,
423}
424
425impl SpecializationRequirements {
426 /// Build aggregate requirements from validated input requirements.
427 ///
428 /// # Examples
429 ///
430 /// ```
431 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
432 /// use tenferro_runtime::{InputSpecializationRequirements, SpecializationRequirements};
433 ///
434 /// let input = InputSpecializationRequirements::builder().build()?;
435 /// assert_eq!(SpecializationRequirements::new(vec![input]).inputs().len(), 1);
436 /// # Ok(())
437 /// # }
438 /// ```
439 pub fn new(inputs: impl Into<Vec<InputSpecializationRequirements>>) -> Self {
440 Self {
441 inputs: inputs.into(),
442 }
443 }
444
445 /// Build fully polymorphic requirements for `input_count` inputs.
446 ///
447 /// # Examples
448 ///
449 /// ```
450 /// use tenferro_runtime::SpecializationRequirements;
451 ///
452 /// assert_eq!(SpecializationRequirements::polymorphic(3).inputs().len(), 3);
453 /// ```
454 pub fn polymorphic(input_count: usize) -> Self {
455 let input = InputSpecializationRequirements {
456 dtype: false,
457 rank: false,
458 concrete_dimensions: Vec::new(),
459 placement: PlacementSpecialization::None,
460 layout: LayoutSpecialization::None,
461 alignment_log2: None,
462 };
463 Self {
464 inputs: vec![input; input_count],
465 }
466 }
467
468 /// Return per-input requirements.
469 ///
470 /// # Examples
471 ///
472 /// ```
473 /// use tenferro_runtime::SpecializationRequirements;
474 ///
475 /// assert!(SpecializationRequirements::polymorphic(0).inputs().is_empty());
476 /// ```
477 pub fn inputs(&self) -> &[InputSpecializationRequirements] {
478 &self.inputs
479 }
480
481 /// Project a concrete input signature through these requirements.
482 ///
483 /// # Examples
484 ///
485 /// ```
486 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
487 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
488 ///
489 /// let projection = SpecializationRequirements::polymorphic(0)
490 /// .project(&InputSignature::new(Vec::new()))
491 /// ?;
492 /// assert!(projection.inputs().is_empty());
493 /// # Ok(())
494 /// # }
495 /// ```
496 ///
497 /// # Errors
498 ///
499 /// Returns [`PrepareError::Specialization`] when the signature arity does
500 /// not match, a concrete axis is outside the actual rank, or a required
501 /// alignment class is unavailable.
502 pub fn project(
503 &self,
504 signature: &InputSignature,
505 ) -> Result<SpecializationProjection, PrepareError> {
506 if self.inputs.len() != signature.entries().len() {
507 return Err(PrepareError::Specialization {
508 source: SpecializationError::WrongInputCount {
509 expected: self.inputs.len(),
510 actual: signature.entries().len(),
511 },
512 });
513 }
514
515 let mut inputs = Vec::with_capacity(self.inputs.len());
516 for (input, (requirements, entry)) in
517 self.inputs.iter().zip(signature.entries()).enumerate()
518 {
519 let concrete_dimensions =
520 project_concrete_dimensions(input, requirements, entry.shape())?;
521 let alignment_log2 = project_alignment(input, requirements, entry.alignment_log2())?;
522 inputs.push(InputSpecializationProjection {
523 dtype: requirements.specializes_dtype().then_some(entry.dtype()),
524 rank: requirements
525 .specializes_rank()
526 .then_some(entry.shape().len()),
527 concrete_dimensions,
528 placement: project_placement(requirements, entry.placement()),
529 layout: project_layout(requirements, entry.layout_class().clone(), entry.strides()),
530 alignment_log2,
531 });
532 }
533
534 Ok(SpecializationProjection {
535 requirements: self.clone(),
536 inputs,
537 })
538 }
539
540 /// Return whether `self` is strictly wider than `other`.
541 ///
542 /// # Examples
543 ///
544 /// ```
545 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
546 /// use tenferro_runtime::{InputSpecializationRequirements, SpecializationRequirements};
547 ///
548 /// let polymorphic = SpecializationRequirements::new(vec![
549 /// InputSpecializationRequirements::builder().build()?,
550 /// ]);
551 /// let mut builder = InputSpecializationRequirements::builder();
552 /// builder.dtype(true);
553 /// let dtype = SpecializationRequirements::new(vec![builder.build()?]);
554 /// assert!(polymorphic.strictly_widens(&dtype));
555 /// # Ok(())
556 /// # }
557 /// ```
558 pub fn strictly_widens(&self, other: &Self) -> bool {
559 self.inputs.len() == other.inputs.len()
560 && self
561 .inputs
562 .iter()
563 .zip(&other.inputs)
564 .try_fold(false, |strict, (left, right)| {
565 input_widening(left, right).map(|input_strict| strict || input_strict)
566 })
567 .unwrap_or(false)
568 }
569}
570
571/// Aggregate projection of a concrete signature.
572///
573/// # Examples
574///
575/// ```
576/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
577/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
578///
579/// let projection = SpecializationRequirements::polymorphic(0)
580/// .project(&InputSignature::new(Vec::new()))
581/// ?;
582/// assert!(projection.inputs().is_empty());
583/// # Ok(())
584/// # }
585/// ```
586#[derive(Clone, Debug, Eq, Hash, PartialEq)]
587pub struct SpecializationProjection {
588 requirements: SpecializationRequirements,
589 inputs: Vec<InputSpecializationProjection>,
590}
591
592impl SpecializationProjection {
593 /// Return the requirements that produced this projection.
594 ///
595 /// # Examples
596 ///
597 /// ```
598 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
599 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
600 ///
601 /// let requirements = SpecializationRequirements::polymorphic(0);
602 /// let projection = requirements.project(&InputSignature::new(Vec::new()))?;
603 /// assert_eq!(projection.requirements(), &requirements);
604 /// # Ok(())
605 /// # }
606 /// ```
607 pub fn requirements(&self) -> &SpecializationRequirements {
608 &self.requirements
609 }
610
611 /// Return per-input projections.
612 ///
613 /// # Examples
614 ///
615 /// ```
616 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
617 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
618 ///
619 /// let projection = SpecializationRequirements::polymorphic(0)
620 /// .project(&InputSignature::new(Vec::new()))
621 /// ?;
622 /// assert!(projection.inputs().is_empty());
623 /// # Ok(())
624 /// # }
625 /// ```
626 pub fn inputs(&self) -> &[InputSpecializationProjection] {
627 &self.inputs
628 }
629}
630
631/// Projected specialization fields for one input.
632///
633/// # Examples
634///
635/// ```
636/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
637/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
638///
639/// let projection = SpecializationRequirements::polymorphic(0)
640/// .project(&InputSignature::new(Vec::new()))
641/// ?;
642/// assert!(projection.inputs().is_empty());
643/// # Ok(())
644/// # }
645/// ```
646#[derive(Clone, Debug, Eq, Hash, PartialEq)]
647pub struct InputSpecializationProjection {
648 dtype: Option<DType>,
649 rank: Option<usize>,
650 concrete_dimensions: Vec<(u32, usize)>,
651 placement: Option<PlacementProjection>,
652 layout: Option<LayoutProjection>,
653 alignment_log2: Option<u8>,
654}
655
656impl InputSpecializationProjection {
657 /// Return projected dtype, if specialized.
658 ///
659 /// # Examples
660 ///
661 /// ```
662 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
663 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
664 ///
665 /// let projection = SpecializationRequirements::polymorphic(0)
666 /// .project(&InputSignature::new(Vec::new()))
667 /// ?;
668 /// assert!(projection.inputs().is_empty());
669 /// # Ok(())
670 /// # }
671 /// ```
672 pub fn dtype(&self) -> Option<DType> {
673 self.dtype
674 }
675
676 /// Return projected rank, if specialized.
677 ///
678 /// # Examples
679 ///
680 /// ```
681 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
682 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
683 ///
684 /// let projection = SpecializationRequirements::polymorphic(0)
685 /// .project(&InputSignature::new(Vec::new()))
686 /// ?;
687 /// assert!(projection.inputs().is_empty());
688 /// # Ok(())
689 /// # }
690 /// ```
691 pub fn rank(&self) -> Option<usize> {
692 self.rank
693 }
694
695 /// Return projected concrete dimensions.
696 ///
697 /// # Examples
698 ///
699 /// ```
700 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
701 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
702 ///
703 /// let projection = SpecializationRequirements::polymorphic(0)
704 /// .project(&InputSignature::new(Vec::new()))
705 /// ?;
706 /// assert!(projection.inputs().is_empty());
707 /// # Ok(())
708 /// # }
709 /// ```
710 pub fn concrete_dimensions(&self) -> &[(u32, usize)] {
711 &self.concrete_dimensions
712 }
713
714 /// Return projected placement, if specialized.
715 ///
716 /// # Examples
717 ///
718 /// ```
719 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
720 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
721 ///
722 /// let projection = SpecializationRequirements::polymorphic(0)
723 /// .project(&InputSignature::new(Vec::new()))
724 /// ?;
725 /// assert!(projection.inputs().is_empty());
726 /// # Ok(())
727 /// # }
728 /// ```
729 pub fn placement(&self) -> Option<&PlacementProjection> {
730 self.placement.as_ref()
731 }
732
733 /// Return projected layout, if specialized.
734 ///
735 /// # Examples
736 ///
737 /// ```
738 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
739 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
740 ///
741 /// let projection = SpecializationRequirements::polymorphic(0)
742 /// .project(&InputSignature::new(Vec::new()))
743 /// ?;
744 /// assert!(projection.inputs().is_empty());
745 /// # Ok(())
746 /// # }
747 /// ```
748 pub fn layout(&self) -> Option<&LayoutProjection> {
749 self.layout.as_ref()
750 }
751
752 /// Return projected alignment class, if specialized.
753 ///
754 /// # Examples
755 ///
756 /// ```
757 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
758 /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
759 ///
760 /// let projection = SpecializationRequirements::polymorphic(0)
761 /// .project(&InputSignature::new(Vec::new()))
762 /// ?;
763 /// assert!(projection.inputs().is_empty());
764 /// # Ok(())
765 /// # }
766 /// ```
767 pub fn alignment_log2(&self) -> Option<u8> {
768 self.alignment_log2
769 }
770}
771
772/// Projected placement specialization.
773///
774/// # Examples
775///
776/// ```
777/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
778/// use tenferro_runtime::{PlacementProjection, StorageClass};
779///
780/// let storage = StorageClass::new("tenferro.storage.host")?;
781/// let projection = PlacementProjection::StorageClass(storage.clone());
782/// assert_eq!(projection, PlacementProjection::StorageClass(storage));
783/// # Ok(())
784/// # }
785/// ```
786#[derive(Clone, Debug, Eq, Hash, PartialEq)]
787pub enum PlacementProjection {
788 /// Full device placement metadata.
789 Device(Placement),
790 /// Runtime-created storage class.
791 StorageClass(StorageClass),
792}
793
794/// Projected layout specialization.
795///
796/// # Examples
797///
798/// ```
799/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
800/// use tenferro_runtime::{LayoutClass, LayoutProjection};
801///
802/// let layout = LayoutClass::new("tenferro.layout.strided")?;
803/// let projection = LayoutProjection::Class(layout.clone());
804/// assert_eq!(projection, LayoutProjection::Class(layout));
805/// # Ok(())
806/// # }
807/// ```
808#[derive(Clone, Debug, Eq, Hash, PartialEq)]
809pub enum LayoutProjection {
810 /// Runtime-created layout class.
811 Class(super::LayoutClass),
812 /// Exact element strides.
813 ExactStrides(StrideVec),
814}
815
816fn validate_unique_axes(axes: &[u32]) -> Result<(), InputSpecializationRequirementsError> {
817 for duplicate_index in 0..axes.len() {
818 if let Some(first_index) =
819 (0..duplicate_index).find(|&first_index| axes[first_index] == axes[duplicate_index])
820 {
821 return Err(InputSpecializationRequirementsError::DuplicateAxis {
822 axis: axes[duplicate_index],
823 first_index,
824 duplicate_index,
825 });
826 }
827 }
828 Ok(())
829}
830
831fn project_concrete_dimensions(
832 input: usize,
833 requirements: &InputSpecializationRequirements,
834 shape: &[usize],
835) -> Result<Vec<(u32, usize)>, PrepareError> {
836 let mut projected = Vec::with_capacity(requirements.concrete_dimensions().len());
837 for &axis in requirements.concrete_dimensions() {
838 let axis_index = axis as usize;
839 let Some(&dimension) = shape.get(axis_index) else {
840 return Err(PrepareError::Specialization {
841 source: SpecializationError::AxisOutOfRange {
842 input,
843 axis,
844 rank: shape.len(),
845 },
846 });
847 };
848 projected.push((axis, dimension));
849 }
850 Ok(projected)
851}
852
853fn project_alignment(
854 input: usize,
855 requirements: &InputSpecializationRequirements,
856 actual: Option<u8>,
857) -> Result<Option<u8>, PrepareError> {
858 match (requirements.alignment_log2(), actual) {
859 (None, _) => Ok(None),
860 (Some(required_alignment_log2), None) => Err(PrepareError::Specialization {
861 source: SpecializationError::AlignmentUnavailable {
862 input,
863 required_alignment_log2,
864 },
865 }),
866 (Some(required), Some(actual)) => Ok(Some(required.min(actual))),
867 }
868}
869
870fn project_placement(
871 requirements: &InputSpecializationRequirements,
872 placement: &Placement,
873) -> Option<PlacementProjection> {
874 match requirements.placement() {
875 PlacementSpecialization::None => None,
876 PlacementSpecialization::Device => Some(PlacementProjection::Device(placement.clone())),
877 PlacementSpecialization::StorageClass => Some(PlacementProjection::StorageClass(
878 storage_class(&placement.memory_kind),
879 )),
880 }
881}
882
883fn project_layout(
884 requirements: &InputSpecializationRequirements,
885 layout_class: super::LayoutClass,
886 strides: &[isize],
887) -> Option<LayoutProjection> {
888 match requirements.layout() {
889 LayoutSpecialization::None => None,
890 LayoutSpecialization::Class => Some(LayoutProjection::Class(layout_class)),
891 LayoutSpecialization::ExactStrides => Some(LayoutProjection::ExactStrides(
892 strides.iter().copied().collect(),
893 )),
894 }
895}
896
897fn storage_class(memory_kind: &MemoryKind) -> StorageClass {
898 match memory_kind {
899 MemoryKind::Device => StorageClass::runtime_created("tenferro.storage.device.v1"),
900 MemoryKind::PinnedHost => StorageClass::runtime_created("tenferro.storage.pinned-host.v1"),
901 MemoryKind::UnpinnedHost => {
902 StorageClass::runtime_created("tenferro.storage.unpinned-host.v1")
903 }
904 MemoryKind::Managed => StorageClass::runtime_created("tenferro.storage.managed.v1"),
905 MemoryKind::Other(payload) if payload.is_empty() => {
906 StorageClass::runtime_created("tenferro.storage.other-empty.v1")
907 }
908 MemoryKind::Other(payload) => StorageClass::runtime_created(other_utf8_storage_id(payload)),
909 }
910}
911
912fn other_utf8_storage_id(payload: &str) -> String {
913 let mut value =
914 String::with_capacity("tenferro.storage.other-utf8-.v1".len() + payload.len() * 2);
915 value.push_str("tenferro.storage.other-utf8-");
916 const HEX: &[u8; 16] = b"0123456789abcdef";
917 for byte in payload.as_bytes() {
918 value.push(HEX[(byte >> 4) as usize] as char);
919 value.push(HEX[(byte & 0x0f) as usize] as char);
920 }
921 value.push_str(".v1");
922 value
923}
924
925fn input_widening(
926 left: &InputSpecializationRequirements,
927 right: &InputSpecializationRequirements,
928) -> Option<bool> {
929 let mut strict = false;
930 strict |= bool_widens(left.specializes_dtype(), right.specializes_dtype())?;
931 strict |= bool_widens(left.specializes_rank(), right.specializes_rank())?;
932 strict |= axes_widen(left.concrete_dimensions(), right.concrete_dimensions())?;
933 strict |= placement_widens(left.placement(), right.placement())?;
934 strict |= layout_widens(left.layout(), right.layout())?;
935 strict |= alignment_widens(left.alignment_log2(), right.alignment_log2())?;
936 Some(strict)
937}
938
939fn bool_widens(left: bool, right: bool) -> Option<bool> {
940 match (left, right) {
941 (false, true) => Some(true),
942 (left, right) if left == right => Some(false),
943 _ => None,
944 }
945}
946
947fn axes_widen(left: &[u32], right: &[u32]) -> Option<bool> {
948 if left.iter().all(|axis| right.contains(axis)) {
949 Some(left.len() < right.len())
950 } else {
951 None
952 }
953}
954
955fn placement_widens(left: PlacementSpecialization, right: PlacementSpecialization) -> Option<bool> {
956 (left <= right).then_some(left < right)
957}
958
959fn layout_widens(left: LayoutSpecialization, right: LayoutSpecialization) -> Option<bool> {
960 let left_rank = layout_rank(left);
961 let right_rank = layout_rank(right);
962 (left_rank <= right_rank).then_some(left_rank < right_rank)
963}
964
965fn layout_rank(layout: LayoutSpecialization) -> u8 {
966 match layout {
967 LayoutSpecialization::None => 0,
968 LayoutSpecialization::Class => 1,
969 LayoutSpecialization::ExactStrides => 2,
970 }
971}
972
973fn alignment_widens(left: Option<u8>, right: Option<u8>) -> Option<bool> {
974 match (left, right) {
975 (None, None) => Some(false),
976 (None, Some(_)) => Some(true),
977 (Some(_), None) => None,
978 (Some(left), Some(right)) if left <= right => Some(left < right),
979 _ => None,
980 }
981}