Skip to main content

eredu_checkpoint/
validation.rs

1//! Deterministic header-only evaluation of declarative checkpoint plans.
2
3use crate::StoredDtype;
4
5use std::collections::{BTreeMap, BTreeSet};
6
7use crate::schema::{
8    AlternativeLayoutGroup, CatalogPolicy, GgufCheckpointPlan, GgufTensorConstraint,
9    GgufTypeConstraint, SafetensorsCheckpointPlan, SafetensorsTensorConstraint,
10    StoredDtypeConstraint, TensorRequirement, TensorRole,
11};
12use eredu_gguf::{Checkpoint as GgufCheckpoint, GgmlType as GgufType};
13
14/// Catalog metadata needed for header-only SafeTensors validation.
15#[derive(Debug, Clone, Eq, PartialEq)]
16pub struct CatalogTensorMetadata {
17    /// Logical tensor shape.
18    pub shape: Vec<usize>,
19    /// Stored scalar encoding.
20    pub stored_dtype: StoredDtype,
21}
22
23/// Backend-neutral SafeTensors catalog consumed by declarative validation.
24pub trait SafetensorsCatalog {
25    /// Returns all catalog keys in deterministic order.
26    fn keys(&self) -> Vec<String>;
27    /// Returns metadata without materializing tensor payloads.
28    fn metadata(&self, key: &str) -> Result<CatalogTensorMetadata, String>;
29}
30
31impl SafetensorsCatalog for dyn crate::store::CheckpointSource + '_ {
32    fn keys(&self) -> Vec<String> {
33        self.source_keys()
34    }
35
36    fn metadata(&self, key: &str) -> Result<CatalogTensorMetadata, String> {
37        self.source_metadata(key)
38            .map(|metadata| CatalogTensorMetadata {
39                shape: metadata.logical_shape,
40                stored_dtype: metadata.stored_dtype,
41            })
42            .map_err(|error| error.to_string())
43    }
44}
45
46/// Stable checkpoint validation categories used by inspection and strict load.
47#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
48pub enum CheckpointIssueKind {
49    /// A required tensor is absent.
50    MissingTensor,
51    /// A tensor is not admitted by the selected contract.
52    UnexpectedTensor,
53    /// Mutually exclusive layouts conflict.
54    ConflictingLayout,
55    /// Tensor geometry does not match the contract.
56    ShapeMismatch,
57    /// Stored encoding is unsupported.
58    UnsupportedEncoding,
59    /// Atomic encoding companions are inconsistent.
60    CompanionMismatch,
61    /// Architecture geometry is invalid.
62    InvalidGeometry,
63    /// Validation could not be completed.
64    ValidationUnavailable,
65}
66
67/// One structured checkpoint diagnostic.
68#[derive(Debug, Clone, Eq, PartialEq)]
69pub struct CheckpointIssue {
70    /// Stable diagnostic category.
71    pub kind: CheckpointIssueKind,
72    /// Human-readable detail.
73    pub detail: String,
74    /// Related tensor name, when applicable.
75    pub tensor_name: Option<String>,
76    /// Related format type code, when applicable.
77    pub tensor_type_code: Option<u32>,
78    /// Related metadata key, when applicable.
79    pub metadata_key: Option<String>,
80}
81
82/// Result of exact, fail-closed checkpoint validation.
83#[derive(Debug, Clone, Eq, PartialEq)]
84pub enum CheckpointValidation {
85    /// The catalog exactly satisfies the contract.
86    Exact,
87    /// The catalog violates the contract.
88    Invalid(Vec<CheckpointIssue>),
89    /// Validation was unavailable and loading must fail closed.
90    Unverified(CheckpointIssue),
91}
92
93/// Neutral strict-load failure consumable by backend-specific error types.
94#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
95#[error("strict checkpoint validation failed")]
96pub struct StrictLoadFailure {
97    /// Missing required tensor names.
98    pub missing: Vec<String>,
99    /// Unexpected tensors and other validation details.
100    pub unused: Vec<String>,
101}
102
103impl CheckpointValidation {
104    /// Converts validation into a backend-neutral strict-load result.
105    pub fn into_loader_result(self) -> Result<(), StrictLoadFailure> {
106        match self {
107            Self::Exact => Ok(()),
108            Self::Unverified(issue) => Err(StrictLoadFailure {
109                missing: Vec::new(),
110                unused: vec![issue.detail],
111            }),
112            Self::Invalid(issues) => {
113                let mut missing = issues
114                    .iter()
115                    .filter(|issue| issue.kind == CheckpointIssueKind::MissingTensor)
116                    .filter_map(|issue| issue.tensor_name.clone())
117                    .collect::<Vec<_>>();
118                let mut unused = issues
119                    .into_iter()
120                    .filter(|issue| issue.kind != CheckpointIssueKind::MissingTensor)
121                    .map(|issue| {
122                        if issue.kind == CheckpointIssueKind::UnexpectedTensor {
123                            issue.tensor_name.unwrap_or(issue.detail)
124                        } else {
125                            issue.detail
126                        }
127                    })
128                    .collect::<Vec<_>>();
129                missing.sort();
130                missing.dedup();
131                unused.sort();
132                Err(StrictLoadFailure { missing, unused })
133            }
134        }
135    }
136
137    /// Builds exact or invalid validation from a diagnostic sequence.
138    pub fn from_issues(issues: Vec<CheckpointIssue>) -> Self {
139        if issues.is_empty() {
140            Self::Exact
141        } else {
142            Self::Invalid(issues)
143        }
144    }
145}
146
147/// Builds a missing-tensor diagnostic.
148pub fn missing(name: &str) -> CheckpointIssue {
149    CheckpointIssue {
150        kind: CheckpointIssueKind::MissingTensor,
151        detail: format!("checkpoint is missing required tensor {name:?}"),
152        tensor_name: Some(name.into()),
153        tensor_type_code: None,
154        metadata_key: None,
155    }
156}
157
158/// Builds a tensor shape diagnostic.
159pub fn shape_mismatch(name: &str, expected: &[usize], actual: &[usize]) -> CheckpointIssue {
160    CheckpointIssue {
161        kind: CheckpointIssueKind::ShapeMismatch,
162        detail: format!("tensor {name:?} expected shape {expected:?}, got {actual:?}"),
163        tensor_name: Some(name.into()),
164        tensor_type_code: None,
165        metadata_key: None,
166    }
167}
168
169/// The single physical layout selected from an architecture checkpoint plan.
170///
171/// Loaders consume this value through `ContractWeightStore`; they cannot read
172/// aliases from an unselected layout or tensors that were not admitted by the
173/// architecture contract.
174#[derive(Debug, Clone, Eq, PartialEq)]
175pub struct ResolvedCheckpointPlan {
176    identity: String,
177    source_keys: BTreeSet<String>,
178    unclaimed_keys: BTreeSet<String>,
179}
180
181impl ResolvedCheckpointPlan {
182    /// Returns the architecture checkpoint-plan identity.
183    pub fn identity(&self) -> &str {
184        &self.identity
185    }
186
187    /// Returns the exact selected physical source names.
188    pub fn source_keys(&self) -> &BTreeSet<String> {
189        &self.source_keys
190    }
191
192    /// Returns catalog names admitted but not claimed by a non-strict plan.
193    pub fn unclaimed_keys(&self) -> &BTreeSet<String> {
194        &self.unclaimed_keys
195    }
196
197    /// Projects an admitted resolution onto an exact subset of its claimed sources.
198    ///
199    /// This is used when an architecture splits one already-admitted artifact into
200    /// independently owned components. The projection cannot introduce a source
201    /// that was not selected by the original header admission. Catalog keys that
202    /// were already unclaimed remain unclaimed; ownership of omitted claimed keys
203    /// must be retained by another typed component of the split artifact.
204    pub fn project_claimed_sources(
205        &self,
206        identity: impl Into<String>,
207        source_keys: BTreeSet<String>,
208    ) -> Result<Self, String> {
209        if let Some(source) = source_keys
210            .iter()
211            .find(|source| !self.source_keys.contains(*source))
212        {
213            return Err(format!(
214                "checkpoint projection introduced unadmitted source {source:?}"
215            ));
216        }
217        Ok(Self {
218            identity: identity.into(),
219            source_keys,
220            unclaimed_keys: self.unclaimed_keys.clone(),
221        })
222    }
223}
224
225#[derive(Debug, Clone, Eq, PartialEq)]
226struct PhysicalMetadata<E> {
227    shape: Vec<usize>,
228    encoding: E,
229}
230
231trait Constraint<E> {
232    fn key(&self) -> &str;
233    fn aliases(&self) -> &[String];
234    fn shape(&self) -> &[usize];
235    fn alternate_shapes(&self) -> &[Vec<usize>];
236    fn element_count(&self) -> Option<usize>;
237    fn requirement(&self) -> TensorRequirement;
238    fn role(&self) -> TensorRole;
239    fn accepts(&self, encoding: &E) -> bool;
240    fn encoding_detail(&self) -> String;
241    fn unsupported_detail(&self, identity: &str, actual: &E) -> String;
242    fn type_code(&self, _actual: &E) -> Option<u32> {
243        None
244    }
245}
246
247impl Constraint<StoredDtype> for SafetensorsTensorConstraint {
248    fn key(&self) -> &str {
249        &self.key
250    }
251    fn aliases(&self) -> &[String] {
252        &self.aliases
253    }
254    fn shape(&self) -> &[usize] {
255        &self.shape
256    }
257    fn alternate_shapes(&self) -> &[Vec<usize>] {
258        &self.alternate_shapes
259    }
260    fn element_count(&self) -> Option<usize> {
261        self.element_count
262    }
263    fn requirement(&self) -> TensorRequirement {
264        self.requirement
265    }
266    fn role(&self) -> TensorRole {
267        self.role
268    }
269    fn accepts(&self, encoding: &StoredDtype) -> bool {
270        self.dtype.accepts(encoding)
271    }
272    fn encoding_detail(&self) -> String {
273        match &self.dtype {
274            StoredDtypeConstraint::Exact(dtype) => format!("{dtype:?}"),
275            StoredDtypeConstraint::OneOf(dtypes) => format!("one of {dtypes:?}"),
276            StoredDtypeConstraint::Floating => "F16, BF16, or F32".into(),
277        }
278    }
279    fn unsupported_detail(&self, _identity: &str, actual: &StoredDtype) -> String {
280        format!(
281            "tensor {:?} uses unsupported SafeTensors dtype {actual:?}; expected {}",
282            self.key,
283            self.encoding_detail()
284        )
285    }
286}
287
288impl Constraint<GgufType> for GgufTensorConstraint {
289    fn key(&self) -> &str {
290        &self.key
291    }
292    fn aliases(&self) -> &[String] {
293        &self.aliases
294    }
295    fn shape(&self) -> &[usize] {
296        &self.shape
297    }
298    fn alternate_shapes(&self) -> &[Vec<usize>] {
299        &self.alternate_shapes
300    }
301    fn element_count(&self) -> Option<usize> {
302        self.element_count
303    }
304    fn requirement(&self) -> TensorRequirement {
305        self.requirement
306    }
307    fn role(&self) -> TensorRole {
308        self.role
309    }
310    fn accepts(&self, encoding: &GgufType) -> bool {
311        self.encoding.accepts(*encoding)
312    }
313    fn encoding_detail(&self) -> String {
314        let GgufTypeConstraint::OperationClass(operation) = &self.encoding;
315        format!("a {operation:?} operation")
316    }
317    fn unsupported_detail(&self, identity: &str, actual: &GgufType) -> String {
318        format!(
319            "GGUF tensor {:?} uses {actual:?} (type {}) for {}, which the {identity} loader does not support",
320            self.key,
321            actual.code(),
322            self.encoding_detail()
323        )
324    }
325    fn type_code(&self, actual: &GgufType) -> Option<u32> {
326        Some(actual.code())
327    }
328}
329
330/// Validates a SafeTensors store without materializing payloads.
331pub fn validate_safetensors_plan(
332    store: &(impl SafetensorsCatalog + ?Sized),
333    plan: &SafetensorsCheckpointPlan,
334) -> CheckpointValidation {
335    let mut catalog = BTreeMap::new();
336    let mut metadata_issues = Vec::new();
337    for key in store.keys() {
338        match store.metadata(&key) {
339            Ok(CatalogTensorMetadata {
340                shape,
341                stored_dtype,
342                ..
343            }) => {
344                catalog.insert(
345                    key,
346                    PhysicalMetadata {
347                        shape,
348                        encoding: stored_dtype,
349                    },
350                );
351            }
352            Err(error) => metadata_issues.push(metadata_failure(&key, error)),
353        }
354    }
355    let mut issues = validate_catalog(
356        &catalog,
357        &plan.identity,
358        &plan.common_tensors,
359        &plan.layout_groups,
360        &plan.catalog_policy,
361    );
362    metadata_issues.append(&mut issues);
363    CheckpointValidation::from_issues(metadata_issues)
364}
365
366/// Resolves and validates the one SafeTensors layout that loading may consume.
367pub fn resolve_safetensors_plan(
368    store: &(impl SafetensorsCatalog + ?Sized),
369    plan: &SafetensorsCheckpointPlan,
370) -> Result<ResolvedCheckpointPlan, CheckpointValidation> {
371    let mut catalog = BTreeMap::new();
372    let mut metadata_issues = Vec::new();
373    for key in store.keys() {
374        match store.metadata(&key) {
375            Ok(CatalogTensorMetadata {
376                shape,
377                stored_dtype,
378                ..
379            }) => {
380                catalog.insert(
381                    key,
382                    PhysicalMetadata {
383                        shape,
384                        encoding: stored_dtype,
385                    },
386                );
387            }
388            Err(error) => metadata_issues.push(metadata_failure(&key, error)),
389        }
390    }
391    resolve_catalog(
392        &catalog,
393        &plan.identity,
394        &plan.common_tensors,
395        &plan.layout_groups,
396        &plan.catalog_policy,
397        metadata_issues,
398    )
399}
400
401/// Validates a GGUF catalog without decoding tensor payloads.
402pub fn validate_gguf_plan(
403    checkpoint: &GgufCheckpoint,
404    plan: &GgufCheckpointPlan,
405) -> CheckpointValidation {
406    let catalog = gguf_catalog(checkpoint);
407    CheckpointValidation::from_issues(validate_catalog(
408        &catalog,
409        &plan.identity,
410        &plan.common_tensors,
411        &plan.layout_groups,
412        &plan.catalog_policy,
413    ))
414}
415
416/// Resolves and validates the one GGUF layout that loading may consume.
417pub fn resolve_gguf_plan(
418    checkpoint: &GgufCheckpoint,
419    plan: &GgufCheckpointPlan,
420) -> Result<ResolvedCheckpointPlan, CheckpointValidation> {
421    resolve_catalog(
422        &gguf_catalog(checkpoint),
423        &plan.identity,
424        &plan.common_tensors,
425        &plan.layout_groups,
426        &plan.catalog_policy,
427        Vec::new(),
428    )
429}
430
431fn gguf_catalog(checkpoint: &GgufCheckpoint) -> BTreeMap<String, PhysicalMetadata<GgufType>> {
432    checkpoint
433        .tensors()
434        .map(|tensor| {
435            let descriptor = tensor.descriptor();
436            (
437                descriptor.name.clone(),
438                PhysicalMetadata {
439                    shape: descriptor
440                        .row_major_shape()
441                        .into_iter()
442                        .map(|dimension| usize::try_from(dimension).unwrap_or(usize::MAX))
443                        .collect(),
444                    encoding: descriptor.ggml_type,
445                },
446            )
447        })
448        .collect()
449}
450
451fn resolve_catalog<E, T>(
452    catalog: &BTreeMap<String, PhysicalMetadata<E>>,
453    identity: &str,
454    common: &[T],
455    groups: &[AlternativeLayoutGroup<T>],
456    policy: &CatalogPolicy,
457    mut issues: Vec<CheckpointIssue>,
458) -> Result<ResolvedCheckpointPlan, CheckpointValidation>
459where
460    E: std::fmt::Debug,
461    T: Constraint<E>,
462{
463    issues.extend(validate_catalog(catalog, identity, common, groups, policy));
464    if !issues.is_empty() {
465        return Err(CheckpointValidation::from_issues(issues));
466    }
467
468    let mut source_keys = BTreeSet::new();
469    let mut select_constraint = |constraint: &T| {
470        if let Some(key) = std::iter::once(constraint.key())
471            .chain(constraint.aliases().iter().map(String::as_str))
472            .find(|key| catalog.contains_key(*key))
473        {
474            source_keys.insert(key.to_string());
475        }
476    };
477    for constraint in common {
478        select_constraint(constraint);
479    }
480
481    for group in groups {
482        if let Some(variant) = group.variants.iter().find(|variant| {
483            variant
484                .discriminator_keys
485                .iter()
486                .all(|key| discriminator_present(catalog, variant, key))
487        }) {
488            for constraint in &variant.tensors {
489                select_constraint(constraint);
490            }
491        }
492    }
493
494    let unclaimed_keys = if policy.strict {
495        BTreeSet::new()
496    } else {
497        catalog
498            .keys()
499            .filter(|key| !source_keys.contains(*key))
500            .filter(|key| !policy.explicitly_allowed_keys.contains(*key))
501            .filter(|key| {
502                !policy
503                    .allowed_prefixes
504                    .iter()
505                    .any(|prefix| key.starts_with(prefix))
506            })
507            .filter(|key| {
508                !policy
509                    .allowed_suffixes
510                    .iter()
511                    .any(|suffix| key.ends_with(suffix))
512            })
513            .cloned()
514            .collect()
515    };
516
517    Ok(ResolvedCheckpointPlan {
518        identity: identity.into(),
519        source_keys,
520        unclaimed_keys,
521    })
522}
523
524/// Validates that architecture-supplied tensor pairs use identical encodings.
525pub fn validate_matching_gguf_encodings(
526    checkpoint: &GgufCheckpoint,
527    pairs: impl IntoIterator<Item = (String, String)>,
528    label: &str,
529) -> Vec<CheckpointIssue> {
530    validate_gguf_encoding_pairs(checkpoint, pairs, label)
531}
532
533fn validate_gguf_encoding_pairs(
534    checkpoint: &GgufCheckpoint,
535    pairs: impl IntoIterator<Item = (String, String)>,
536    label: &str,
537) -> Vec<CheckpointIssue> {
538    let catalog = checkpoint
539        .tensors()
540        .map(|tensor| (tensor.descriptor().name.as_str(), tensor))
541        .collect::<BTreeMap<_, _>>();
542    let mut issues = Vec::new();
543    for (gate_name, up_name) in pairs {
544        let (Some(gate), Some(up)) = (
545            catalog.get(gate_name.as_str()),
546            catalog.get(up_name.as_str()),
547        ) else {
548            continue;
549        };
550        let gate_type = gate.descriptor().ggml_type;
551        let up_type = up.descriptor().ggml_type;
552        let compatible = gate_type == up_type
553            && gate.affine() == up.affine()
554            && gate.is_mxfp4() == up.is_mxfp4();
555        if !compatible {
556            issues.push(CheckpointIssue {
557                kind: CheckpointIssueKind::CompanionMismatch,
558                detail: format!(
559                    "{label} paired expert tensors {gate_name:?} and {up_name:?} use incompatible encodings {:?} and {:?}",
560                    gate_type, up_type
561                ),
562                tensor_name: Some(gate_name),
563                tensor_type_code: Some(gate_type.code()),
564                metadata_key: None,
565            });
566        }
567    }
568    issues
569}
570
571fn validate_catalog<E, T>(
572    catalog: &BTreeMap<String, PhysicalMetadata<E>>,
573    identity: &str,
574    common: &[T],
575    groups: &[AlternativeLayoutGroup<T>],
576    policy: &CatalogPolicy,
577) -> Vec<CheckpointIssue>
578where
579    E: std::fmt::Debug,
580    T: Constraint<E>,
581{
582    let mut issues = Vec::new();
583    let mut accounted = BTreeSet::new();
584    for constraint in common {
585        account_constraint(&mut accounted, constraint);
586        validate_constraint(catalog, identity, constraint, &mut issues);
587    }
588
589    for group in groups {
590        let mut present = Vec::new();
591        let mut partial = Vec::new();
592        for variant in &group.variants {
593            let count = variant
594                .discriminator_keys
595                .iter()
596                .filter(|key| discriminator_present(catalog, variant, key))
597                .count();
598            if count == variant.discriminator_keys.len() {
599                present.push(variant);
600            } else if count != 0 {
601                partial.push((variant, count));
602            }
603        }
604
605        for (variant, count) in &partial {
606            issues.push(CheckpointIssue {
607                kind: CheckpointIssueKind::ConflictingLayout,
608                detail: format!(
609                    "checkpoint plan {:?} layout group {:?} has partially present variant {:?}: {count}/{} discriminator tensors are present",
610                    identity,
611                    group.id,
612                    variant.id,
613                    variant.discriminator_keys.len()
614                ),
615                tensor_name: variant
616                    .discriminator_keys
617                    .iter()
618                    .find(|key| !discriminator_present(catalog, variant, key))
619                    .cloned(),
620                tensor_type_code: None,
621                metadata_key: None,
622            });
623        }
624
625        if !present.is_empty() && !partial.is_empty() {
626            issues.push(CheckpointIssue {
627                kind: CheckpointIssueKind::ConflictingLayout,
628                detail: format!(
629                    "checkpoint plan {:?} layout group {:?} mixes present variants {:?} with partially present variants {:?}",
630                    identity,
631                    group.id,
632                    present
633                        .iter()
634                        .map(|variant| variant.id.as_str())
635                        .collect::<Vec<_>>(),
636                    partial
637                        .iter()
638                        .map(|(variant, _)| variant.id.as_str())
639                        .collect::<Vec<_>>()
640                ),
641                tensor_name: present
642                    .first()
643                    .and_then(|variant| variant.discriminator_keys.first())
644                    .cloned(),
645                tensor_type_code: None,
646                metadata_key: None,
647            });
648        }
649
650        if present.len() > 1 {
651            issues.push(CheckpointIssue {
652                kind: CheckpointIssueKind::ConflictingLayout,
653                detail: format!(
654                    "checkpoint plan {:?} layout group {:?} has conflicting variants {:?}",
655                    identity,
656                    group.id,
657                    present
658                        .iter()
659                        .map(|variant| variant.id.as_str())
660                        .collect::<Vec<_>>()
661                ),
662                tensor_name: present
663                    .get(1)
664                    .and_then(|variant| variant.discriminator_keys.first())
665                    .cloned(),
666                tensor_type_code: None,
667                metadata_key: None,
668            });
669        } else if present.is_empty() && partial.is_empty() && group.required {
670            let missing_key = group
671                .variants
672                .first()
673                .and_then(|variant| variant.discriminator_keys.first())
674                .cloned();
675            issues.push(CheckpointIssue {
676                kind: CheckpointIssueKind::MissingTensor,
677                detail: format!(
678                    "checkpoint plan {:?} has no matching variant for required layout group {:?}",
679                    identity, group.id
680                ),
681                tensor_name: missing_key,
682                tensor_type_code: None,
683                metadata_key: None,
684            });
685        }
686
687        for variant in present {
688            for constraint in &variant.tensors {
689                account_constraint(&mut accounted, constraint);
690                validate_constraint(catalog, identity, constraint, &mut issues);
691            }
692        }
693        for (variant, _) in partial {
694            for constraint in &variant.tensors {
695                if constraint_present(catalog, constraint) {
696                    account_constraint(&mut accounted, constraint);
697                }
698                validate_constraint(catalog, identity, constraint, &mut issues);
699            }
700        }
701    }
702
703    if policy.strict {
704        for key in catalog.keys() {
705            if accounted.contains(key)
706                || policy.explicitly_allowed_keys.contains(key)
707                || policy
708                    .allowed_prefixes
709                    .iter()
710                    .any(|prefix| key.starts_with(prefix))
711                || policy
712                    .allowed_suffixes
713                    .iter()
714                    .any(|suffix| key.ends_with(suffix))
715            {
716                continue;
717            }
718            issues.push(CheckpointIssue {
719                kind: CheckpointIssueKind::UnexpectedTensor,
720                detail: format!("{identity} catalog contains unexpected tensor {key:?}"),
721                tensor_name: Some(key.clone()),
722                tensor_type_code: None,
723                metadata_key: None,
724            });
725        }
726    }
727    issues
728}
729
730fn constraint_present<E, T: Constraint<E>>(
731    catalog: &BTreeMap<String, PhysicalMetadata<E>>,
732    constraint: &T,
733) -> bool {
734    std::iter::once(constraint.key())
735        .chain(constraint.aliases().iter().map(String::as_str))
736        .any(|key| catalog.contains_key(key))
737}
738
739fn discriminator_present<E, T: Constraint<E>>(
740    catalog: &BTreeMap<String, PhysicalMetadata<E>>,
741    variant: &crate::schema::LayoutVariant<T>,
742    key: &str,
743) -> bool {
744    variant
745        .tensors
746        .iter()
747        .find(|constraint| constraint.key() == key)
748        .map_or_else(
749            || catalog.contains_key(key),
750            |constraint| constraint_present(catalog, constraint),
751        )
752}
753
754fn account_constraint<E, T: Constraint<E>>(accounted: &mut BTreeSet<String>, constraint: &T) {
755    accounted.insert(constraint.key().to_string());
756    accounted.extend(constraint.aliases().iter().cloned());
757}
758
759fn validate_constraint<E: std::fmt::Debug, T: Constraint<E>>(
760    catalog: &BTreeMap<String, PhysicalMetadata<E>>,
761    identity: &str,
762    constraint: &T,
763    issues: &mut Vec<CheckpointIssue>,
764) {
765    let present = std::iter::once(constraint.key())
766        .chain(constraint.aliases().iter().map(String::as_str))
767        .filter_map(|key| catalog.get(key).map(|metadata| (key, metadata)))
768        .collect::<Vec<_>>();
769    if present.len() > 1 {
770        issues.push(CheckpointIssue {
771            kind: CheckpointIssueKind::ConflictingLayout,
772            detail: format!(
773                "checkpoint plan {identity:?} contains multiple physical aliases for logical tensor {:?}: {:?}",
774                constraint.key(),
775                present.iter().map(|(key, _)| *key).collect::<Vec<_>>()
776            ),
777            tensor_name: present.get(1).map(|(key, _)| (*key).to_string()),
778            tensor_type_code: None,
779            metadata_key: None,
780        });
781        return;
782    }
783    let Some((actual_key, actual)) = present.first().copied() else {
784        if constraint.requirement() == TensorRequirement::Required {
785            if constraint.role() == TensorRole::Companion {
786                issues.push(companion_issue(
787                    constraint.key(),
788                    format!(
789                        "checkpoint is missing required companion tensor {:?}",
790                        constraint.key()
791                    ),
792                ));
793            } else {
794                issues.push(missing(constraint.key()));
795            }
796        }
797        return;
798    };
799
800    if constraint.role() == TensorRole::Companion
801        && (!accepts_shape(constraint, &actual.shape) || !constraint.accepts(&actual.encoding))
802    {
803        issues.push(companion_issue(
804            actual_key,
805            format!(
806                "companion tensor {:?} expected shape {:?} and {}, got {:?} {:?}",
807                actual_key,
808                constraint.shape(),
809                constraint.encoding_detail(),
810                actual.shape,
811                actual.encoding
812            ),
813        ));
814        return;
815    }
816
817    if !accepts_shape(constraint, &actual.shape) {
818        if let Some(element_count) = constraint.element_count() {
819            issues.push(CheckpointIssue {
820                kind: CheckpointIssueKind::ShapeMismatch,
821                detail: format!(
822                    "tensor {actual_key:?} must contain {element_count} elements for the loader transform, got {:?}",
823                    actual.shape
824                ),
825                tensor_name: Some(actual_key.into()),
826                tensor_type_code: constraint.type_code(&actual.encoding),
827                metadata_key: None,
828            });
829        } else if constraint.alternate_shapes().is_empty() {
830            issues.push(shape_mismatch(
831                actual_key,
832                constraint.shape(),
833                &actual.shape,
834            ));
835        } else {
836            let expected = std::iter::once(constraint.shape())
837                .chain(
838                    constraint
839                        .alternate_shapes()
840                        .iter()
841                        .map(|shape| shape.as_slice()),
842                )
843                .collect::<Vec<_>>();
844            issues.push(CheckpointIssue {
845                kind: CheckpointIssueKind::ShapeMismatch,
846                detail: format!(
847                    "tensor {actual_key:?} expected one of shapes {expected:?}, got {:?}",
848                    actual.shape
849                ),
850                tensor_name: Some(actual_key.into()),
851                tensor_type_code: None,
852                metadata_key: None,
853            });
854        }
855    }
856    if !constraint.accepts(&actual.encoding) {
857        issues.push(CheckpointIssue {
858            kind: CheckpointIssueKind::UnsupportedEncoding,
859            detail: constraint.unsupported_detail(identity, &actual.encoding),
860            tensor_name: Some(actual_key.into()),
861            tensor_type_code: constraint.type_code(&actual.encoding),
862            metadata_key: None,
863        });
864    }
865}
866
867fn accepts_shape<E, T: Constraint<E>>(constraint: &T, actual: &[usize]) -> bool {
868    if let Some(element_count) = constraint.element_count() {
869        return actual
870            .iter()
871            .try_fold(1usize, |count, dimension| count.checked_mul(*dimension))
872            == Some(element_count);
873    }
874    actual == constraint.shape()
875        || constraint
876            .alternate_shapes()
877            .iter()
878            .any(|shape| shape == actual)
879}
880
881fn companion_issue(name: &str, detail: String) -> CheckpointIssue {
882    CheckpointIssue {
883        kind: CheckpointIssueKind::CompanionMismatch,
884        detail,
885        tensor_name: Some(name.into()),
886        tensor_type_code: None,
887        metadata_key: Some("quantization".into()),
888    }
889}
890
891fn metadata_failure(name: &str, error: String) -> CheckpointIssue {
892    CheckpointIssue {
893        kind: CheckpointIssueKind::ConflictingLayout,
894        detail: format!("could not validate tensor {name:?}: {error}"),
895        tensor_name: Some(name.into()),
896        tensor_type_code: None,
897        metadata_key: None,
898    }
899}
900
901#[cfg(test)]
902mod tests {
903    use super::*;
904    use crate::schema::{LayoutVariant, TensorOperation};
905
906    fn safe(
907        key: &str,
908        shape: &[usize],
909        dtype: StoredDtype,
910    ) -> (String, PhysicalMetadata<StoredDtype>) {
911        (
912            key.into(),
913            PhysicalMetadata {
914                shape: shape.to_vec(),
915                encoding: dtype,
916            },
917        )
918    }
919
920    fn safe_plan(
921        common: Vec<SafetensorsTensorConstraint>,
922        groups: Vec<AlternativeLayoutGroup<SafetensorsTensorConstraint>>,
923        policy: CatalogPolicy,
924    ) -> SafetensorsCheckpointPlan {
925        SafetensorsCheckpointPlan::new("test", common, groups, policy).unwrap()
926    }
927
928    #[test]
929    fn required_optional_unexpected_and_catalog_exclusions_are_generic() {
930        let plan = safe_plan(
931            vec![
932                SafetensorsTensorConstraint::required(
933                    "required",
934                    vec![2],
935                    StoredDtypeConstraint::Floating,
936                ),
937                SafetensorsTensorConstraint::required(
938                    "optional",
939                    vec![1],
940                    StoredDtypeConstraint::Floating,
941                )
942                .optional(),
943            ],
944            Vec::new(),
945            CatalogPolicy {
946                strict: true,
947                explicitly_allowed_keys: BTreeSet::from(["allowed".into()]),
948                allowed_prefixes: vec!["cache.".into()],
949                allowed_suffixes: vec![".rotary".into()],
950            },
951        );
952        let catalog = BTreeMap::from([
953            safe("required", &[2], StoredDtype::F16),
954            safe("allowed", &[1], StoredDtype::I32),
955            safe("cache.value", &[1], StoredDtype::I32),
956            safe("layer.rotary", &[1], StoredDtype::F32),
957            safe("unexpected", &[1], StoredDtype::F32),
958        ]);
959        let issues = validate_catalog(
960            &catalog,
961            &plan.identity,
962            &plan.common_tensors,
963            &plan.layout_groups,
964            &plan.catalog_policy,
965        );
966        assert_eq!(issues.len(), 1);
967        assert_eq!(issues[0].kind, CheckpointIssueKind::UnexpectedTensor);
968
969        let mut non_strict = plan.clone();
970        non_strict.catalog_policy.strict = false;
971        assert!(validate_catalog(
972            &catalog,
973            &non_strict.identity,
974            &non_strict.common_tensors,
975            &non_strict.layout_groups,
976            &non_strict.catalog_policy,
977        )
978        .is_empty());
979        let resolved = resolve_catalog(
980            &catalog,
981            &non_strict.identity,
982            &non_strict.common_tensors,
983            &non_strict.layout_groups,
984            &non_strict.catalog_policy,
985            Vec::new(),
986        )
987        .unwrap();
988        assert_eq!(
989            resolved.unclaimed_keys(),
990            &BTreeSet::from(["unexpected".into()])
991        );
992    }
993
994    #[test]
995    fn admitted_resolution_projection_cannot_introduce_sources() {
996        let admitted = ResolvedCheckpointPlan {
997            identity: "complete".into(),
998            source_keys: BTreeSet::from(["target".into(), "extension".into()]),
999            unclaimed_keys: BTreeSet::from(["metadata".into()]),
1000        };
1001        let target = admitted
1002            .project_claimed_sources("target", BTreeSet::from(["target".into()]))
1003            .unwrap();
1004
1005        assert_eq!(target.identity(), "target");
1006        assert_eq!(target.source_keys(), &BTreeSet::from(["target".into()]));
1007        assert_eq!(
1008            target.unclaimed_keys(),
1009            &BTreeSet::from(["metadata".into()])
1010        );
1011        assert!(admitted
1012            .project_claimed_sources("target", BTreeSet::from(["not-admitted".into()]))
1013            .is_err());
1014    }
1015
1016    #[test]
1017    fn exact_floating_and_encoded_fp8_constraints_keep_storage_distinct() {
1018        let constraints = vec![
1019            SafetensorsTensorConstraint::required(
1020                "exact",
1021                vec![1],
1022                StoredDtypeConstraint::Exact(StoredDtype::U8),
1023            ),
1024            SafetensorsTensorConstraint::required(
1025                "weight",
1026                vec![2, 2],
1027                StoredDtypeConstraint::OneOf(vec![StoredDtype::F8E4M3, StoredDtype::U8]),
1028            ),
1029            SafetensorsTensorConstraint::required(
1030                "scale",
1031                vec![1, 1],
1032                StoredDtypeConstraint::Floating,
1033            )
1034            .companion(),
1035        ];
1036        let plan = safe_plan(constraints, Vec::new(), CatalogPolicy::strict());
1037        for floating in [StoredDtype::F16, StoredDtype::BF16, StoredDtype::F32] {
1038            let catalog = BTreeMap::from([
1039                safe("exact", &[1], StoredDtype::U8),
1040                safe("weight", &[2, 2], StoredDtype::F8E4M3),
1041                safe("scale", &[1, 1], floating),
1042            ]);
1043            assert!(validate_catalog(
1044                &catalog,
1045                &plan.identity,
1046                &plan.common_tensors,
1047                &plan.layout_groups,
1048                &plan.catalog_policy,
1049            )
1050            .is_empty());
1051        }
1052        let catalog = BTreeMap::from([
1053            safe("exact", &[1], StoredDtype::U8),
1054            safe("weight", &[2, 2], StoredDtype::U8),
1055            safe("scale", &[1, 1], StoredDtype::I32),
1056        ]);
1057        let issues = validate_catalog(
1058            &catalog,
1059            &plan.identity,
1060            &plan.common_tensors,
1061            &plan.layout_groups,
1062            &plan.catalog_policy,
1063        );
1064        assert_eq!(issues[0].kind, CheckpointIssueKind::CompanionMismatch);
1065    }
1066
1067    #[test]
1068    fn physical_aliases_are_selected_once_and_accounted_by_strict_catalogs() {
1069        let plan = safe_plan(
1070            vec![SafetensorsTensorConstraint::required(
1071                "canonical",
1072                vec![2],
1073                StoredDtypeConstraint::Floating,
1074            )
1075            .with_aliases(["released"])],
1076            Vec::new(),
1077            CatalogPolicy::strict(),
1078        );
1079        let released = BTreeMap::from([safe("released", &[2], StoredDtype::BF16)]);
1080        assert!(validate_catalog(
1081            &released,
1082            &plan.identity,
1083            &plan.common_tensors,
1084            &plan.layout_groups,
1085            &plan.catalog_policy,
1086        )
1087        .is_empty());
1088
1089        let conflicting = BTreeMap::from([
1090            safe("canonical", &[2], StoredDtype::F16),
1091            safe("released", &[2], StoredDtype::BF16),
1092        ]);
1093        let issues = validate_catalog(
1094            &conflicting,
1095            &plan.identity,
1096            &plan.common_tensors,
1097            &plan.layout_groups,
1098            &plan.catalog_policy,
1099        );
1100        assert_eq!(issues.len(), 1);
1101        assert_eq!(issues[0].kind, CheckpointIssueKind::ConflictingLayout);
1102        assert_eq!(issues[0].tensor_name.as_deref(), Some("released"));
1103    }
1104
1105    #[test]
1106    fn safetensors_alternate_shapes_accept_only_declared_layouts() {
1107        let plan = safe_plan(
1108            vec![SafetensorsTensorConstraint::required(
1109                "convolution",
1110                vec![4, 1, 2],
1111                StoredDtypeConstraint::Floating,
1112            )
1113            .with_alternate_shapes([vec![4, 2, 1]])],
1114            Vec::new(),
1115            CatalogPolicy::strict(),
1116        );
1117        let alternate = BTreeMap::from([safe("convolution", &[4, 2, 1], StoredDtype::BF16)]);
1118        assert!(validate_catalog(
1119            &alternate,
1120            &plan.identity,
1121            &plan.common_tensors,
1122            &plan.layout_groups,
1123            &plan.catalog_policy,
1124        )
1125        .is_empty());
1126
1127        let undeclared = BTreeMap::from([safe("convolution", &[4, 2], StoredDtype::BF16)]);
1128        let issues = validate_catalog(
1129            &undeclared,
1130            &plan.identity,
1131            &plan.common_tensors,
1132            &plan.layout_groups,
1133            &plan.catalog_policy,
1134        );
1135        assert_eq!(issues.len(), 1);
1136        assert_eq!(issues[0].kind, CheckpointIssueKind::ShapeMismatch);
1137    }
1138
1139    #[test]
1140    fn element_count_constraints_accept_reshape_equivalent_storage() {
1141        let plan = safe_plan(
1142            vec![SafetensorsTensorConstraint::required(
1143                "convolution",
1144                vec![4, 1, 2],
1145                StoredDtypeConstraint::Floating,
1146            )
1147            .with_element_count(8)],
1148            Vec::new(),
1149            CatalogPolicy::strict(),
1150        );
1151        let reshaped = BTreeMap::from([safe("convolution", &[4, 2], StoredDtype::BF16)]);
1152        assert!(validate_catalog(
1153            &reshaped,
1154            &plan.identity,
1155            &plan.common_tensors,
1156            &plan.layout_groups,
1157            &plan.catalog_policy,
1158        )
1159        .is_empty());
1160
1161        let wrong = BTreeMap::from([safe("convolution", &[4, 3], StoredDtype::BF16)]);
1162        let issues = validate_catalog(
1163            &wrong,
1164            &plan.identity,
1165            &plan.common_tensors,
1166            &plan.layout_groups,
1167            &plan.catalog_policy,
1168        );
1169        assert_eq!(issues.len(), 1);
1170        assert_eq!(issues[0].kind, CheckpointIssueKind::ShapeMismatch);
1171        assert!(issues[0].detail.contains("must contain 8 elements"));
1172    }
1173
1174    fn alternatives() -> Vec<AlternativeLayoutGroup<SafetensorsTensorConstraint>> {
1175        vec![AlternativeLayoutGroup {
1176            id: "projection".into(),
1177            required: true,
1178            variants: vec![
1179                LayoutVariant {
1180                    id: "packed".into(),
1181                    tensors: vec![SafetensorsTensorConstraint::required(
1182                        "packed",
1183                        vec![4, 2],
1184                        StoredDtypeConstraint::Floating,
1185                    )],
1186                    discriminator_keys: vec!["packed".into()],
1187                },
1188                LayoutVariant {
1189                    id: "split".into(),
1190                    tensors: vec![
1191                        SafetensorsTensorConstraint::required(
1192                            "gate",
1193                            vec![2, 2],
1194                            StoredDtypeConstraint::Floating,
1195                        ),
1196                        SafetensorsTensorConstraint::required(
1197                            "up",
1198                            vec![2, 2],
1199                            StoredDtypeConstraint::Floating,
1200                        ),
1201                    ],
1202                    discriminator_keys: vec!["gate".into(), "up".into()],
1203                },
1204            ],
1205        }]
1206    }
1207
1208    #[test]
1209    fn alternatives_report_missing_partial_and_conflicting_layouts() {
1210        let plan = safe_plan(Vec::new(), alternatives(), CatalogPolicy::strict());
1211        let evaluate = |catalog: BTreeMap<_, _>| {
1212            validate_catalog(
1213                &catalog,
1214                &plan.identity,
1215                &plan.common_tensors,
1216                &plan.layout_groups,
1217                &plan.catalog_policy,
1218            )
1219        };
1220        assert!(evaluate(BTreeMap::from([safe("packed", &[4, 2], StoredDtype::F32)])).is_empty());
1221        let missing = evaluate(BTreeMap::new());
1222        assert_eq!(missing[0].kind, CheckpointIssueKind::MissingTensor);
1223        let partial = evaluate(BTreeMap::from([safe("gate", &[2, 2], StoredDtype::F32)]));
1224        assert!(partial
1225            .iter()
1226            .any(|issue| issue.kind == CheckpointIssueKind::MissingTensor));
1227        let conflict = evaluate(BTreeMap::from([
1228            safe("packed", &[4, 2], StoredDtype::F32),
1229            safe("gate", &[2, 2], StoredDtype::F32),
1230            safe("up", &[2, 2], StoredDtype::F32),
1231        ]));
1232        assert!(conflict
1233            .iter()
1234            .any(|issue| issue.detail.contains("conflicting variants")));
1235        let mixed_partial = evaluate(BTreeMap::from([
1236            safe("packed", &[4, 2], StoredDtype::F32),
1237            safe("gate", &[2, 2], StoredDtype::F32),
1238        ]));
1239        assert!(mixed_partial
1240            .iter()
1241            .any(|issue| issue.detail.contains("partially present variants")));
1242    }
1243
1244    #[test]
1245    fn resolution_selects_one_layout_and_only_its_physical_sources() {
1246        let plan = safe_plan(Vec::new(), alternatives(), CatalogPolicy::strict());
1247        let catalog = BTreeMap::from([safe("packed", &[4, 2], StoredDtype::BF16)]);
1248        let resolved = resolve_catalog(
1249            &catalog,
1250            &plan.identity,
1251            &plan.common_tensors,
1252            &plan.layout_groups,
1253            &plan.catalog_policy,
1254            Vec::new(),
1255        )
1256        .unwrap();
1257
1258        assert_eq!(
1259            resolved.source_keys().iter().cloned().collect::<Vec<_>>(),
1260            ["packed"]
1261        );
1262        assert!(!resolved.source_keys().contains("gate"));
1263        assert!(!resolved.source_keys().contains("up"));
1264    }
1265
1266    #[test]
1267    fn gguf_operation_class_constraints_are_checked() {
1268        let constraints = vec![
1269            GgufTensorConstraint::required(
1270                "index",
1271                vec![2],
1272                GgufTypeConstraint::OperationClass(TensorOperation::I32),
1273            ),
1274            GgufTensorConstraint::required(
1275                "matrix",
1276                vec![2, 2],
1277                GgufTypeConstraint::OperationClass(TensorOperation::Matrix),
1278            ),
1279            GgufTensorConstraint::required(
1280                "flexible",
1281                vec![2, 2],
1282                GgufTypeConstraint::OperationClass(TensorOperation::Dense),
1283            )
1284            .with_alternate_shapes([vec![2, 1, 2]]),
1285        ];
1286        let plan =
1287            GgufCheckpointPlan::new("test", constraints, Vec::new(), CatalogPolicy::strict())
1288                .unwrap();
1289        let catalog = BTreeMap::from([
1290            (
1291                "index".into(),
1292                PhysicalMetadata {
1293                    shape: vec![2],
1294                    encoding: GgufType::I32,
1295                },
1296            ),
1297            (
1298                "matrix".into(),
1299                PhysicalMetadata {
1300                    shape: vec![2, 2],
1301                    encoding: GgufType::Q4K,
1302                },
1303            ),
1304            (
1305                "flexible".into(),
1306                PhysicalMetadata {
1307                    shape: vec![2, 1, 2],
1308                    encoding: GgufType::F16,
1309                },
1310            ),
1311        ]);
1312        assert!(validate_catalog(
1313            &catalog,
1314            &plan.identity,
1315            &plan.common_tensors,
1316            &plan.layout_groups,
1317            &plan.catalog_policy,
1318        )
1319        .is_empty());
1320    }
1321
1322    #[test]
1323    fn issue_order_does_not_depend_on_checkpoint_key_order() {
1324        let plan = safe_plan(
1325            vec![SafetensorsTensorConstraint::required(
1326                "required",
1327                vec![2],
1328                StoredDtypeConstraint::Exact(StoredDtype::F32),
1329            )],
1330            Vec::new(),
1331            CatalogPolicy::strict(),
1332        );
1333        let left = BTreeMap::from([
1334            safe("z", &[1], StoredDtype::F16),
1335            safe("a", &[1], StoredDtype::F16),
1336        ]);
1337        let right = left
1338            .iter()
1339            .rev()
1340            .map(|(key, value)| (key.clone(), value.clone()))
1341            .collect();
1342        let validate = |catalog: &BTreeMap<_, _>| {
1343            validate_catalog(
1344                catalog,
1345                &plan.identity,
1346                &plan.common_tensors,
1347                &plan.layout_groups,
1348                &plan.catalog_policy,
1349            )
1350        };
1351        assert_eq!(validate(&left), validate(&right));
1352    }
1353}