1#![allow(missing_docs)]
4
5use std::collections::BTreeSet;
6
7use eredu_gguf::GgmlType as GgufType;
8
9use crate::{BlockFp8ScaleEncoding, LinearFormat, StoredDtype};
10
11#[derive(Debug, Clone, Eq, PartialEq)]
13pub struct FusedProjectionSegment {
14 pub semantic: String,
15 pub width: usize,
16}
17
18impl FusedProjectionSegment {
19 pub fn new(semantic: impl Into<String>, width: usize) -> Result<Self, String> {
20 let semantic = semantic.into();
21 if semantic.trim().is_empty() || width == 0 {
22 return Err("fused projection segments require a name and positive width".into());
23 }
24 Ok(Self { semantic, width })
25 }
26}
27
28#[derive(Debug, Clone, Eq, PartialEq)]
31pub struct FusedSegmentedProjectionSchema {
32 input_width: usize,
33 segments: Vec<FusedProjectionSegment>,
34 output_width: usize,
35}
36
37impl FusedSegmentedProjectionSchema {
38 pub fn new(
39 input_width: usize,
40 segments: impl IntoIterator<Item = FusedProjectionSegment>,
41 ) -> Result<Self, String> {
42 if input_width == 0 {
43 return Err("fused projection input width must be positive".into());
44 }
45 let segments = segments.into_iter().collect::<Vec<_>>();
46 if segments.is_empty() {
47 return Err("fused projection requires at least one segment".into());
48 }
49 let mut names = BTreeSet::new();
50 let mut output_width = 0usize;
51 for segment in &segments {
52 if segment.semantic.trim().is_empty()
53 || segment.width == 0
54 || !names.insert(segment.semantic.clone())
55 {
56 return Err("fused projection segments must be positive and uniquely named".into());
57 }
58 output_width = output_width
59 .checked_add(segment.width)
60 .ok_or_else(|| "fused projection output width overflows".to_string())?;
61 }
62 Ok(Self {
63 input_width,
64 segments,
65 output_width,
66 })
67 }
68
69 pub const fn input_width(&self) -> usize {
70 self.input_width
71 }
72
73 pub const fn output_width(&self) -> usize {
74 self.output_width
75 }
76
77 pub fn matrix_shape(&self) -> Vec<usize> {
78 vec![self.output_width, self.input_width]
79 }
80
81 pub fn bias_shape(&self) -> Vec<usize> {
82 vec![self.output_width]
83 }
84
85 pub fn segment_ranges(&self) -> Vec<std::ops::Range<usize>> {
86 let mut start = 0;
87 self.segments
88 .iter()
89 .map(|segment| {
90 let range = start..start + segment.width;
91 start = range.end;
92 range
93 })
94 .collect()
95 }
96}
97
98#[derive(Debug, Clone, Copy, Eq, PartialEq)]
100pub enum DepthwiseKernelAxes {
101 ChannelsSingletonKernel,
103 ChannelsKernelSingleton,
105}
106
107#[derive(Debug, Clone, Copy, Eq, PartialEq)]
109pub struct DepthwiseConvolutionSchema {
110 channels: usize,
111 kernel: usize,
112 storage_axes: DepthwiseKernelAxes,
113 execution_axes: DepthwiseKernelAxes,
114 bias: bool,
115}
116
117impl DepthwiseConvolutionSchema {
118 pub fn new(channels: usize, kernel: usize, bias: bool) -> Result<Self, String> {
119 Self::with_axes(
120 channels,
121 kernel,
122 DepthwiseKernelAxes::ChannelsSingletonKernel,
123 DepthwiseKernelAxes::ChannelsSingletonKernel,
124 bias,
125 )
126 }
127
128 pub fn with_axes(
129 channels: usize,
130 kernel: usize,
131 storage_axes: DepthwiseKernelAxes,
132 execution_axes: DepthwiseKernelAxes,
133 bias: bool,
134 ) -> Result<Self, String> {
135 if channels == 0 || kernel == 0 {
136 return Err("depthwise convolution channels and kernel must be positive".into());
137 }
138 Ok(Self {
139 channels,
140 kernel,
141 storage_axes,
142 execution_axes,
143 bias,
144 })
145 }
146
147 pub fn storage_shape(self) -> Vec<usize> {
148 self.shape(self.storage_axes)
149 }
150
151 pub fn execution_shape(self) -> Vec<usize> {
152 self.shape(self.execution_axes)
153 }
154
155 pub fn bias_shape(self) -> Option<Vec<usize>> {
156 self.bias.then(|| vec![self.channels])
157 }
158
159 pub const fn element_count(self) -> usize {
160 self.channels * self.kernel
161 }
162
163 fn shape(self, axes: DepthwiseKernelAxes) -> Vec<usize> {
164 match axes {
165 DepthwiseKernelAxes::ChannelsSingletonKernel => vec![self.channels, 1, self.kernel],
166 DepthwiseKernelAxes::ChannelsKernelSingleton => vec![self.channels, self.kernel, 1],
167 }
168 }
169}
170
171#[derive(Debug, Clone, Copy, Eq, PartialEq)]
173pub struct RecurrentParameterGroupSchema {
174 pub heads: usize,
175 pub groups: usize,
176 pub head_width: usize,
177 pub state_width: usize,
178}
179
180impl RecurrentParameterGroupSchema {
181 pub fn new(
182 heads: usize,
183 groups: usize,
184 head_width: usize,
185 state_width: usize,
186 ) -> Result<Self, String> {
187 if heads == 0
188 || groups == 0
189 || head_width == 0
190 || state_width == 0
191 || !heads.is_multiple_of(groups)
192 {
193 return Err(
194 "recurrent parameter groups require positive widths and whole groups".into(),
195 );
196 }
197 Ok(Self {
198 heads,
199 groups,
200 head_width,
201 state_width,
202 })
203 }
204
205 pub const fn per_head_shape(self) -> [usize; 1] {
206 [self.heads]
207 }
208
209 pub const fn grouped_state_width(self) -> usize {
210 self.groups * self.state_width
211 }
212
213 pub const fn recurrent_state_shape(self) -> [usize; 3] {
214 [self.heads, self.head_width, self.state_width]
215 }
216}
217
218#[derive(Debug, Clone, Eq, PartialEq)]
220pub struct MatrixScaleNames {
221 pub key: String,
223 pub aliases: Vec<String>,
225}
226
227#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
229pub enum MatrixConstraintError {
230 #[error("quantized matrix {name:?} has scalar shape")]
232 Scalar { name: String },
233 #[error("{detail}")]
235 Invalid { detail: String },
236 #[error("block-FP8 matrix {name:?} requires an explicit scale companion name")]
238 MissingBlockScaleName { name: String },
239}
240
241pub fn matrix_for_linear_format(
247 name: impl Into<String>,
248 aliases: impl IntoIterator<Item = impl Into<String>>,
249 shape: Vec<usize>,
250 format: LinearFormat,
251 scale_companion: Option<MatrixScaleNames>,
252) -> Result<Vec<SafetensorsTensorConstraint>, MatrixConstraintError> {
253 let name = name.into();
254 let aliases = aliases.into_iter().map(Into::into).collect::<Vec<String>>();
255 if format == LinearFormat::Dense {
256 return Ok(vec![SafetensorsTensorConstraint::required(
257 name,
258 shape,
259 StoredDtypeConstraint::Floating,
260 )
261 .with_aliases(aliases)]);
262 }
263 if let LinearFormat::E4M3BlockFp8(fp8) = format {
264 fp8.validate()
265 .map_err(|error| MatrixConstraintError::Invalid {
266 detail: error.to_string(),
267 })?;
268 let scale = scale_companion
269 .ok_or_else(|| MatrixConstraintError::MissingBlockScaleName { name: name.clone() })?;
270 let row_axis = shape
271 .len()
272 .checked_sub(2)
273 .ok_or_else(|| MatrixConstraintError::Scalar { name: name.clone() })?;
274 let column_axis = shape.len() - 1;
275 let rows = *shape
276 .get(row_axis)
277 .ok_or_else(|| MatrixConstraintError::Scalar { name: name.clone() })?;
278 let columns = *shape
279 .get(column_axis)
280 .ok_or_else(|| MatrixConstraintError::Scalar { name: name.clone() })?;
281 let block_rows =
282 usize::try_from(fp8.block_rows).map_err(|_| MatrixConstraintError::Invalid {
283 detail: format!("block-FP8 row geometry for {name:?} exceeds usize"),
284 })?;
285 let block_columns =
286 usize::try_from(fp8.block_columns).map_err(|_| MatrixConstraintError::Invalid {
287 detail: format!("block-FP8 column geometry for {name:?} exceeds usize"),
288 })?;
289 let mut scale_shape = shape.clone();
290 scale_shape[row_axis] = rows.div_ceil(block_rows);
291 scale_shape[column_axis] = columns.div_ceil(block_columns);
292 let scale_dtype = match fp8.scale_encoding {
293 BlockFp8ScaleEncoding::FloatingPoint => StoredDtypeConstraint::Floating,
294 BlockFp8ScaleEncoding::Ue8m0 => StoredDtypeConstraint::Exact(StoredDtype::F8E8M0),
295 };
296 return Ok(vec![
297 SafetensorsTensorConstraint::required(
298 name.clone(),
299 shape,
300 StoredDtypeConstraint::Exact(StoredDtype::F8E4M3),
301 )
302 .with_aliases(aliases),
303 SafetensorsTensorConstraint::required(scale.key, scale_shape, scale_dtype)
304 .with_aliases(scale.aliases)
305 .linear_companion(name.clone(), LinearCompanionKind::Scale),
306 ]);
307 }
308 let quantization = format
309 .weight_quantization()
310 .expect("non-dense, non-FP8 linear formats use packed quantization");
311 let input = *shape
312 .last()
313 .ok_or_else(|| MatrixConstraintError::Scalar { name: name.clone() })?;
314 let bits =
315 usize::try_from(quantization.bits()).map_err(|_| MatrixConstraintError::Invalid {
316 detail: format!("quantization bit width for {name:?} exceeds usize"),
317 })?;
318 let group =
319 usize::try_from(quantization.group_size()).map_err(|_| MatrixConstraintError::Invalid {
320 detail: format!("quantization group size for {name:?} exceeds usize"),
321 })?;
322 let packed_bits = input
323 .checked_mul(bits)
324 .ok_or_else(|| MatrixConstraintError::Invalid {
325 detail: format!("quantized matrix {name:?} packing geometry overflows"),
326 })?;
327 if group == 0
328 || !input.is_multiple_of(group)
329 || !input.is_multiple_of(32)
330 || !packed_bits.is_multiple_of(32)
331 {
332 return Err(MatrixConstraintError::Invalid {
333 detail: format!(
334 "quantized matrix {name:?} input dimension {input} is incompatible with group size {group} and {bits}-bit packing"
335 ),
336 });
337 }
338 let mut packed = shape.clone();
339 *packed.last_mut().expect("matrix shape") = packed_bits / 32;
340 let mut companion = shape;
341 *companion.last_mut().expect("matrix shape") = input / group;
342 let prefix = name.strip_suffix(".weight").unwrap_or(&name).to_string();
343 let companion_dtype = || {
344 StoredDtypeConstraint::OneOf(vec![
345 StoredDtype::F16,
346 StoredDtype::BF16,
347 StoredDtype::F32,
348 StoredDtype::U8,
349 ])
350 };
351 let companion_alias = |component: &str| {
352 aliases
353 .iter()
354 .map(|alias| {
355 let prefix = alias.strip_suffix(".weight").unwrap_or(alias);
356 format!("{prefix}.{component}")
357 })
358 .collect::<Vec<_>>()
359 };
360 let mut constraints = vec![SafetensorsTensorConstraint::required(
361 name.clone(),
362 packed,
363 StoredDtypeConstraint::Exact(StoredDtype::U32),
364 )
365 .with_aliases(aliases.clone())];
366 let scale = scale_companion.unwrap_or_else(|| MatrixScaleNames {
367 key: format!("{prefix}.scales"),
368 aliases: companion_alias("scales"),
369 });
370 constraints.push(
371 SafetensorsTensorConstraint::required(scale.key, companion.clone(), companion_dtype())
372 .with_aliases(scale.aliases)
373 .linear_companion(name.clone(), LinearCompanionKind::Scale),
374 );
375 if quantization.has_biases() {
376 constraints.push(
377 SafetensorsTensorConstraint::required(
378 format!("{prefix}.biases"),
379 companion,
380 companion_dtype(),
381 )
382 .with_aliases(companion_alias("biases"))
383 .linear_companion(name.clone(), LinearCompanionKind::AffineBias),
384 );
385 }
386 Ok(constraints)
387}
388
389#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
391pub enum TensorRequirement {
392 Required,
393 Optional,
394}
395
396#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
398pub enum TensorRole {
399 Tensor,
400 Companion,
401}
402
403#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
405pub enum LinearCompanionKind {
406 Scale,
408 AffineBias,
410}
411
412#[derive(Debug, Clone, Eq, PartialEq)]
414pub struct LinearCompanionConstraint {
415 pub primary: String,
417 pub kind: LinearCompanionKind,
419}
420
421#[derive(Debug, Clone, Eq, PartialEq)]
423pub enum StoredDtypeConstraint {
424 Exact(StoredDtype),
425 OneOf(Vec<StoredDtype>),
426 Floating,
428}
429
430impl StoredDtypeConstraint {
431 pub fn accepts(&self, actual: &StoredDtype) -> bool {
432 match self {
433 Self::Exact(expected) => expected == actual,
434 Self::OneOf(expected) => expected.contains(actual),
435 Self::Floating => matches!(
436 actual,
437 StoredDtype::F16 | StoredDtype::BF16 | StoredDtype::F32
438 ),
439 }
440 }
441
442 fn normalize(&mut self) {
443 if let Self::OneOf(dtypes) = self {
444 dtypes.sort_by_key(|dtype| format!("{dtype:?}"));
445 dtypes.dedup();
446 }
447 }
448}
449
450#[derive(Debug, Clone, Eq, PartialEq)]
452pub struct SafetensorsTensorConstraint {
453 pub key: String,
454 pub aliases: Vec<String>,
456 pub shape: Vec<usize>,
457 pub alternate_shapes: Vec<Vec<usize>>,
459 pub element_count: Option<usize>,
462 pub dtype: StoredDtypeConstraint,
463 pub requirement: TensorRequirement,
464 pub role: TensorRole,
465 pub linear_companion: Option<LinearCompanionConstraint>,
467}
468
469impl SafetensorsTensorConstraint {
470 pub fn required(
471 key: impl Into<String>,
472 shape: impl Into<Vec<usize>>,
473 dtype: StoredDtypeConstraint,
474 ) -> Self {
475 Self {
476 key: key.into(),
477 aliases: Vec::new(),
478 shape: shape.into(),
479 alternate_shapes: Vec::new(),
480 element_count: None,
481 dtype,
482 requirement: TensorRequirement::Required,
483 role: TensorRole::Tensor,
484 linear_companion: None,
485 }
486 }
487
488 pub fn optional(mut self) -> Self {
489 self.requirement = TensorRequirement::Optional;
490 self
491 }
492
493 pub fn with_aliases(mut self, aliases: impl IntoIterator<Item = impl Into<String>>) -> Self {
494 self.aliases = aliases.into_iter().map(Into::into).collect();
495 self
496 }
497
498 pub fn with_element_count(mut self, element_count: usize) -> Self {
499 self.element_count = Some(element_count);
500 self
501 }
502
503 pub fn with_alternate_shapes(
504 mut self,
505 shapes: impl IntoIterator<Item = impl Into<Vec<usize>>>,
506 ) -> Self {
507 self.alternate_shapes = shapes.into_iter().map(Into::into).collect();
508 self
509 }
510
511 pub fn companion(mut self) -> Self {
512 self.role = TensorRole::Companion;
513 self
514 }
515
516 pub fn linear_companion(
518 mut self,
519 primary: impl Into<String>,
520 kind: LinearCompanionKind,
521 ) -> Self {
522 self.role = TensorRole::Companion;
523 self.linear_companion = Some(LinearCompanionConstraint {
524 primary: primary.into(),
525 kind,
526 });
527 self
528 }
529}
530
531#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
533pub enum TensorOperation {
534 Matrix,
535 Vector,
536 Dense,
537 I32,
538 MxFp4Matrix,
539}
540
541#[derive(Debug, Clone, Eq, PartialEq)]
543pub enum GgufTypeConstraint {
544 OperationClass(TensorOperation),
545}
546
547impl GgufTypeConstraint {
548 pub fn accepts(&self, actual: GgufType) -> bool {
549 match self {
550 Self::OperationClass(operation) => gguf_encoding_supported(*operation, actual),
551 }
552 }
553
554 fn normalize(&mut self) {}
555}
556
557pub fn gguf_encoding_supported(operation: TensorOperation, encoding: GgufType) -> bool {
559 match operation {
560 TensorOperation::Vector | TensorOperation::Dense => {
561 matches!(encoding, GgufType::F32 | GgufType::F16 | GgufType::Bf16)
562 }
563 TensorOperation::I32 => encoding == GgufType::I32,
564 TensorOperation::MxFp4Matrix => encoding == GgufType::MxFp4,
565 TensorOperation::Matrix => !matches!(
566 encoding,
567 GgufType::I8
568 | GgufType::I16
569 | GgufType::I32
570 | GgufType::I64
571 | GgufType::F64
572 | GgufType::RemovedIQ4NL4_4
573 | GgufType::RemovedIQ4NL4_8
574 | GgufType::RemovedIQ4NL8_8
575 | GgufType::Unknown(_)
576 ),
577 }
578}
579
580#[derive(Debug, Clone, Eq, PartialEq)]
582pub struct GgufTensorConstraint {
583 pub key: String,
584 pub aliases: Vec<String>,
586 pub shape: Vec<usize>,
587 pub alternate_shapes: Vec<Vec<usize>>,
590 pub element_count: Option<usize>,
593 pub encoding: GgufTypeConstraint,
594 pub requirement: TensorRequirement,
595 pub role: TensorRole,
596}
597
598impl GgufTensorConstraint {
599 pub fn required(
600 key: impl Into<String>,
601 shape: impl Into<Vec<usize>>,
602 encoding: GgufTypeConstraint,
603 ) -> Self {
604 Self {
605 key: key.into(),
606 aliases: Vec::new(),
607 shape: shape.into(),
608 alternate_shapes: Vec::new(),
609 element_count: None,
610 encoding,
611 requirement: TensorRequirement::Required,
612 role: TensorRole::Tensor,
613 }
614 }
615
616 pub fn with_aliases(mut self, aliases: impl IntoIterator<Item = impl Into<String>>) -> Self {
617 self.aliases = aliases.into_iter().map(Into::into).collect();
618 self
619 }
620
621 pub fn with_alternate_shapes(
622 mut self,
623 shapes: impl IntoIterator<Item = impl Into<Vec<usize>>>,
624 ) -> Self {
625 self.alternate_shapes = shapes.into_iter().map(Into::into).collect();
626 self
627 }
628
629 pub fn with_element_count(mut self, element_count: usize) -> Self {
630 self.element_count = Some(element_count);
631 self
632 }
633}
634
635#[derive(Debug, Clone, Eq, PartialEq)]
637pub struct LayoutVariant<T> {
638 pub id: String,
639 pub tensors: Vec<T>,
640 pub discriminator_keys: Vec<String>,
641}
642
643#[derive(Debug, Clone, Eq, PartialEq)]
645pub struct AlternativeLayoutGroup<T> {
646 pub id: String,
647 pub required: bool,
648 pub variants: Vec<LayoutVariant<T>>,
649}
650
651#[derive(Debug, Clone, Eq, PartialEq)]
653pub struct CatalogPolicy {
654 pub strict: bool,
655 pub explicitly_allowed_keys: BTreeSet<String>,
656 pub allowed_prefixes: Vec<String>,
657 pub allowed_suffixes: Vec<String>,
658}
659
660impl CatalogPolicy {
661 pub fn strict() -> Self {
662 Self {
663 strict: true,
664 explicitly_allowed_keys: BTreeSet::new(),
665 allowed_prefixes: Vec::new(),
666 allowed_suffixes: Vec::new(),
667 }
668 }
669
670 pub fn non_strict() -> Self {
671 Self {
672 strict: false,
673 ..Self::strict()
674 }
675 }
676
677 fn normalize(&mut self) {
678 self.allowed_prefixes.sort();
679 self.allowed_prefixes.dedup();
680 self.allowed_suffixes.sort();
681 self.allowed_suffixes.dedup();
682 }
683}
684
685#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
687pub enum CheckpointPlanError {
688 #[error("checkpoint plan identity must not be empty")]
689 EmptyIdentity,
690 #[error("checkpoint plan contains an empty {kind} id")]
691 EmptyId { kind: &'static str },
692 #[error("checkpoint layout group {group:?} has no variants")]
693 EmptyLayoutGroup { group: String },
694 #[error("checkpoint layout variant {variant:?} has no tensors")]
695 EmptyLayoutVariant { variant: String },
696 #[error("checkpoint tensor key must not be empty")]
697 EmptyTensorKey,
698 #[error("checkpoint tensor {key:?} contains an empty physical alias")]
699 EmptyTensorAlias { key: String },
700 #[error("checkpoint tensor {key:?} has invalid shape {shape:?}")]
701 InvalidShape { key: String, shape: Vec<usize> },
702 #[error("checkpoint tensor {key:?} shape element count overflows")]
703 ShapeOverflow { key: String },
704 #[error("checkpoint tensor {key:?} has invalid required element count {element_count}")]
705 InvalidElementCount { key: String, element_count: usize },
706 #[error("checkpoint tensor {key:?} canonical shape contains {shape_elements} elements, but its required element count is {element_count}")]
707 ElementCountMismatch {
708 key: String,
709 shape_elements: usize,
710 element_count: usize,
711 },
712 #[error("checkpoint plan contains duplicate tensor key {key:?}")]
713 DuplicateTensorKey { key: String },
714 #[error("checkpoint layout variant {variant:?} has invalid discriminator {key:?}")]
715 InvalidDiscriminator { variant: String, key: String },
716 #[error("checkpoint plan contains duplicate {kind} id {id:?}")]
717 DuplicateId { kind: &'static str, id: String },
718 #[error("checkpoint catalog policy contains an empty explicitly allowed key")]
719 EmptyAllowedKey,
720 #[error("checkpoint catalog policy contains an empty allowed prefix")]
721 EmptyAllowedPrefix,
722 #[error("checkpoint catalog policy contains an empty allowed suffix")]
723 EmptyAllowedSuffix,
724 #[error("checkpoint tensor {key:?} has an empty encoding alternative set")]
725 EmptyEncodingSet { key: String },
726}
727
728trait PhysicalConstraint {
729 fn key(&self) -> &str;
730 fn aliases(&self) -> &[String];
731 fn aliases_mut(&mut self) -> &mut Vec<String>;
732 fn shape(&self) -> &[usize];
733 fn alternate_shapes(&self) -> &[Vec<usize>];
734 fn element_count(&self) -> Option<usize>;
735 fn normalize(&mut self);
736 fn has_empty_encoding_set(&self) -> bool;
737}
738
739impl PhysicalConstraint for SafetensorsTensorConstraint {
740 fn key(&self) -> &str {
741 &self.key
742 }
743 fn aliases(&self) -> &[String] {
744 &self.aliases
745 }
746 fn aliases_mut(&mut self) -> &mut Vec<String> {
747 &mut self.aliases
748 }
749 fn shape(&self) -> &[usize] {
750 &self.shape
751 }
752 fn alternate_shapes(&self) -> &[Vec<usize>] {
753 &self.alternate_shapes
754 }
755 fn element_count(&self) -> Option<usize> {
756 self.element_count
757 }
758 fn normalize(&mut self) {
759 self.dtype.normalize();
760 self.alternate_shapes.sort();
761 self.alternate_shapes.dedup();
762 self.alternate_shapes.retain(|shape| shape != &self.shape);
763 }
764 fn has_empty_encoding_set(&self) -> bool {
765 matches!(&self.dtype, StoredDtypeConstraint::OneOf(dtypes) if dtypes.is_empty())
766 }
767}
768
769impl PhysicalConstraint for GgufTensorConstraint {
770 fn key(&self) -> &str {
771 &self.key
772 }
773 fn aliases(&self) -> &[String] {
774 &self.aliases
775 }
776 fn aliases_mut(&mut self) -> &mut Vec<String> {
777 &mut self.aliases
778 }
779 fn shape(&self) -> &[usize] {
780 &self.shape
781 }
782 fn alternate_shapes(&self) -> &[Vec<usize>] {
783 &self.alternate_shapes
784 }
785 fn element_count(&self) -> Option<usize> {
786 self.element_count
787 }
788 fn normalize(&mut self) {
789 self.encoding.normalize();
790 self.alternate_shapes.sort();
791 self.alternate_shapes.dedup();
792 self.alternate_shapes.retain(|shape| shape != &self.shape);
793 }
794 fn has_empty_encoding_set(&self) -> bool {
795 false
796 }
797}
798
799fn normalize_plan<T: PhysicalConstraint>(
800 identity: &str,
801 common: &mut [T],
802 groups: &mut [AlternativeLayoutGroup<T>],
803 policy: &mut CatalogPolicy,
804) -> Result<(), CheckpointPlanError> {
805 if identity.trim().is_empty() {
806 return Err(CheckpointPlanError::EmptyIdentity);
807 }
808 let normalize_tensor = |tensor: &mut T| {
809 tensor.normalize();
810 if tensor.key().trim().is_empty() {
811 return Err(CheckpointPlanError::EmptyTensorKey);
812 }
813 tensor.aliases_mut().sort();
814 tensor.aliases_mut().dedup();
815 if tensor.aliases().iter().any(|alias| alias.trim().is_empty()) {
816 return Err(CheckpointPlanError::EmptyTensorAlias {
817 key: tensor.key().into(),
818 });
819 }
820 let shapes = std::iter::once(tensor.shape()).chain(
822 tensor
823 .alternate_shapes()
824 .iter()
825 .map(|shape| shape.as_slice()),
826 );
827 let mut canonical_elements = None;
828 for (index, shape) in shapes.enumerate() {
829 if shape.contains(&0) {
830 return Err(CheckpointPlanError::InvalidShape {
831 key: tensor.key().into(),
832 shape: shape.to_vec(),
833 });
834 }
835 let elements = shape
836 .iter()
837 .try_fold(1usize, |count, dimension| count.checked_mul(*dimension))
838 .ok_or_else(|| CheckpointPlanError::ShapeOverflow {
839 key: tensor.key().into(),
840 })?;
841 if index == 0 {
842 canonical_elements = Some(elements);
843 }
844 }
845 if let Some(element_count) = tensor.element_count() {
846 if element_count == 0 {
847 return Err(CheckpointPlanError::InvalidElementCount {
848 key: tensor.key().into(),
849 element_count,
850 });
851 }
852 if canonical_elements != Some(element_count) {
853 return Err(CheckpointPlanError::ElementCountMismatch {
854 key: tensor.key().into(),
855 shape_elements: canonical_elements.expect("canonical shape was checked"),
856 element_count,
857 });
858 }
859 }
860 if tensor.has_empty_encoding_set() {
861 return Err(CheckpointPlanError::EmptyEncodingSet {
862 key: tensor.key().into(),
863 });
864 }
865 Ok(())
866 };
867 let physical_keys = |tensor: &T| {
868 std::iter::once(tensor.key().to_string())
869 .chain(tensor.aliases().iter().cloned())
870 .collect::<Vec<_>>()
871 };
872 let mut keys = BTreeSet::new();
873 for tensor in common.iter_mut() {
874 normalize_tensor(tensor)?;
875 for physical_key in physical_keys(tensor) {
876 if !keys.insert(physical_key.clone()) {
877 return Err(CheckpointPlanError::DuplicateTensorKey { key: physical_key });
878 }
879 }
880 }
881 common.sort_by(|left, right| left.key().cmp(right.key()));
882
883 let mut group_ids = BTreeSet::new();
884 for group in groups.iter_mut() {
885 if group.id.trim().is_empty() {
886 return Err(CheckpointPlanError::EmptyId {
887 kind: "layout group",
888 });
889 }
890 if !group_ids.insert(group.id.clone()) {
891 return Err(CheckpointPlanError::DuplicateId {
892 kind: "layout group",
893 id: group.id.clone(),
894 });
895 }
896 if group.variants.is_empty() {
897 return Err(CheckpointPlanError::EmptyLayoutGroup {
898 group: group.id.clone(),
899 });
900 }
901 let mut variant_ids = BTreeSet::new();
902 let mut group_keys = BTreeSet::new();
903 for variant in &mut group.variants {
904 if variant.id.trim().is_empty() {
905 return Err(CheckpointPlanError::EmptyId {
906 kind: "layout variant",
907 });
908 }
909 if !variant_ids.insert(variant.id.clone()) {
910 return Err(CheckpointPlanError::DuplicateId {
911 kind: "layout variant",
912 id: variant.id.clone(),
913 });
914 }
915 if variant.tensors.is_empty() {
916 return Err(CheckpointPlanError::EmptyLayoutVariant {
917 variant: variant.id.clone(),
918 });
919 }
920 let mut variant_keys = keys.clone();
921 for tensor in &mut variant.tensors {
922 normalize_tensor(tensor)?;
923 for physical_key in physical_keys(tensor) {
924 if !variant_keys.insert(physical_key.clone()) {
925 return Err(CheckpointPlanError::DuplicateTensorKey { key: physical_key });
926 }
927 group_keys.insert(physical_key);
928 }
929 }
930 variant
931 .tensors
932 .sort_by(|left, right| left.key().cmp(right.key()));
933 if variant.discriminator_keys.is_empty() {
934 variant.discriminator_keys = variant
935 .tensors
936 .iter()
937 .map(|tensor| tensor.key().to_string())
938 .collect();
939 }
940 variant.discriminator_keys.sort();
941 variant.discriminator_keys.dedup();
942 let variant_keys = variant
943 .tensors
944 .iter()
945 .map(|tensor| tensor.key())
946 .collect::<BTreeSet<_>>();
947 if let Some(key) = variant
948 .discriminator_keys
949 .iter()
950 .find(|key| !variant_keys.contains(key.as_str()))
951 {
952 return Err(CheckpointPlanError::InvalidDiscriminator {
953 variant: variant.id.clone(),
954 key: key.clone(),
955 });
956 }
957 }
958 keys.extend(group_keys);
959 group.variants.sort_by(|left, right| left.id.cmp(&right.id));
960 }
961 groups.sort_by(|left, right| left.id.cmp(&right.id));
962 if policy
963 .explicitly_allowed_keys
964 .iter()
965 .any(|key| key.trim().is_empty())
966 {
967 return Err(CheckpointPlanError::EmptyAllowedKey);
968 }
969 if policy
970 .allowed_prefixes
971 .iter()
972 .any(|prefix| prefix.trim().is_empty())
973 {
974 return Err(CheckpointPlanError::EmptyAllowedPrefix);
975 }
976 if policy
977 .allowed_suffixes
978 .iter()
979 .any(|suffix| suffix.trim().is_empty())
980 {
981 return Err(CheckpointPlanError::EmptyAllowedSuffix);
982 }
983 policy.normalize();
984 Ok(())
985}
986
987macro_rules! checkpoint_plan {
988 ($name:ident, $constraint:ty) => {
989 #[derive(Debug, Clone, Eq, PartialEq)]
990 pub struct $name {
991 pub identity: String,
992 pub common_tensors: Vec<$constraint>,
993 pub layout_groups: Vec<AlternativeLayoutGroup<$constraint>>,
994 pub catalog_policy: CatalogPolicy,
995 }
996
997 impl $name {
998 pub fn new(
999 identity: impl Into<String>,
1000 mut common_tensors: Vec<$constraint>,
1001 mut layout_groups: Vec<AlternativeLayoutGroup<$constraint>>,
1002 mut catalog_policy: CatalogPolicy,
1003 ) -> Result<Self, CheckpointPlanError> {
1004 let identity = identity.into();
1005 normalize_plan(
1006 &identity,
1007 &mut common_tensors,
1008 &mut layout_groups,
1009 &mut catalog_policy,
1010 )?;
1011 Ok(Self {
1012 identity,
1013 common_tensors,
1014 layout_groups,
1015 catalog_policy,
1016 })
1017 }
1018 }
1019 };
1020}
1021
1022checkpoint_plan!(SafetensorsCheckpointPlan, SafetensorsTensorConstraint);
1023checkpoint_plan!(GgufCheckpointPlan, GgufTensorConstraint);
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028 use crate::{BlockFp8Format, BlockFp8ScaleEncoding, WeightQuantization};
1029
1030 #[test]
1031 fn block_fp8_matrix_declares_exact_weight_scale_geometry_and_dtype() {
1032 let constraints = matrix_for_linear_format(
1033 "projection.weight",
1034 ["projection.alias.weight"],
1035 vec![8, 257, 129],
1036 LinearFormat::E4M3BlockFp8(
1037 BlockFp8Format::new(128, 128, BlockFp8ScaleEncoding::Ue8m0).unwrap(),
1038 ),
1039 Some(MatrixScaleNames {
1040 key: "projection.weight_scale_inv".into(),
1041 aliases: vec!["projection.alias.weight_scale_inv".into()],
1042 }),
1043 )
1044 .unwrap();
1045 assert_eq!(constraints.len(), 2);
1046 assert_eq!(constraints[0].shape, vec![8, 257, 129]);
1047 assert_eq!(
1048 constraints[0].dtype,
1049 StoredDtypeConstraint::Exact(StoredDtype::F8E4M3)
1050 );
1051 assert_eq!(constraints[1].shape, vec![8, 3, 2]);
1052 assert_eq!(
1053 constraints[1].dtype,
1054 StoredDtypeConstraint::Exact(StoredDtype::F8E8M0)
1055 );
1056 assert_eq!(constraints[1].role, TensorRole::Companion);
1057 }
1058
1059 #[test]
1060 fn block_fp8_matrix_requires_architecture_supplied_scale_identity() {
1061 assert!(matches!(
1062 matrix_for_linear_format(
1063 "projection.weight",
1064 Vec::<String>::new(),
1065 vec![128, 128],
1066 LinearFormat::E4M3BlockFp8(
1067 BlockFp8Format::new(128, 128, BlockFp8ScaleEncoding::FloatingPoint,).unwrap(),
1068 ),
1069 None,
1070 ),
1071 Err(MatrixConstraintError::MissingBlockScaleName { .. })
1072 ));
1073 }
1074
1075 #[test]
1076 fn packed_matrix_accepts_only_declared_physical_aliases() {
1077 let format = LinearFormat::from(WeightQuantization::Affine(
1078 crate::AffineQuantization::new(32, 4).unwrap(),
1079 ));
1080 let constraints = matrix_for_linear_format(
1081 "projection.weight",
1082 ["projection.alias.weight"],
1083 vec![64, 64],
1084 format,
1085 None,
1086 )
1087 .unwrap();
1088
1089 assert_eq!(constraints[0].aliases, ["projection.alias.weight"]);
1090 }
1091
1092 #[test]
1093 fn construction_sorts_and_rejects_duplicate_or_invalid_shapes() {
1094 let tensor = |key: &str, shape| {
1095 SafetensorsTensorConstraint::required(key, shape, StoredDtypeConstraint::Floating)
1096 };
1097 let plan = SafetensorsCheckpointPlan::new(
1098 "stable",
1099 vec![
1100 tensor("z", vec![2]),
1101 tensor("a", vec![1]),
1102 tensor("scalar", vec![]),
1103 ],
1104 Vec::new(),
1105 CatalogPolicy::strict(),
1106 )
1107 .unwrap();
1108 assert_eq!(
1109 plan.common_tensors
1110 .iter()
1111 .map(|tensor| tensor.key.as_str())
1112 .collect::<Vec<_>>(),
1113 ["a", "scalar", "z"]
1114 );
1115 assert!(matches!(
1116 SafetensorsCheckpointPlan::new(
1117 "duplicate",
1118 vec![tensor("a", vec![1]), tensor("a", vec![1])],
1119 Vec::new(),
1120 CatalogPolicy::strict(),
1121 ),
1122 Err(CheckpointPlanError::DuplicateTensorKey { .. })
1123 ));
1124 let aliased = tensor("logical", vec![1]).with_aliases(["physical"]);
1125 assert!(matches!(
1126 SafetensorsCheckpointPlan::new(
1127 "duplicate alias",
1128 vec![aliased, tensor("physical", vec![1])],
1129 Vec::new(),
1130 CatalogPolicy::strict(),
1131 ),
1132 Err(CheckpointPlanError::DuplicateTensorKey { key }) if key == "physical"
1133 ));
1134 assert!(matches!(
1135 SafetensorsCheckpointPlan::new(
1136 "zero",
1137 vec![tensor("a", vec![0])],
1138 Vec::new(),
1139 CatalogPolicy::strict(),
1140 ),
1141 Err(CheckpointPlanError::InvalidShape { .. })
1142 ));
1143 assert!(matches!(
1144 SafetensorsCheckpointPlan::new(
1145 "overflow",
1146 vec![tensor("a", vec![usize::MAX, 2])],
1147 Vec::new(),
1148 CatalogPolicy::strict(),
1149 ),
1150 Err(CheckpointPlanError::ShapeOverflow { .. })
1151 ));
1152 assert!(matches!(
1153 SafetensorsCheckpointPlan::new(
1154 "invalid element count",
1155 vec![tensor("a", vec![2, 2]).with_element_count(3)],
1156 Vec::new(),
1157 CatalogPolicy::strict(),
1158 ),
1159 Err(CheckpointPlanError::ElementCountMismatch { .. })
1160 ));
1161 assert!(matches!(
1162 SafetensorsCheckpointPlan::new(
1163 "zero element count",
1164 vec![tensor("a", vec![2, 2]).with_element_count(0)],
1165 Vec::new(),
1166 CatalogPolicy::strict(),
1167 ),
1168 Err(CheckpointPlanError::InvalidElementCount { .. })
1169 ));
1170 assert!(matches!(
1171 GgufCheckpointPlan::new(
1172 "invalid alternate",
1173 vec![GgufTensorConstraint::required(
1174 "a",
1175 vec![1],
1176 GgufTypeConstraint::OperationClass(TensorOperation::Dense),
1177 )
1178 .with_alternate_shapes([vec![1, 0]])],
1179 Vec::new(),
1180 CatalogPolicy::strict(),
1181 ),
1182 Err(CheckpointPlanError::InvalidShape { .. })
1183 ));
1184 assert!(matches!(
1185 SafetensorsCheckpointPlan::new(
1186 "invalid SafeTensors alternate",
1187 vec![tensor("a", vec![1]).with_alternate_shapes([vec![1, 0]])],
1188 Vec::new(),
1189 CatalogPolicy::strict(),
1190 ),
1191 Err(CheckpointPlanError::InvalidShape { .. })
1192 ));
1193 assert!(matches!(
1194 SafetensorsCheckpointPlan::new(
1195 "empty encoding",
1196 vec![SafetensorsTensorConstraint::required(
1197 "a",
1198 vec![1],
1199 StoredDtypeConstraint::OneOf(Vec::new()),
1200 )],
1201 Vec::new(),
1202 CatalogPolicy::strict(),
1203 ),
1204 Err(CheckpointPlanError::EmptyEncodingSet { .. })
1205 ));
1206 let mut empty_suffix = CatalogPolicy::strict();
1207 empty_suffix.allowed_suffixes.push(" ".into());
1208 assert!(matches!(
1209 SafetensorsCheckpointPlan::new(
1210 "empty allowed suffix",
1211 vec![tensor("a", vec![1])],
1212 Vec::new(),
1213 empty_suffix,
1214 ),
1215 Err(CheckpointPlanError::EmptyAllowedSuffix)
1216 ));
1217
1218 let shared = tensor("shared", vec![1]);
1219 let sibling_shared = SafetensorsCheckpointPlan::new(
1220 "sibling shared key",
1221 Vec::new(),
1222 vec![AlternativeLayoutGroup {
1223 id: "layout".into(),
1224 required: true,
1225 variants: vec![
1226 LayoutVariant {
1227 id: "a".into(),
1228 tensors: vec![tensor("a", vec![1]), shared.clone()],
1229 discriminator_keys: vec!["a".into()],
1230 },
1231 LayoutVariant {
1232 id: "b".into(),
1233 tensors: vec![tensor("b", vec![1]), shared],
1234 discriminator_keys: vec!["b".into()],
1235 },
1236 ],
1237 }],
1238 CatalogPolicy::strict(),
1239 )
1240 .unwrap();
1241 assert_eq!(sibling_shared.layout_groups[0].variants.len(), 2);
1242 }
1243
1244 #[test]
1245 fn hybrid_operator_schemas_freeze_segments_convolution_axes_and_recurrent_groups() {
1246 let projection = FusedSegmentedProjectionSchema::new(
1247 16,
1248 [
1249 FusedProjectionSegment::new("gate", 8).unwrap(),
1250 FusedProjectionSegment::new("state", 6).unwrap(),
1251 FusedProjectionSegment::new("time", 2).unwrap(),
1252 ],
1253 )
1254 .unwrap();
1255 assert_eq!(projection.matrix_shape(), [16, 16]);
1256 assert_eq!(projection.segment_ranges(), [0..8, 8..14, 14..16]);
1257 assert!(FusedSegmentedProjectionSchema::new(
1258 4,
1259 [
1260 FusedProjectionSegment::new("same", 2).unwrap(),
1261 FusedProjectionSegment::new("same", 2).unwrap(),
1262 ]
1263 )
1264 .is_err());
1265
1266 let convolution = DepthwiseConvolutionSchema::with_axes(
1267 12,
1268 3,
1269 DepthwiseKernelAxes::ChannelsKernelSingleton,
1270 DepthwiseKernelAxes::ChannelsSingletonKernel,
1271 true,
1272 )
1273 .unwrap();
1274 assert_eq!(convolution.storage_shape(), [12, 3, 1]);
1275 assert_eq!(convolution.execution_shape(), [12, 1, 3]);
1276 assert_eq!(convolution.bias_shape(), Some(vec![12]));
1277 assert_eq!(convolution.element_count(), 36);
1278
1279 let recurrent = RecurrentParameterGroupSchema::new(8, 2, 4, 3).unwrap();
1280 assert_eq!(recurrent.per_head_shape(), [8]);
1281 assert_eq!(recurrent.grouped_state_width(), 6);
1282 assert_eq!(recurrent.recurrent_state_shape(), [8, 4, 3]);
1283 assert!(RecurrentParameterGroupSchema::new(7, 2, 4, 3).is_err());
1284 }
1285}