Skip to main content

ferrum_interfaces/vnext/
model.rs

1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Deserializer, Serialize};
3use sha2::{Digest, Sha256};
4use std::collections::{BTreeMap, BTreeSet};
5
6use super::{
7    checked_elements, physical_component_ids, validate_physical_layout_budget, AttributeId,
8    AxisWeightComponent, BlockQuantizationSpec, CanonicalRational, CompositeWeightPart,
9    ContractVersion, ElementType, ExternalModelMetadataId, ModelFamilyId, NodeId, OperationId,
10    PhysicalStorageLayout, PhysicalWeightComponentBinding, PhysicalWeightLayout,
11    PhysicalWeightPadding, ProgramValueId, QuantizationGrouping, QuantizationPacking,
12    QuantizationSpec, ResolvedTensorLayout, ResolvedWeightBinding, ResolvedWeightComponentLayout,
13    ResolvedWeightLogicalValidation, SemanticValue, StateId, StateInitialization, TokenizerId,
14    VNextError, WeightComponentRole, WeightEncoding, WeightFormatId, WeightId, WeightLayoutId,
15    MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH, MAX_PHYSICAL_WEIGHT_LAYOUT_NODES,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct WeightComponentSpec {
20    pub id: WeightId,
21    pub role: WeightComponentRole,
22    /// Ordered checkpoint tensors forming this physical component. Dense
23    /// multi-source components use `[source_count, source_shape...]` and stack
24    /// tensors in this exact order. Format adapters may instead define a typed
25    /// source recipe (for example values plus quantization sidecars), but must
26    /// validate every source identity, order, shape, and resulting byte count.
27    /// Aliases are resolved before the model family constructs this schema.
28    pub external_names: Vec<String>,
29    pub dimensions: Vec<u64>,
30    pub encoding: WeightEncoding,
31    pub required: bool,
32}
33
34impl WeightComponentSpec {
35    /// Exact bytes occupied by this physical component. Separate-component
36    /// quantized dimensions are byte dimensions. Block-quantized dimensions
37    /// are block-grid dimensions and are multiplied by the block ABI size.
38    /// Logical element counts live on `WeightTensorSpec` and are checked
39    /// against the corresponding physical layout contract.
40    pub fn physical_bytes(&self) -> Result<u64, VNextError> {
41        self.encoding.physical_bytes(&self.dimensions, &self.id)
42    }
43
44    pub fn dense_element_type(&self) -> Option<ElementType> {
45        self.encoding.dense_element_type()
46    }
47
48    pub fn physical_element_type(&self) -> ElementType {
49        self.dense_element_type().unwrap_or(ElementType::U8)
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct PhysicalWeightComponentRef {
55    pub component_id: WeightId,
56    pub physical_dimensions: Vec<u64>,
57    pub resource_bytes: u64,
58    pub element_type: ElementType,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct WeightTensorSpec {
63    pub id: WeightId,
64    pub dimensions: Vec<u64>,
65    /// Dtype observed by the semantic program after decoding any physical
66    /// packing, indexing, or quantization components.
67    pub logical_element_type: ElementType,
68    pub physical_layout: PhysicalWeightLayout,
69    pub required: bool,
70}
71
72impl WeightTensorSpec {
73    pub fn logical_elements(&self) -> Result<u64, VNextError> {
74        checked_elements(&self.dimensions).ok_or_else(|| VNextError::InvalidExecutionPlan {
75            reason: format!("logical weight `{}` element count overflows u64", self.id),
76        })
77    }
78
79    pub fn logical_bytes(&self) -> Result<u64, VNextError> {
80        self.logical_elements()?
81            .checked_mul(self.logical_element_type.size_bytes())
82            .ok_or_else(|| VNextError::InvalidExecutionPlan {
83                reason: format!("logical weight `{}` byte size overflows u64", self.id),
84            })
85    }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct WeightSchema {
90    pub format_id: WeightFormatId,
91    pub layout_id: WeightLayoutId,
92    pub version: ContractVersion,
93    pub components: Vec<WeightComponentSpec>,
94    pub tensors: Vec<WeightTensorSpec>,
95}
96
97impl WeightSchema {
98    pub(crate) fn normalize(&mut self) {
99        self.components
100            .sort_by(|left, right| left.id.cmp(&right.id));
101        for tensor in &mut self.tensors {
102            tensor.physical_layout.normalize();
103        }
104        self.tensors.sort_by(|left, right| left.id.cmp(&right.id));
105    }
106
107    pub fn validate(&self, family_id: &ModelFamilyId) -> Result<(), VNextError> {
108        if self.version.major == 0 || self.components.is_empty() || self.tensors.is_empty() {
109            return Err(VNextError::UnknownWeightLayout {
110                family_id: family_id.to_string(),
111                layout_id: self.layout_id.to_string(),
112            });
113        }
114        for tensor in &self.tensors {
115            validate_physical_layout_budget(&tensor.physical_layout).map_err(|reason| {
116                VNextError::InvalidModelConfig {
117                    family_id: family_id.to_string(),
118                    field: format!("weight_schema.tensors.{}.physical_layout", tensor.id),
119                    reason,
120                }
121            })?;
122        }
123        let mut component_ids = BTreeSet::new();
124        let mut names = BTreeSet::new();
125        let mut components = BTreeMap::new();
126        let mut quantization_abis = BTreeMap::new();
127        for component in &self.components {
128            if !component_ids.insert(component.id.clone())
129                || component.external_names.is_empty()
130                || component.dimensions.is_empty()
131                || component
132                    .external_names
133                    .iter()
134                    .any(|name| name.trim().is_empty() || !names.insert(name.clone()))
135                || component.dimensions.iter().any(|extent| *extent == 0)
136            {
137                return Err(VNextError::InvalidModelConfig {
138                    family_id: family_id.to_string(),
139                    field: "weight_schema.components".to_owned(),
140                    reason: "component identities, names, and dimensions must be valid and unique"
141                        .to_owned(),
142                });
143            }
144            if let WeightEncoding::Quantized(quantization) = &component.encoding {
145                quantization.validate()?;
146            }
147            if let WeightEncoding::BlockQuantized(quantization) = &component.encoding {
148                quantization.validate()?;
149            }
150            let quantization_format = match &component.encoding {
151                WeightEncoding::Quantized(spec) => Some(&spec.format_id),
152                WeightEncoding::BlockQuantized(spec) => Some(&spec.format_id),
153                WeightEncoding::Dense { .. } | WeightEncoding::DenseAffine { .. } => None,
154            };
155            if let Some(format_id) = quantization_format {
156                if let Some(existing) = quantization_abis.get(format_id) {
157                    if existing != &component.encoding {
158                        return Err(VNextError::InvalidModelConfig {
159                            family_id: family_id.to_string(),
160                            field: "weight_schema.components.encoding".to_owned(),
161                            reason: format!(
162                                "quantization format `{format_id}` maps to conflicting physical ABIs"
163                            ),
164                        });
165                    }
166                } else {
167                    quantization_abis.insert(format_id.clone(), component.encoding.clone());
168                }
169            }
170            if let WeightEncoding::DenseAffine { element_type, .. } = &component.encoding {
171                if component.role != WeightComponentRole::Values
172                    || !matches!(
173                        element_type,
174                        ElementType::F16 | ElementType::Bf16 | ElementType::F32
175                    )
176                {
177                    return Err(VNextError::InvalidModelConfig {
178                        family_id: family_id.to_string(),
179                        field: "weight_schema.components.encoding".to_owned(),
180                        reason: format!(
181                            "affine dense component `{}` must be a floating-point value component",
182                            component.id
183                        ),
184                    });
185                }
186            }
187            component
188                .physical_bytes()
189                .map_err(|error| VNextError::InvalidModelConfig {
190                    family_id: family_id.to_string(),
191                    field: "weight_schema.components.dimensions".to_owned(),
192                    reason: error.to_string(),
193                })?;
194            let role_encoding_valid = match component.role {
195                WeightComponentRole::Scales => matches!(
196                    component.encoding,
197                    WeightEncoding::Dense {
198                        element_type: ElementType::U8
199                            | ElementType::F16
200                            | ElementType::Bf16
201                            | ElementType::F32
202                    }
203                ),
204                WeightComponentRole::ZeroPoints
205                | WeightComponentRole::Indices
206                | WeightComponentRole::Permutation => matches!(
207                    component.encoding,
208                    WeightEncoding::Dense {
209                        element_type: ElementType::U8
210                            | ElementType::U32
211                            | ElementType::I8
212                            | ElementType::I32
213                    }
214                ),
215                WeightComponentRole::PackedValues => {
216                    matches!(
217                        component.encoding,
218                        WeightEncoding::Quantized(_) | WeightEncoding::BlockQuantized(_)
219                    )
220                }
221                _ => true,
222            };
223            if !role_encoding_valid {
224                return Err(VNextError::InvalidModelConfig {
225                    family_id: family_id.to_string(),
226                    field: "weight_schema.components.encoding".to_owned(),
227                    reason: format!(
228                        "component `{}` encoding is incompatible with its structural role",
229                        component.id
230                    ),
231                });
232            }
233            components.insert(component.id.clone(), component);
234        }
235
236        let mut tensor_ids = BTreeSet::new();
237        let mut referenced_components = BTreeSet::new();
238        for tensor in &self.tensors {
239            if !tensor_ids.insert(tensor.id.clone())
240                || tensor.dimensions.is_empty()
241                || tensor.dimensions.iter().any(|extent| *extent == 0)
242            {
243                return Err(VNextError::InvalidModelConfig {
244                    family_id: family_id.to_string(),
245                    field: "weight_schema.tensors".to_owned(),
246                    reason: "logical weight identities and dimensions must be valid and unique"
247                        .to_owned(),
248                });
249            }
250            self.validate_physical_layout(
251                family_id,
252                tensor,
253                &components,
254                &mut referenced_components,
255            )?;
256        }
257        if let Some(component) = self
258            .components
259            .iter()
260            .find(|component| component.required && !referenced_components.contains(&component.id))
261        {
262            return Err(VNextError::InvalidModelConfig {
263                family_id: family_id.to_string(),
264                field: "weight_schema.components".to_owned(),
265                reason: format!(
266                    "required component `{}` is not referenced by a logical weight",
267                    component.id
268                ),
269            });
270        }
271        Ok(())
272    }
273
274    pub fn fingerprint(&self) -> Result<String, VNextError> {
275        let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
276            context: "fingerprint weight schema",
277            message: error.to_string(),
278        })?;
279        Ok(format!("{:x}", Sha256::digest(bytes)))
280    }
281
282    pub fn quantization_formats(&self) -> BTreeSet<super::QuantizationFormatId> {
283        self.components
284            .iter()
285            .filter_map(|component| match &component.encoding {
286                WeightEncoding::Quantized(spec) => Some(spec.format_id.clone()),
287                WeightEncoding::BlockQuantized(spec) => Some(spec.format_id.clone()),
288                WeightEncoding::Dense { .. } | WeightEncoding::DenseAffine { .. } => None,
289            })
290            .collect()
291    }
292
293    fn validate_physical_layout(
294        &self,
295        family_id: &ModelFamilyId,
296        tensor: &WeightTensorSpec,
297        components: &BTreeMap<WeightId, &WeightComponentSpec>,
298        referenced: &mut BTreeSet<WeightId>,
299    ) -> Result<(), VNextError> {
300        let mut validator = PhysicalLayoutValidator {
301            family_id,
302            tensor_id: &tensor.id,
303            components,
304            referenced,
305            visited_nodes: 0,
306        };
307        validator.validate_layout(
308            &tensor.physical_layout,
309            &tensor.dimensions,
310            tensor.logical_element_type,
311            1,
312        )
313    }
314
315    pub fn tensor(&self, weight_id: &WeightId) -> Option<&WeightTensorSpec> {
316        self.tensors.iter().find(|tensor| &tensor.id == weight_id)
317    }
318
319    /// Ordered physical components needed to materialize one logical value.
320    /// The order is the schema's component order and is therefore stable for
321    /// fingerprints and resource-plan evidence.
322    pub fn physical_component_refs(
323        &self,
324        weight_id: &WeightId,
325    ) -> Result<Vec<&WeightComponentSpec>, VNextError> {
326        let tensor = self
327            .tensor(weight_id)
328            .ok_or_else(|| VNextError::InvalidExecutionPlan {
329                reason: format!("unknown logical weight `{weight_id}`"),
330            })?;
331        let required = physical_component_ids(&tensor.physical_layout).map_err(|reason| {
332            VNextError::InvalidExecutionPlan {
333                reason: format!(
334                    "logical weight `{weight_id}` has invalid physical layout: {reason}"
335                ),
336            }
337        })?;
338        let result = self
339            .components
340            .iter()
341            .filter(|component| required.contains(&component.id))
342            .collect::<Vec<_>>();
343        if result.len() != required.len() {
344            return Err(VNextError::InvalidExecutionPlan {
345                reason: format!("logical weight `{weight_id}` references an unknown component"),
346            });
347        }
348        Ok(result)
349    }
350
351    pub fn physical_bytes(&self, weight_id: &WeightId) -> Result<u64, VNextError> {
352        self.physical_component_refs(weight_id)?
353            .into_iter()
354            .try_fold(0_u64, |total, component| {
355                total
356                    .checked_add(component.physical_bytes()?)
357                    .ok_or_else(|| VNextError::InvalidExecutionPlan {
358                        reason: format!("logical weight `{weight_id}` physical bytes overflow u64"),
359                    })
360            })
361    }
362
363    pub fn physical_resource_requirements(
364        &self,
365        weight_id: &WeightId,
366    ) -> Result<Vec<PhysicalWeightComponentRef>, VNextError> {
367        self.physical_component_refs(weight_id)?
368            .into_iter()
369            .map(|component| {
370                Ok(PhysicalWeightComponentRef {
371                    component_id: component.id.clone(),
372                    physical_dimensions: component.dimensions.clone(),
373                    resource_bytes: component.physical_bytes()?,
374                    element_type: component.physical_element_type(),
375                })
376            })
377            .collect()
378    }
379}
380
381impl ResolvedWeightBinding {
382    pub fn from_schema(schema: &WeightSchema, weight_id: &WeightId) -> Result<Self, VNextError> {
383        let tensor = schema
384            .tensor(weight_id)
385            .ok_or_else(|| VNextError::InvalidExecutionPlan {
386                reason: format!("unknown logical weight `{weight_id}`"),
387            })?;
388        let mut components = schema
389            .physical_component_refs(weight_id)?
390            .into_iter()
391            .map(|component| {
392                ResolvedWeightComponentLayout::from_parts(
393                    component.id.clone(),
394                    component.role,
395                    component.dimensions.clone(),
396                    component.encoding.clone(),
397                )
398            })
399            .collect::<Vec<_>>();
400        components.sort_by(|left, right| left.component_id().cmp(right.component_id()));
401        let binding = Self::from_parts(
402            weight_id.clone(),
403            schema.format_id.clone(),
404            schema.layout_id.clone(),
405            schema.version,
406            tensor.physical_layout.clone(),
407            components,
408        )?;
409        binding.validate_logical(&tensor.dimensions, tensor.logical_element_type)?;
410        Ok(binding)
411    }
412
413    pub fn validate_logical(
414        &self,
415        logical_dimensions: &[u64],
416        logical_element_type: ElementType,
417    ) -> Result<(), VNextError> {
418        ResolvedWeightLogicalValidation::validate_logical_contract(
419            self,
420            logical_dimensions,
421            logical_element_type,
422        )
423    }
424}
425
426impl ResolvedWeightLogicalValidation for ResolvedWeightBinding {
427    fn validate_logical_contract(
428        &self,
429        logical_dimensions: &[u64],
430        logical_element_type: ElementType,
431    ) -> Result<(), VNextError> {
432        self.validate_structure()?;
433        let schema = WeightSchema {
434            format_id: self.schema_format_id().clone(),
435            layout_id: self.layout_id().clone(),
436            version: self.schema_version(),
437            components: self
438                .components()
439                .iter()
440                .map(|component| WeightComponentSpec {
441                    id: component.component_id().clone(),
442                    role: component.role(),
443                    external_names: vec![format!("resolved.{}", component.component_id())],
444                    dimensions: component.physical_dimensions().to_vec(),
445                    encoding: component.encoding().clone(),
446                    required: true,
447                })
448                .collect(),
449            tensors: vec![WeightTensorSpec {
450                id: self.weight_id().clone(),
451                dimensions: logical_dimensions.to_vec(),
452                logical_element_type,
453                physical_layout: self.physical_layout().clone(),
454                required: true,
455            }],
456        };
457        schema.validate(&ModelFamilyId::new("family.resolved-weight-binding")?)
458    }
459}
460
461struct PhysicalLayoutValidator<'schema, 'references> {
462    family_id: &'schema ModelFamilyId,
463    tensor_id: &'schema WeightId,
464    components: &'schema BTreeMap<WeightId, &'schema WeightComponentSpec>,
465    referenced: &'references mut BTreeSet<WeightId>,
466    visited_nodes: usize,
467}
468
469impl<'schema, 'references> PhysicalLayoutValidator<'schema, 'references> {
470    fn invalid(&self, reason: impl Into<String>) -> VNextError {
471        VNextError::InvalidModelConfig {
472            family_id: self.family_id.to_string(),
473            field: format!("weight_schema.tensors.{}.physical_layout", self.tensor_id),
474            reason: reason.into(),
475        }
476    }
477
478    fn visit_node(&mut self, depth: usize) -> Result<(), VNextError> {
479        if depth > MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH {
480            return Err(self.invalid(format!(
481                "physical layout depth exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH}"
482            )));
483        }
484        self.visited_nodes = self
485            .visited_nodes
486            .checked_add(1)
487            .ok_or_else(|| self.invalid("physical layout node count overflows usize"))?;
488        if self.visited_nodes > MAX_PHYSICAL_WEIGHT_LAYOUT_NODES {
489            return Err(self.invalid(format!(
490                "physical layout node count exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_NODES}"
491            )));
492        }
493        Ok(())
494    }
495
496    fn component(
497        &self,
498        component_id: &WeightId,
499    ) -> Result<&'schema WeightComponentSpec, VNextError> {
500        self.components
501            .get(component_id)
502            .copied()
503            .ok_or_else(|| self.invalid(format!("unknown component `{component_id}`")))
504    }
505
506    fn bind_component(
507        &mut self,
508        binding: &PhysicalWeightComponentBinding,
509        semantic_dimensions: &[u64],
510        role: WeightComponentRole,
511        depth: usize,
512    ) -> Result<&'schema WeightComponentSpec, VNextError> {
513        self.visit_node(depth)?;
514        let component = self.component(&binding.component_id)?;
515        if component.role != role {
516            return Err(self.invalid(format!(
517                "component `{}` has role {:?}, expected {:?}",
518                component.id, component.role, role
519            )));
520        }
521        self.validate_storage(component, semantic_dimensions, &binding.storage)?;
522        if !self.referenced.insert(component.id.clone()) {
523            return Err(self.invalid(format!(
524                "component `{}` is referenced more than once in the physical layout tree",
525                component.id
526            )));
527        }
528        Ok(component)
529    }
530
531    fn validate_storage(
532        &self,
533        component: &WeightComponentSpec,
534        semantic_dimensions: &[u64],
535        storage: &PhysicalStorageLayout,
536    ) -> Result<(), VNextError> {
537        if semantic_dimensions.is_empty()
538            || semantic_dimensions.iter().any(|extent| *extent == 0)
539            || checked_elements(semantic_dimensions).is_none()
540        {
541            return Err(self.invalid(format!(
542                "component `{}` has an invalid or overflowing semantic shape",
543                component.id
544            )));
545        }
546        let raw_elements = checked_elements(&component.dimensions).ok_or_else(|| {
547            self.invalid(format!(
548                "component `{}` raw storage shape overflows u64",
549                component.id
550            ))
551        })?;
552        match storage {
553            PhysicalStorageLayout::Contiguous { padding } => {
554                let padded = self.resolve_padding(semantic_dimensions, padding)?;
555                if component.dimensions != padded {
556                    return Err(self.invalid(format!(
557                        "component `{}` contiguous shape {:?} differs from its explicit physical shape {:?}",
558                        component.id, padded, component.dimensions
559                    )));
560                }
561            }
562            PhysicalStorageLayout::Strided {
563                strides_in_elements,
564                padding,
565            } => {
566                let padded = self.resolve_padding(semantic_dimensions, padding)?;
567                let span = self.checked_strided_span(&padded, strides_in_elements, 1)?;
568                if span != raw_elements {
569                    return Err(self.invalid(format!(
570                        "component `{}` strided span {span} differs from its raw storage element count {raw_elements}",
571                        component.id
572                    )));
573                }
574            }
575            PhysicalStorageLayout::Tiled {
576                tile_shape,
577                axis_order,
578                tile_strides_in_elements,
579                padding,
580            } => {
581                let rank = semantic_dimensions.len();
582                if tile_shape.len() != rank
583                    || tile_shape.iter().any(|extent| *extent == 0)
584                    || !is_axis_permutation(axis_order, rank)
585                    || tile_strides_in_elements.len() != rank
586                {
587                    return Err(self.invalid(format!(
588                        "component `{}` tile shape, axis order, or strides do not match rank",
589                        component.id
590                    )));
591                }
592                let padded = self.resolve_padding(semantic_dimensions, padding)?;
593                let minimal_padded = semantic_dimensions
594                    .iter()
595                    .zip(tile_shape)
596                    .map(|(extent, tile)| checked_round_up(*extent, *tile))
597                    .collect::<Option<Vec<_>>>()
598                    .ok_or_else(|| {
599                        self.invalid(format!(
600                            "component `{}` tile padding overflows u64",
601                            component.id
602                        ))
603                    })?;
604                match padding {
605                    PhysicalWeightPadding::Exact if minimal_padded != semantic_dimensions => {
606                        return Err(self.invalid(format!(
607                            "component `{}` needs tile padding but declares exact storage",
608                            component.id
609                        )));
610                    }
611                    PhysicalWeightPadding::ZeroFill { .. } if padded != minimal_padded => {
612                        return Err(self.invalid(format!(
613                            "component `{}` tiled zero-fill shape is not the unique minimal padded shape",
614                            component.id
615                        )));
616                    }
617                    _ => {}
618                }
619                let semantic_grid = padded
620                    .iter()
621                    .zip(tile_shape)
622                    .map(|(extent, tile)| extent / tile)
623                    .collect::<Vec<_>>();
624                let physical_grid = axis_order
625                    .iter()
626                    .map(|axis| semantic_grid[*axis as usize])
627                    .collect::<Vec<_>>();
628                let tile_elements = checked_elements(tile_shape).ok_or_else(|| {
629                    self.invalid(format!(
630                        "component `{}` tile size overflows u64",
631                        component.id
632                    ))
633                })?;
634                let span = self.checked_strided_span(
635                    &physical_grid,
636                    tile_strides_in_elements,
637                    tile_elements,
638                )?;
639                if span != raw_elements {
640                    return Err(self.invalid(format!(
641                        "component `{}` tiled span {span} differs from its raw storage element count {raw_elements}",
642                        component.id
643                    )));
644                }
645            }
646        }
647        Ok(())
648    }
649
650    fn resolve_padding(
651        &self,
652        semantic_dimensions: &[u64],
653        padding: &PhysicalWeightPadding,
654    ) -> Result<Vec<u64>, VNextError> {
655        match padding {
656            PhysicalWeightPadding::Exact => Ok(semantic_dimensions.to_vec()),
657            PhysicalWeightPadding::ZeroFill { padded_dimensions } => {
658                if padded_dimensions.len() != semantic_dimensions.len()
659                    || padded_dimensions.iter().any(|extent| *extent == 0)
660                    || padded_dimensions
661                        .iter()
662                        .zip(semantic_dimensions)
663                        .any(|(padded, semantic)| padded < semantic)
664                    || padded_dimensions == semantic_dimensions
665                    || checked_elements(padded_dimensions).is_none()
666                {
667                    return Err(self.invalid(
668                        "zero-fill padding must explicitly enlarge a valid shape without shrinking any axis",
669                    ));
670                }
671                Ok(padded_dimensions.clone())
672            }
673        }
674    }
675
676    fn checked_strided_span(
677        &self,
678        dimensions: &[u64],
679        strides: &[u64],
680        base_span: u64,
681    ) -> Result<u64, VNextError> {
682        if dimensions.is_empty()
683            || dimensions.len() != strides.len()
684            || dimensions.iter().any(|extent| *extent == 0)
685            || strides.iter().any(|stride| *stride == 0)
686            || base_span == 0
687        {
688            return Err(self.invalid("strided storage dimensions and strides are invalid"));
689        }
690        let mut axes = dimensions
691            .iter()
692            .copied()
693            .zip(strides.iter().copied())
694            .filter(|(extent, _)| *extent > 1)
695            .collect::<Vec<_>>();
696        axes.sort_by_key(|(_, stride)| *stride);
697        let mut span = base_span;
698        for (extent, stride) in axes {
699            if stride < span {
700                return Err(
701                    self.invalid("strided storage aliases coordinates or overlaps physical tiles")
702                );
703            }
704            span = extent
705                .checked_sub(1)
706                .and_then(|count| count.checked_mul(stride))
707                .and_then(|addition| span.checked_add(addition))
708                .ok_or_else(|| self.invalid("strided storage span overflows u64"))?;
709        }
710        Ok(span)
711    }
712
713    fn grouped_dimensions(
714        &self,
715        semantic_dimensions: &[u64],
716        padding: &PhysicalWeightPadding,
717        group_axis: usize,
718        group_size: u64,
719    ) -> Result<Vec<u64>, VNextError> {
720        let axis_extent = semantic_dimensions[group_axis];
721        let minimal_axis = checked_round_up(axis_extent, group_size)
722            .ok_or_else(|| self.invalid("quantization group padding overflows u64"))?;
723        match padding {
724            PhysicalWeightPadding::Exact => {
725                if minimal_axis != axis_extent {
726                    return Err(self.invalid(
727                        "quantization groups require padding but exact storage was declared",
728                    ));
729                }
730                Ok(semantic_dimensions.to_vec())
731            }
732            PhysicalWeightPadding::ZeroFill { padded_dimensions } => {
733                if minimal_axis == axis_extent
734                    || padded_dimensions.len() != semantic_dimensions.len()
735                    || padded_dimensions.iter().enumerate().any(|(axis, extent)| {
736                        if axis == group_axis {
737                            *extent != minimal_axis
738                        } else {
739                            *extent != semantic_dimensions[axis]
740                        }
741                    })
742                {
743                    return Err(self.invalid(
744                        "quantization zero-fill must pad only the group axis to its unique minimal extent",
745                    ));
746                }
747                checked_elements(padded_dimensions)
748                    .is_some()
749                    .then(|| padded_dimensions.clone())
750                    .ok_or_else(|| self.invalid("quantization padded shape overflows u64"))
751            }
752        }
753    }
754
755    fn validate_dense_values(
756        &mut self,
757        binding: &PhysicalWeightComponentBinding,
758        semantic_dimensions: &[u64],
759        logical_element_type: ElementType,
760        depth: usize,
761    ) -> Result<(), VNextError> {
762        let component = self.bind_component(
763            binding,
764            semantic_dimensions,
765            WeightComponentRole::Values,
766            depth,
767        )?;
768        if component.dense_element_type() != Some(logical_element_type) {
769            return Err(self.invalid(format!(
770                "values component `{}` dtype differs from the logical tensor",
771                component.id
772            )));
773        }
774        Ok(())
775    }
776
777    fn validate_axis_component(
778        &mut self,
779        axis_component: &AxisWeightComponent,
780        semantic_dimensions: &[u64],
781        expected_axis: usize,
782        role: WeightComponentRole,
783        allow_narrow_integer: bool,
784        depth: usize,
785    ) -> Result<(), VNextError> {
786        if axis_component.axis as usize != expected_axis {
787            return Err(self.invalid(format!(
788                "axis component `{}` targets axis {}, expected {expected_axis}",
789                axis_component.component.component_id, axis_component.axis
790            )));
791        }
792        let axis_shape = [semantic_dimensions[expected_axis]];
793        let component = self.bind_component(&axis_component.component, &axis_shape, role, depth)?;
794        let integer_type_valid = component.dense_element_type().is_some_and(|element_type| {
795            if allow_narrow_integer {
796                matches!(
797                    element_type,
798                    ElementType::U8 | ElementType::U32 | ElementType::I8 | ElementType::I32
799                )
800            } else {
801                matches!(element_type, ElementType::U32 | ElementType::I32)
802            }
803        });
804        if !integer_type_valid {
805            return Err(self.invalid(format!(
806                "axis component `{}` must use an integer encoding valid for {:?}",
807                component.id, role
808            )));
809        }
810        Ok(())
811    }
812
813    fn validate_layout(
814        &mut self,
815        layout: &PhysicalWeightLayout,
816        semantic_dimensions: &[u64],
817        logical_element_type: ElementType,
818        depth: usize,
819    ) -> Result<(), VNextError> {
820        self.visit_node(depth)?;
821        if semantic_dimensions.is_empty()
822            || semantic_dimensions.iter().any(|extent| *extent == 0)
823            || checked_elements(semantic_dimensions).is_none()
824        {
825            return Err(self.invalid("logical layout shape is empty, zero, or overflowing"));
826        }
827        match layout {
828            PhysicalWeightLayout::Dense { component_id } => {
829                let binding =
830                    PhysicalWeightComponentBinding::exact_contiguous(component_id.clone());
831                self.validate_dense_values(
832                    &binding,
833                    semantic_dimensions,
834                    logical_element_type,
835                    depth,
836                )?;
837            }
838            PhysicalWeightLayout::Stored { component } => {
839                self.validate_dense_values(
840                    component,
841                    semantic_dimensions,
842                    logical_element_type,
843                    depth,
844                )?;
845            }
846            PhysicalWeightLayout::Composite { parts } => {
847                if parts.is_empty() {
848                    return Err(self.invalid("composite layout has no parts"));
849                }
850                let rank = semantic_dimensions.len();
851                let mut covered_elements = 0_u64;
852                for (index, part) in parts.iter().enumerate() {
853                    if part.logical_offsets.len() != rank
854                        || part.extents.len() != rank
855                        || part.extents.iter().any(|extent| *extent == 0)
856                        || part
857                            .logical_offsets
858                            .iter()
859                            .zip(&part.extents)
860                            .zip(semantic_dimensions)
861                            .any(|((offset, extent), logical)| {
862                                offset.checked_add(*extent).is_none_or(|end| end > *logical)
863                            })
864                    {
865                        return Err(self.invalid(format!(
866                            "composite part {index} has invalid semantic offsets or extents"
867                        )));
868                    }
869                    for previous in &parts[..index] {
870                        let overlaps = part
871                            .logical_offsets
872                            .iter()
873                            .zip(&part.extents)
874                            .zip(previous.logical_offsets.iter().zip(&previous.extents))
875                            .all(|((offset, extent), (other_offset, other_extent))| {
876                                offset.checked_add(*extent).is_some_and(|end| {
877                                    other_offset.checked_add(*other_extent).is_some_and(
878                                        |other_end| *offset < other_end && *other_offset < end,
879                                    )
880                                })
881                            });
882                        if overlaps {
883                            return Err(self.invalid("composite semantic placements overlap"));
884                        }
885                    }
886                    let part_elements = checked_elements(&part.extents)
887                        .ok_or_else(|| self.invalid("composite part size overflows u64"))?;
888                    covered_elements = covered_elements
889                        .checked_add(part_elements)
890                        .ok_or_else(|| self.invalid("composite coverage overflows u64"))?;
891                    self.validate_layout(
892                        &part.layout,
893                        &part.extents,
894                        logical_element_type,
895                        depth + 1,
896                    )?;
897                }
898                if covered_elements != checked_elements(semantic_dimensions).unwrap() {
899                    return Err(self.invalid(
900                        "composite semantic placements do not cover the logical tensor exactly",
901                    ));
902                }
903            }
904            PhysicalWeightLayout::Quantized {
905                packed_values,
906                packed_dimensions,
907                scales,
908                zero_points,
909                zero_point_packed_dimensions,
910                axis_indices,
911                permutation,
912                codebook,
913                group_axis,
914                group_padding,
915            } => {
916                if !matches!(
917                    logical_element_type,
918                    ElementType::F16 | ElementType::Bf16 | ElementType::F32
919                ) {
920                    return Err(
921                        self.invalid("quantized logical weight dtype must be floating point")
922                    );
923                }
924                let axis = *group_axis as usize;
925                if axis >= semantic_dimensions.len() {
926                    return Err(self.invalid("quantization group axis is out of range"));
927                }
928                let quantization = {
929                    let component = self.component(&packed_values.component_id)?;
930                    let WeightEncoding::Quantized(spec) = &component.encoding else {
931                        return Err(self.invalid(
932                            "packed-values component does not carry a quantization spec",
933                        ));
934                    };
935                    spec.clone()
936                };
937                let group_size = quantization
938                    .grouping
939                    .resolved_size(semantic_dimensions[axis]);
940                if group_size == 0 {
941                    return Err(self.invalid(
942                        "two-dimensional block grouping requires a block-grid quantized layout",
943                    ));
944                }
945                let grouped_dimensions =
946                    self.grouped_dimensions(semantic_dimensions, group_padding, axis, group_size)?;
947                let packed_bytes = checked_elements(&grouped_dimensions)
948                    .and_then(|elements| {
949                        elements.checked_mul(u64::from(quantization.bits_per_weight))
950                    })
951                    .and_then(|bits| bits.checked_add(7))
952                    .map(|bits| bits / 8)
953                    .ok_or_else(|| self.invalid("packed-values size overflows u64"))?;
954                if checked_elements(packed_dimensions) != Some(packed_bytes) {
955                    return Err(self.invalid(format!(
956                        "packed-values semantic shape contains {} storage bytes, expected {packed_bytes}",
957                        checked_elements(packed_dimensions)
958                            .map_or_else(|| "an overflowing number of".to_owned(), |value| value.to_string())
959                    )));
960                }
961                let packed = self.bind_component(
962                    packed_values,
963                    packed_dimensions,
964                    WeightComponentRole::PackedValues,
965                    depth,
966                )?;
967                if packed.encoding != WeightEncoding::Quantized(quantization.clone()) {
968                    return Err(self.invalid(
969                        "packed-values encoding changed while validating the quantized tree",
970                    ));
971                }
972
973                let mut group_shape = grouped_dimensions;
974                group_shape[axis] /= group_size;
975                let scales_component =
976                    self.bind_component(scales, &group_shape, WeightComponentRole::Scales, depth)?;
977                if scales_component.dense_element_type() != Some(quantization.scale_type) {
978                    return Err(
979                        self.invalid("scale component dtype differs from the quantization spec")
980                    );
981                }
982                match (
983                    quantization.zero_point_type,
984                    zero_points,
985                    zero_point_packed_dimensions,
986                ) {
987                    (Some(expected_type), Some(binding), Some(packed_dimensions)) => {
988                        let expected_bytes = checked_elements(&group_shape)
989                            .and_then(|elements| {
990                                elements.checked_mul(u64::from(quantization.bits_per_weight))
991                            })
992                            .and_then(|bits| bits.checked_add(7))
993                            .map(|bits| bits / 8)
994                            .ok_or_else(|| self.invalid("packed zero-point size overflows u64"))?;
995                        let component = self.bind_component(
996                            binding,
997                            packed_dimensions,
998                            WeightComponentRole::ZeroPoints,
999                            depth,
1000                        )?;
1001                        if component.dense_element_type() != Some(expected_type)
1002                            || component.physical_bytes()? != expected_bytes
1003                        {
1004                            return Err(self.invalid(
1005                                "packed zero-point component differs from its quantization contract",
1006                            ));
1007                        }
1008                    }
1009                    (Some(expected_type), Some(binding), None) => {
1010                        let component = self.bind_component(
1011                            binding,
1012                            &group_shape,
1013                            WeightComponentRole::ZeroPoints,
1014                            depth,
1015                        )?;
1016                        if component.dense_element_type() != Some(expected_type) {
1017                            return Err(self.invalid(
1018                                "zero-point component dtype differs from the quantization spec",
1019                            ));
1020                        }
1021                    }
1022                    (None, None, None) => {}
1023                    _ => {
1024                        return Err(self.invalid(
1025                            "zero-point component presence differs from the quantization spec",
1026                        ));
1027                    }
1028                }
1029                if let Some(axis_indices) = axis_indices {
1030                    self.validate_axis_component(
1031                        axis_indices,
1032                        semantic_dimensions,
1033                        axis,
1034                        WeightComponentRole::Indices,
1035                        true,
1036                        depth,
1037                    )?;
1038                }
1039                if let Some(permutation) = permutation {
1040                    self.validate_axis_component(
1041                        permutation,
1042                        semantic_dimensions,
1043                        axis,
1044                        WeightComponentRole::Permutation,
1045                        false,
1046                        depth,
1047                    )?;
1048                }
1049                if let Some(codebook) = codebook {
1050                    let entries = 1_u64
1051                        .checked_shl(u32::from(quantization.bits_per_weight))
1052                        .ok_or_else(|| self.invalid("codebook size overflows u64"))?;
1053                    let component = self.bind_component(
1054                        codebook,
1055                        &[entries],
1056                        WeightComponentRole::Codebook,
1057                        depth,
1058                    )?;
1059                    if component.dense_element_type() != Some(logical_element_type) {
1060                        return Err(
1061                            self.invalid("codebook dtype differs from the logical tensor dtype")
1062                        );
1063                    }
1064                }
1065            }
1066            PhysicalWeightLayout::QuantizedBlockGrid {
1067                packed_values,
1068                packed_dimensions,
1069                scales,
1070                block_axes,
1071            } => {
1072                if !matches!(
1073                    logical_element_type,
1074                    ElementType::F16 | ElementType::Bf16 | ElementType::F32
1075                ) {
1076                    return Err(self.invalid(
1077                        "block-grid quantized logical weight dtype must be floating point",
1078                    ));
1079                }
1080                let axes = [block_axes[0] as usize, block_axes[1] as usize];
1081                if axes[0] >= axes[1] || axes[1] >= semantic_dimensions.len() {
1082                    return Err(self.invalid(
1083                        "block-grid quantization axes must be distinct, in range, and ascending",
1084                    ));
1085                }
1086                let quantization = {
1087                    let component = self.component(&packed_values.component_id)?;
1088                    let WeightEncoding::Quantized(spec) = &component.encoding else {
1089                        return Err(self.invalid(
1090                            "block-grid packed-values component does not carry a quantization spec",
1091                        ));
1092                    };
1093                    spec.clone()
1094                };
1095                let block_shape = quantization.grouping.block_shape_2d().ok_or_else(|| {
1096                    self.invalid(
1097                        "block-grid packed-values quantization spec must carry a two-dimensional block shape",
1098                    )
1099                })?;
1100                if quantization.zero_point_type.is_some() {
1101                    return Err(self.invalid(
1102                        "block-grid quantization does not support an implicit zero-point component",
1103                    ));
1104                }
1105
1106                let packed_bytes = checked_elements(semantic_dimensions)
1107                    .and_then(|elements| {
1108                        elements.checked_mul(u64::from(quantization.bits_per_weight))
1109                    })
1110                    .and_then(|bits| bits.checked_add(7))
1111                    .map(|bits| bits / 8)
1112                    .ok_or_else(|| self.invalid("block-grid packed-values size overflows u64"))?;
1113                if packed_dimensions.is_empty()
1114                    || checked_elements(packed_dimensions) != Some(packed_bytes)
1115                {
1116                    return Err(self.invalid(format!(
1117                        "block-grid packed-values semantic shape contains {} storage bytes, expected {packed_bytes}",
1118                        checked_elements(packed_dimensions).map_or_else(
1119                            || "an invalid or overflowing number of".to_owned(),
1120                            |value| value.to_string(),
1121                        )
1122                    )));
1123                }
1124                let packed = self.bind_component(
1125                    packed_values,
1126                    packed_dimensions,
1127                    WeightComponentRole::PackedValues,
1128                    depth,
1129                )?;
1130                if packed.encoding != WeightEncoding::Quantized(quantization.clone()) {
1131                    return Err(self.invalid(
1132                        "packed-values encoding changed while validating the block-grid tree",
1133                    ));
1134                }
1135
1136                let mut scale_dimensions = semantic_dimensions.to_vec();
1137                for (axis, block_size) in axes.into_iter().zip(block_shape) {
1138                    scale_dimensions[axis] =
1139                        semantic_dimensions[axis].div_ceil(u64::from(block_size.get()));
1140                }
1141                let scales_component = self.bind_component(
1142                    scales,
1143                    &scale_dimensions,
1144                    WeightComponentRole::Scales,
1145                    depth,
1146                )?;
1147                if scales_component.dense_element_type() != Some(quantization.scale_type) {
1148                    return Err(self.invalid(
1149                        "block-grid scale component dtype differs from the quantization spec",
1150                    ));
1151                }
1152            }
1153            PhysicalWeightLayout::BlockQuantized {
1154                blocks,
1155                block_axis,
1156                block_padding,
1157            } => {
1158                if !matches!(
1159                    logical_element_type,
1160                    ElementType::F16 | ElementType::Bf16 | ElementType::F32
1161                ) {
1162                    return Err(
1163                        self.invalid("block-quantized logical weight dtype must be floating point")
1164                    );
1165                }
1166                let axis = *block_axis as usize;
1167                if axis >= semantic_dimensions.len() {
1168                    return Err(self.invalid("block quantization axis is out of range"));
1169                }
1170                let quantization = {
1171                    let component = self.component(&blocks.component_id)?;
1172                    let WeightEncoding::BlockQuantized(spec) = &component.encoding else {
1173                        return Err(self
1174                            .invalid("block component does not carry a block quantization spec"));
1175                    };
1176                    spec.clone()
1177                };
1178                let mut block_dimensions = self.grouped_dimensions(
1179                    semantic_dimensions,
1180                    block_padding,
1181                    axis,
1182                    u64::from(quantization.logical_values_per_block),
1183                )?;
1184                block_dimensions[axis] /= u64::from(quantization.logical_values_per_block);
1185                let component = self.bind_component(
1186                    blocks,
1187                    &block_dimensions,
1188                    WeightComponentRole::PackedValues,
1189                    depth,
1190                )?;
1191                if component.encoding != WeightEncoding::BlockQuantized(quantization) {
1192                    return Err(
1193                        self.invalid("block encoding changed while validating the physical layout")
1194                    );
1195                }
1196            }
1197            PhysicalWeightLayout::AxisReshapePermutation {
1198                values,
1199                axis,
1200                logical_offset,
1201                extent,
1202                reshape,
1203                stored_axis_order,
1204            } => {
1205                let axis = *axis as usize;
1206                let end = logical_offset.checked_add(*extent);
1207                let reshape_rank = reshape.len();
1208                let order_is_permutation = stored_axis_order.len() == reshape_rank
1209                    && stored_axis_order
1210                        .iter()
1211                        .all(|axis| (*axis as usize) < reshape_rank)
1212                    && stored_axis_order
1213                        .iter()
1214                        .copied()
1215                        .collect::<BTreeSet<_>>()
1216                        .len()
1217                        == reshape_rank;
1218                let order_is_identity = stored_axis_order
1219                    .iter()
1220                    .filter(|stored| reshape[**stored as usize] > 1)
1221                    .copied()
1222                    .eq(reshape
1223                        .iter()
1224                        .enumerate()
1225                        .filter_map(|(axis, extent)| (*extent > 1).then_some(axis as u32)));
1226                if axis >= semantic_dimensions.len()
1227                    || *extent == 0
1228                    || end.is_none_or(|end| end > semantic_dimensions[axis])
1229                    || reshape_rank < 2
1230                    || reshape.iter().any(|dimension| *dimension == 0)
1231                    || checked_elements(reshape) != Some(*extent)
1232                    || !order_is_permutation
1233                    || order_is_identity
1234                {
1235                    return Err(self.invalid(
1236                        "axis reshape permutation has invalid range, shape, or stored axis order",
1237                    ));
1238                }
1239                self.validate_layout(values, semantic_dimensions, logical_element_type, depth + 1)?;
1240            }
1241            PhysicalWeightLayout::Indexed {
1242                indices,
1243                values,
1244                source_axis_extent,
1245            } => {
1246                let axis = indices.axis as usize;
1247                if axis >= semantic_dimensions.len() || *source_axis_extent == 0 {
1248                    return Err(self.invalid("indexed layout axis or source extent is invalid"));
1249                }
1250                self.validate_axis_component(
1251                    indices,
1252                    semantic_dimensions,
1253                    axis,
1254                    WeightComponentRole::Indices,
1255                    true,
1256                    depth,
1257                )?;
1258                let mut source_dimensions = semantic_dimensions.to_vec();
1259                source_dimensions[axis] = *source_axis_extent;
1260                checked_elements(&source_dimensions)
1261                    .ok_or_else(|| self.invalid("indexed source semantic shape overflows u64"))?;
1262                self.validate_layout(values, &source_dimensions, logical_element_type, depth + 1)?;
1263            }
1264            PhysicalWeightLayout::ExpertStack {
1265                experts,
1266                expert_axis,
1267            } => {
1268                let axis = *expert_axis as usize;
1269                if axis >= semantic_dimensions.len() {
1270                    return Err(self.invalid("expert stack axis is out of range"));
1271                }
1272                let expected_count = usize::try_from(semantic_dimensions[axis]).map_err(|_| {
1273                    self.invalid("expert stack count does not fit the platform usize")
1274                })?;
1275                if experts.is_empty() || experts.len() != expected_count {
1276                    return Err(self
1277                        .invalid("expert stack child count differs from its logical expert axis"));
1278                }
1279                let mut expert_dimensions = semantic_dimensions.to_vec();
1280                expert_dimensions.remove(axis);
1281                if expert_dimensions.is_empty() {
1282                    return Err(self.invalid(
1283                        "expert stack children must retain at least one tensor dimension",
1284                    ));
1285                }
1286                for expert in experts {
1287                    self.validate_layout(
1288                        expert,
1289                        &expert_dimensions,
1290                        logical_element_type,
1291                        depth + 1,
1292                    )?;
1293                }
1294            }
1295        }
1296        Ok(())
1297    }
1298}
1299
1300fn is_axis_permutation(axis_order: &[u32], rank: usize) -> bool {
1301    axis_order.len() == rank
1302        && axis_order.iter().all(|axis| (*axis as usize) < rank)
1303        && axis_order.iter().copied().collect::<BTreeSet<_>>().len() == rank
1304}
1305
1306fn checked_round_up(extent: u64, multiple: u64) -> Option<u64> {
1307    if extent == 0 || multiple == 0 {
1308        return None;
1309    }
1310    extent
1311        .checked_add(multiple.checked_sub(1)?)
1312        .map(|rounded| rounded / multiple * multiple)
1313}
1314
1315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1316pub struct ProgramTensorSpec {
1317    pub dimensions: Vec<u64>,
1318    pub element_type: ElementType,
1319    pub layout: ResolvedTensorLayout,
1320}
1321
1322impl ProgramTensorSpec {
1323    pub fn validate(&self, field: &str) -> Result<(), VNextError> {
1324        super::ResolvedTensorSpec::new(
1325            self.dimensions.clone(),
1326            self.element_type,
1327            self.layout.clone(),
1328        )
1329        .map(|_| ())
1330        .map_err(|error| VNextError::InvalidExecutionPlan {
1331            reason: format!("{field} is invalid: {error}"),
1332        })
1333    }
1334
1335    pub fn byte_len(&self) -> Result<u64, VNextError> {
1336        checked_elements(&self.dimensions)
1337            .and_then(|elements| elements.checked_mul(self.element_type.size_bytes()))
1338            .ok_or_else(|| VNextError::InvalidExecutionPlan {
1339                reason: "program tensor byte size overflows u64".to_owned(),
1340            })
1341    }
1342}
1343
1344#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1345pub struct WeightReference {
1346    pub weight_id: WeightId,
1347    pub value_id: ProgramValueId,
1348    pub tensor: ProgramTensorSpec,
1349}
1350
1351#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1352pub struct StateSpec {
1353    pub id: StateId,
1354    pub value_id: ProgramValueId,
1355    /// Logical tensor view consumed by one operation. It does not select a
1356    /// physical allocator, addressability profile, or backing pool.
1357    pub tensor: ProgramTensorSpec,
1358    pub lifetime: StateLifetime,
1359    pub capacity_demand: StateCapacityDemand,
1360    /// Initial contents required when a new logical state scope acquires
1361    /// physical backing. This is semantic model state, not an allocator hint.
1362    pub initialization: StateInitialization,
1363}
1364
1365#[derive(Deserialize)]
1366#[serde(deny_unknown_fields)]
1367struct StateSpecWire {
1368    id: StateId,
1369    value_id: ProgramValueId,
1370    tensor: ProgramTensorSpec,
1371    lifetime: StateLifetime,
1372    capacity_demand: StateCapacityDemand,
1373    initialization: StateInitialization,
1374}
1375
1376impl<'de> Deserialize<'de> for StateSpec {
1377    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1378    where
1379        D: Deserializer<'de>,
1380    {
1381        let wire = StateSpecWire::deserialize(deserializer)?;
1382        wire.tensor
1383            .validate("state_spec.tensor")
1384            .and_then(|()| wire.capacity_demand.validate(wire.tensor.byte_len()?))
1385            .map_err(serde::de::Error::custom)?;
1386        Ok(Self {
1387            id: wire.id,
1388            value_id: wire.value_id,
1389            tensor: wire.tensor,
1390            lifetime: wire.lifetime,
1391            capacity_demand: wire.capacity_demand,
1392            initialization: wire.initialization,
1393        })
1394    }
1395}
1396
1397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1398#[serde(rename_all = "snake_case")]
1399pub enum StateLifetime {
1400    Request,
1401    Sequence,
1402    Step,
1403}
1404
1405/// Backend-neutral capacity formula for semantic state. This deliberately says
1406/// nothing about pages, blocks, allocator kind, or provider-visible regions.
1407/// Concrete physical storage is selected only while building an execution
1408/// plan from provider requirements, runtime offers, and typed policy.
1409#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1410#[serde(rename_all = "snake_case")]
1411pub enum StateCapacityDemand {
1412    FixedPerScope,
1413    TokenScaled {
1414        bytes_per_token: u64,
1415        maximum_tokens: u64,
1416    },
1417}
1418
1419#[derive(Deserialize)]
1420#[serde(rename_all = "snake_case", deny_unknown_fields)]
1421enum StateCapacityDemandWire {
1422    FixedPerScope,
1423    TokenScaled {
1424        bytes_per_token: u64,
1425        maximum_tokens: u64,
1426    },
1427}
1428
1429impl<'de> Deserialize<'de> for StateCapacityDemand {
1430    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1431    where
1432        D: Deserializer<'de>,
1433    {
1434        let demand = match StateCapacityDemandWire::deserialize(deserializer)? {
1435            StateCapacityDemandWire::FixedPerScope => Self::FixedPerScope,
1436            StateCapacityDemandWire::TokenScaled {
1437                bytes_per_token,
1438                maximum_tokens,
1439            } => Self::TokenScaled {
1440                bytes_per_token,
1441                maximum_tokens,
1442            },
1443        };
1444        demand.validate(1).map_err(serde::de::Error::custom)?;
1445        Ok(demand)
1446    }
1447}
1448
1449impl StateCapacityDemand {
1450    pub fn validate(self, tensor_minimum_bytes: u64) -> Result<(), VNextError> {
1451        let valid = match self {
1452            Self::FixedPerScope => tensor_minimum_bytes > 0,
1453            Self::TokenScaled {
1454                bytes_per_token,
1455                maximum_tokens,
1456            } => {
1457                bytes_per_token >= tensor_minimum_bytes
1458                    && maximum_tokens > 0
1459                    && bytes_per_token.checked_mul(maximum_tokens).is_some()
1460            }
1461        };
1462        if !valid {
1463            return Err(VNextError::InvalidExecutionPlan {
1464                reason: "state resource demand is zero, smaller than its tensor, or overflows u64"
1465                    .to_owned(),
1466            });
1467        }
1468        Ok(())
1469    }
1470
1471    pub fn minimum_bytes(self, tensor_minimum_bytes: u64) -> Result<u64, VNextError> {
1472        self.validate(tensor_minimum_bytes)?;
1473        Ok(match self {
1474            Self::FixedPerScope => tensor_minimum_bytes,
1475            Self::TokenScaled {
1476                bytes_per_token, ..
1477            } => bytes_per_token,
1478        })
1479    }
1480
1481    pub fn theoretical_bytes(self, tensor_minimum_bytes: u64) -> Result<u64, VNextError> {
1482        self.validate(tensor_minimum_bytes)?;
1483        match self {
1484            Self::FixedPerScope => Ok(tensor_minimum_bytes),
1485            Self::TokenScaled {
1486                bytes_per_token,
1487                maximum_tokens,
1488            } => bytes_per_token.checked_mul(maximum_tokens).ok_or_else(|| {
1489                VNextError::InvalidExecutionPlan {
1490                    reason: "token-scaled state demand overflows u64".to_owned(),
1491                }
1492            }),
1493        }
1494    }
1495}
1496
1497#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1498#[serde(rename_all = "snake_case")]
1499pub enum ProgramNodeWorkSpec {
1500    Fixed,
1501    Tokens { value_id: ProgramValueId, axis: u32 },
1502}
1503
1504impl ProgramNodeWorkSpec {
1505    pub fn tokens(value_id: ProgramValueId, axis: u32) -> Self {
1506        Self::Tokens { value_id, axis }
1507    }
1508}
1509
1510#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1511pub struct ProgramNode {
1512    pub id: NodeId,
1513    pub operation_id: OperationId,
1514    pub required_version: ContractVersion,
1515    pub work: ProgramNodeWorkSpec,
1516    pub inputs: Vec<ProgramValueId>,
1517    pub outputs: Vec<ProgramValueId>,
1518    pub attributes: BTreeMap<AttributeId, SemanticValue>,
1519}
1520
1521#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1522pub struct ProgramBlock {
1523    pub id: String,
1524    pub nodes: Vec<ProgramNode>,
1525}
1526
1527/// Backend-free semantic program for a model family.
1528#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1529pub struct ModelProgram {
1530    family_id: ModelFamilyId,
1531    inputs: Vec<ProgramValueId>,
1532    blocks: Vec<ProgramBlock>,
1533    states: Vec<StateSpec>,
1534    weights: Vec<WeightReference>,
1535    outputs: Vec<ProgramValueId>,
1536}
1537
1538#[derive(Deserialize)]
1539#[serde(deny_unknown_fields)]
1540struct ModelProgramWire {
1541    family_id: ModelFamilyId,
1542    inputs: Vec<ProgramValueId>,
1543    blocks: Vec<ProgramBlock>,
1544    states: Vec<StateSpec>,
1545    weights: Vec<WeightReference>,
1546    outputs: Vec<ProgramValueId>,
1547}
1548
1549impl<'de> Deserialize<'de> for ModelProgram {
1550    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1551    where
1552        D: Deserializer<'de>,
1553    {
1554        let wire = ModelProgramWire::deserialize(deserializer)?;
1555        Self::new(
1556            wire.family_id,
1557            wire.inputs,
1558            wire.blocks,
1559            wire.states,
1560            wire.weights,
1561            wire.outputs,
1562        )
1563        .map_err(serde::de::Error::custom)
1564    }
1565}
1566
1567impl ModelProgram {
1568    pub fn new(
1569        family_id: ModelFamilyId,
1570        inputs: Vec<ProgramValueId>,
1571        blocks: Vec<ProgramBlock>,
1572        mut states: Vec<StateSpec>,
1573        mut weights: Vec<WeightReference>,
1574        outputs: Vec<ProgramValueId>,
1575    ) -> Result<Self, VNextError> {
1576        if blocks.is_empty() {
1577            return Err(VNextError::InvalidModelConfig {
1578                family_id: family_id.to_string(),
1579                field: "program.blocks".to_owned(),
1580                reason: "at least one block is required".to_owned(),
1581            });
1582        }
1583        let mut known_values = BTreeSet::new();
1584        if inputs.is_empty()
1585            || inputs
1586                .iter()
1587                .any(|input| !known_values.insert(input.clone()))
1588        {
1589            return Err(VNextError::InvalidModelConfig {
1590                family_id: family_id.to_string(),
1591                field: "program.inputs".to_owned(),
1592                reason: "input identities must be non-empty and unique".to_owned(),
1593            });
1594        }
1595        let mut block_ids = BTreeSet::new();
1596        let mut node_ids = BTreeSet::new();
1597        for state in &states {
1598            let tensor_valid = state
1599                .tensor
1600                .validate(&format!("program.states.{}.tensor", state.id))
1601                .and_then(|()| state.capacity_demand.validate(state.tensor.byte_len()?));
1602            if tensor_valid.is_err() || !known_values.insert(state.value_id.clone()) {
1603                return Err(VNextError::InvalidModelConfig {
1604                    family_id: family_id.to_string(),
1605                    field: "program.states.value_id".to_owned(),
1606                    reason: format!("duplicate value `{}`", state.value_id),
1607                });
1608            }
1609        }
1610        let mut weight_ids = BTreeSet::new();
1611        for weight in &weights {
1612            if !weight_ids.insert(weight.weight_id.clone())
1613                || !known_values.insert(weight.value_id.clone())
1614            {
1615                return Err(VNextError::InvalidModelConfig {
1616                    family_id: family_id.to_string(),
1617                    field: "program.weights".to_owned(),
1618                    reason: format!(
1619                        "duplicate weight `{}` or value `{}`",
1620                        weight.weight_id, weight.value_id
1621                    ),
1622                });
1623            }
1624            weight
1625                .tensor
1626                .validate(&format!("program.weights.{}.tensor", weight.weight_id))?;
1627        }
1628        for block in &blocks {
1629            if block.id.is_empty() || block.nodes.is_empty() || !block_ids.insert(block.id.clone())
1630            {
1631                return Err(VNextError::InvalidModelConfig {
1632                    family_id: family_id.to_string(),
1633                    field: "program.blocks.id".to_owned(),
1634                    reason: "block identities must be non-empty and unique".to_owned(),
1635                });
1636            }
1637            for node in &block.nodes {
1638                if node.required_version.major == 0 || node.outputs.is_empty() {
1639                    return Err(VNextError::InvalidModelConfig {
1640                        family_id: family_id.to_string(),
1641                        field: "program.nodes.contract".to_owned(),
1642                        reason: format!("node `{}` has an invalid version or no outputs", node.id),
1643                    });
1644                }
1645                for value in node.attributes.values() {
1646                    value.validate(&format!("program node `{}` attributes", node.id))?;
1647                }
1648                if !node_ids.insert(node.id.clone()) {
1649                    return Err(VNextError::InvalidModelConfig {
1650                        family_id: family_id.to_string(),
1651                        field: "program.nodes.id".to_owned(),
1652                        reason: format!("duplicate node `{}`", node.id),
1653                    });
1654                }
1655                if let ProgramNodeWorkSpec::Tokens { value_id, .. } = &node.work {
1656                    let source_count = node
1657                        .inputs
1658                        .iter()
1659                        .chain(&node.outputs)
1660                        .filter(|candidate| *candidate == value_id)
1661                        .count();
1662                    let is_state_or_weight = states.iter().any(|state| state.value_id == *value_id)
1663                        || weights.iter().any(|weight| weight.value_id == *value_id);
1664                    if source_count != 1 || is_state_or_weight {
1665                        return Err(VNextError::InvalidModelConfig {
1666                            family_id: family_id.to_string(),
1667                            field: "program.nodes.work".to_owned(),
1668                            reason: format!(
1669                                "node `{}` token work source must identify one activation binding",
1670                                node.id
1671                            ),
1672                        });
1673                    }
1674                }
1675                if node
1676                    .inputs
1677                    .iter()
1678                    .any(|input| !known_values.contains(input))
1679                {
1680                    return Err(VNextError::InvalidModelConfig {
1681                        family_id: family_id.to_string(),
1682                        field: "program.nodes.inputs".to_owned(),
1683                        reason: format!("node `{}` references an unknown input", node.id),
1684                    });
1685                }
1686                for output in &node.outputs {
1687                    if !known_values.insert(output.clone()) {
1688                        return Err(VNextError::InvalidModelConfig {
1689                            family_id: family_id.to_string(),
1690                            field: "program.nodes.outputs".to_owned(),
1691                            reason: format!("value `{output}` has multiple producers"),
1692                        });
1693                    }
1694                }
1695            }
1696        }
1697        let mut state_ids = BTreeSet::new();
1698        if states
1699            .iter()
1700            .any(|state| !state_ids.insert(state.id.clone()))
1701        {
1702            return Err(VNextError::InvalidModelConfig {
1703                family_id: family_id.to_string(),
1704                field: "program.states.id".to_owned(),
1705                reason: "state identities must be unique".to_owned(),
1706            });
1707        }
1708        let mut output_ids = BTreeSet::new();
1709        if outputs.is_empty()
1710            || outputs
1711                .iter()
1712                .any(|output| !known_values.contains(output) || !output_ids.insert(output.clone()))
1713        {
1714            return Err(VNextError::InvalidModelConfig {
1715                family_id: family_id.to_string(),
1716                field: "program.outputs".to_owned(),
1717                reason: "program outputs must be non-empty, known, and unique".to_owned(),
1718            });
1719        }
1720        states.sort_by(|left, right| left.id.cmp(&right.id));
1721        weights.sort_by(|left, right| left.weight_id.cmp(&right.weight_id));
1722        Ok(Self {
1723            family_id,
1724            inputs,
1725            blocks,
1726            states,
1727            weights,
1728            outputs,
1729        })
1730    }
1731
1732    pub fn family_id(&self) -> &ModelFamilyId {
1733        &self.family_id
1734    }
1735
1736    pub fn inputs(&self) -> &[ProgramValueId] {
1737        &self.inputs
1738    }
1739
1740    pub fn blocks(&self) -> &[ProgramBlock] {
1741        &self.blocks
1742    }
1743
1744    pub fn states(&self) -> &[StateSpec] {
1745        &self.states
1746    }
1747
1748    pub fn weights(&self) -> &[WeightReference] {
1749        &self.weights
1750    }
1751
1752    pub fn outputs(&self) -> &[ProgramValueId] {
1753        &self.outputs
1754    }
1755
1756    pub fn fingerprint(&self) -> Result<String, VNextError> {
1757        let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
1758            context: "serialize model program",
1759            message: error.to_string(),
1760        })?;
1761        Ok(format!("{:x}", Sha256::digest(bytes)))
1762    }
1763}
1764
1765impl WeightSchema {
1766    pub fn validate_program_references(
1767        &self,
1768        family_id: &ModelFamilyId,
1769        program: &ModelProgram,
1770    ) -> Result<(), VNextError> {
1771        if program.family_id() != family_id {
1772            return Err(VNextError::InvalidModelConfig {
1773                family_id: family_id.to_string(),
1774                field: "program.family_id".to_owned(),
1775                reason: "program family does not match the weight schema owner".to_owned(),
1776            });
1777        }
1778        let schema_weights = self
1779            .tensors
1780            .iter()
1781            .map(|tensor| (&tensor.id, tensor.required))
1782            .collect::<BTreeMap<_, _>>();
1783        let referenced_weights = program
1784            .weights()
1785            .iter()
1786            .map(|reference| &reference.weight_id)
1787            .collect::<BTreeSet<_>>();
1788        if let Some(weight_id) = referenced_weights
1789            .iter()
1790            .find(|weight_id| !schema_weights.contains_key(**weight_id))
1791        {
1792            return Err(VNextError::InvalidModelConfig {
1793                family_id: family_id.to_string(),
1794                field: "program.weights".to_owned(),
1795                reason: format!("program references unknown weight `{weight_id}`"),
1796            });
1797        }
1798        if let Some(weight_id) = schema_weights.iter().find_map(|(weight_id, required)| {
1799            (*required && !referenced_weights.contains(weight_id)).then_some(*weight_id)
1800        }) {
1801            return Err(VNextError::InvalidModelConfig {
1802                family_id: family_id.to_string(),
1803                field: "program.weights".to_owned(),
1804                reason: format!("program does not reference required weight `{weight_id}`"),
1805            });
1806        }
1807        for reference in program.weights() {
1808            let tensor = self.tensor(&reference.weight_id).ok_or_else(|| {
1809                VNextError::InvalidModelConfig {
1810                    family_id: family_id.to_string(),
1811                    field: "program.weights".to_owned(),
1812                    reason: format!(
1813                        "program references unknown weight `{}`",
1814                        reference.weight_id
1815                    ),
1816                }
1817            })?;
1818            if reference.tensor.dimensions != tensor.dimensions
1819                || reference.tensor.element_type != tensor.logical_element_type
1820            {
1821                return Err(VNextError::InvalidModelConfig {
1822                    family_id: family_id.to_string(),
1823                    field: format!("program.weights.{}.tensor", reference.weight_id),
1824                    reason: "program value shape or dtype differs from the logical weight schema"
1825                        .to_owned(),
1826                });
1827            }
1828        }
1829        Ok(())
1830    }
1831}
1832
1833#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1834pub struct TemplateMetadata {
1835    pub template: String,
1836    pub source_file: String,
1837    pub sha256: String,
1838}
1839
1840#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1841#[serde(rename_all = "snake_case")]
1842pub enum SpecialTokenRole {
1843    Bos,
1844    Eos,
1845    Pad,
1846    Stop,
1847}
1848
1849#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
1850pub struct SpecialTokenCollision {
1851    first: SpecialTokenRole,
1852    second: SpecialTokenRole,
1853}
1854
1855#[derive(Deserialize)]
1856#[serde(deny_unknown_fields)]
1857struct SpecialTokenCollisionWire {
1858    first: SpecialTokenRole,
1859    second: SpecialTokenRole,
1860}
1861
1862impl<'de> Deserialize<'de> for SpecialTokenCollision {
1863    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1864    where
1865        D: Deserializer<'de>,
1866    {
1867        let wire = SpecialTokenCollisionWire::deserialize(deserializer)?;
1868        Self::new(wire.first, wire.second).map_err(serde::de::Error::custom)
1869    }
1870}
1871
1872impl SpecialTokenCollision {
1873    pub fn new(first: SpecialTokenRole, second: SpecialTokenRole) -> Result<Self, VNextError> {
1874        if first == second {
1875            return Err(VNextError::InvalidExecutionPlan {
1876                reason: "a special-token collision must name two different roles".to_owned(),
1877            });
1878        }
1879        let (first, second) = if first < second {
1880            (first, second)
1881        } else {
1882            (second, first)
1883        };
1884        Ok(Self { first, second })
1885    }
1886
1887    pub const fn first(&self) -> SpecialTokenRole {
1888        self.first
1889    }
1890
1891    pub const fn second(&self) -> SpecialTokenRole {
1892        self.second
1893    }
1894}
1895
1896#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1897pub struct SpecialTokenCollisionPolicy {
1898    allowed: BTreeSet<SpecialTokenCollision>,
1899}
1900
1901#[derive(Deserialize)]
1902#[serde(deny_unknown_fields)]
1903struct SpecialTokenCollisionPolicyWire {
1904    allowed: BTreeSet<SpecialTokenCollision>,
1905}
1906
1907impl<'de> Deserialize<'de> for SpecialTokenCollisionPolicy {
1908    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1909    where
1910        D: Deserializer<'de>,
1911    {
1912        let wire = SpecialTokenCollisionPolicyWire::deserialize(deserializer)?;
1913        Ok(Self::new(wire.allowed))
1914    }
1915}
1916
1917impl SpecialTokenCollisionPolicy {
1918    pub fn new(allowed: BTreeSet<SpecialTokenCollision>) -> Self {
1919        Self { allowed }
1920    }
1921
1922    pub fn require_distinct() -> Self {
1923        Self {
1924            allowed: BTreeSet::new(),
1925        }
1926    }
1927
1928    pub fn allows(&self, left: SpecialTokenRole, right: SpecialTokenRole) -> bool {
1929        SpecialTokenCollision::new(left, right)
1930            .is_ok_and(|collision| self.allowed.contains(&collision))
1931    }
1932
1933    pub fn allowed(&self) -> &BTreeSet<SpecialTokenCollision> {
1934        &self.allowed
1935    }
1936}
1937
1938#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1939pub struct SpecialTokenMetadata {
1940    pub bos_token_id: Option<u32>,
1941    pub eos_token_ids: BTreeSet<u32>,
1942    pub pad_token_id: Option<u32>,
1943    pub collision_policy: SpecialTokenCollisionPolicy,
1944}
1945
1946#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1947pub struct ModelSemanticMetadata {
1948    pub template: TemplateMetadata,
1949    pub special_tokens: SpecialTokenMetadata,
1950}
1951
1952/// Compile-time model family provider with a typed, validated configuration.
1953pub trait ModelFamilyProvider: Send + Sync {
1954    type Config: Clone + Send + Sync + Serialize + DeserializeOwned + 'static;
1955
1956    fn family_id(&self) -> &ModelFamilyId;
1957
1958    fn external_metadata_ids(&self) -> BTreeSet<ExternalModelMetadataId>;
1959
1960    fn validate_config_identity(
1961        &self,
1962        raw: &serde_json::Value,
1963        config: &Self::Config,
1964    ) -> Result<(), VNextError>;
1965
1966    /// Returns the exact external metadata identity represented by `config`.
1967    /// Every provider must make this selection explicit; core never assumes a
1968    /// singleton catalog row is the intended typed identity.
1969    fn validated_external_metadata_id(
1970        &self,
1971        raw: &serde_json::Value,
1972        config: &Self::Config,
1973    ) -> Result<ExternalModelMetadataId, VNextError>;
1974
1975    fn parse_config(&self, raw: &serde_json::Value) -> Result<Self::Config, VNextError>;
1976
1977    fn weight_schema(&self, config: &Self::Config) -> Result<WeightSchema, VNextError>;
1978
1979    fn semantic_program(&self, config: &Self::Config) -> Result<ModelProgram, VNextError>;
1980
1981    fn semantic_metadata(&self, config: &Self::Config)
1982        -> Result<ModelSemanticMetadata, VNextError>;
1983}
1984
1985/// Maximum raw JSON bytes accepted before decoding a prepared family package.
1986pub const MAX_PREPARED_MODEL_FAMILY_WIRE_BYTES: usize = 16 * 1024 * 1024;
1987
1988#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1989pub struct PreparedModelFamily {
1990    family_id: ModelFamilyId,
1991    external_metadata_id: ExternalModelMetadataId,
1992    canonical_config: serde_json::Value,
1993    config_fingerprint: String,
1994    weight_schema: WeightSchema,
1995    program: ModelProgram,
1996    metadata: ModelSemanticMetadata,
1997}
1998
1999/// Serialized prepared packages are evidence, not trusted runtime objects.
2000/// Rehydration must resolve the typed provider again and reproduce every field.
2001#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2002pub struct UnvalidatedPreparedModelFamily {
2003    family_id: ModelFamilyId,
2004    external_metadata_id: ExternalModelMetadataId,
2005    canonical_config: serde_json::Value,
2006    config_fingerprint: String,
2007    weight_schema: WeightSchema,
2008    program: ModelProgram,
2009    metadata: ModelSemanticMetadata,
2010}
2011
2012/// Crate-private serde shape used by both the top-level decoder and nested
2013/// resolved-plan wire. Public code can only obtain the explicit unvalidated
2014/// package through a byte-bounded decoder.
2015#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2016pub(crate) struct PreparedModelFamilyWire {
2017    family_id: ModelFamilyId,
2018    external_metadata_id: ExternalModelMetadataId,
2019    canonical_config: serde_json::Value,
2020    config_fingerprint: String,
2021    weight_schema: WeightSchema,
2022    program: ModelProgram,
2023    metadata: ModelSemanticMetadata,
2024}
2025
2026#[derive(Deserialize, Serialize)]
2027#[serde(deny_unknown_fields)]
2028struct PreparedModelFamilyWireFields {
2029    family_id: ModelFamilyId,
2030    external_metadata_id: ExternalModelMetadataId,
2031    canonical_config: serde_json::Value,
2032    config_fingerprint: String,
2033    weight_schema: WeightSchema,
2034    program: ModelProgram,
2035    metadata: ModelSemanticMetadata,
2036}
2037
2038impl<'de> Deserialize<'de> for PreparedModelFamilyWire {
2039    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2040    where
2041        D: Deserializer<'de>,
2042    {
2043        let raw = serde_json::Value::deserialize(deserializer)?;
2044        let fields =
2045            PreparedModelFamilyWireFields::deserialize(&raw).map_err(serde::de::Error::custom)?;
2046        let canonical = serde_json::to_value(&fields).map_err(serde::de::Error::custom)?;
2047        if canonical != raw {
2048            return Err(serde::de::Error::custom(
2049                "prepared model family wire contains unknown or non-canonical nested fields",
2050            ));
2051        }
2052        Ok(Self {
2053            family_id: fields.family_id,
2054            external_metadata_id: fields.external_metadata_id,
2055            canonical_config: fields.canonical_config,
2056            config_fingerprint: fields.config_fingerprint,
2057            weight_schema: fields.weight_schema,
2058            program: fields.program,
2059            metadata: fields.metadata,
2060        })
2061    }
2062}
2063
2064impl From<PreparedModelFamilyWire> for UnvalidatedPreparedModelFamily {
2065    fn from(wire: PreparedModelFamilyWire) -> Self {
2066        Self {
2067            family_id: wire.family_id,
2068            external_metadata_id: wire.external_metadata_id,
2069            canonical_config: wire.canonical_config,
2070            config_fingerprint: wire.config_fingerprint,
2071            weight_schema: wire.weight_schema,
2072            program: wire.program,
2073            metadata: wire.metadata,
2074        }
2075    }
2076}
2077
2078impl UnvalidatedPreparedModelFamily {
2079    pub fn revalidate(
2080        self,
2081        registry: &dyn ModelFamilyRegistry,
2082    ) -> Result<PreparedModelFamily, VNextError> {
2083        let registration = registry.resolve(&self.family_id)?;
2084        if registration.family_id() != &self.family_id {
2085            return Err(VNextError::InvalidModelConfig {
2086                family_id: self.family_id.to_string(),
2087                field: "registration.family_id".to_owned(),
2088                reason: "registry returned a registration for a different family".to_owned(),
2089            });
2090        }
2091        let metadata_registration = registry.resolve_external(&self.external_metadata_id)?;
2092        if !std::ptr::eq(registration, metadata_registration) {
2093            return Err(VNextError::InvalidModelConfig {
2094                family_id: self.family_id.to_string(),
2095                field: "external_metadata_id".to_owned(),
2096                reason: "external metadata identity resolves to a different family registration"
2097                    .to_owned(),
2098            });
2099        }
2100        let rebuilt = registration.prepare(&self.canonical_config)?;
2101        let exact_match = rebuilt.family_id == self.family_id
2102            && rebuilt.external_metadata_id == self.external_metadata_id
2103            && rebuilt.canonical_config == self.canonical_config
2104            && rebuilt.config_fingerprint == self.config_fingerprint
2105            && rebuilt.weight_schema == self.weight_schema
2106            && rebuilt.program == self.program
2107            && rebuilt.metadata == self.metadata;
2108        if !exact_match {
2109            return Err(VNextError::InvalidModelConfig {
2110                family_id: self.family_id.to_string(),
2111                field: "prepared_package".to_owned(),
2112                reason: "serialized package differs from the typed provider reconstruction"
2113                    .to_owned(),
2114            });
2115        }
2116        Ok(rebuilt)
2117    }
2118}
2119
2120impl PreparedModelFamily {
2121    fn from_canonical_config(
2122        family_id: ModelFamilyId,
2123        external_metadata_id: ExternalModelMetadataId,
2124        canonical_config: serde_json::Value,
2125        mut weight_schema: WeightSchema,
2126        program: ModelProgram,
2127        metadata: ModelSemanticMetadata,
2128    ) -> Result<Self, VNextError> {
2129        if !canonical_config.is_object()
2130            || canonicalize_json(canonical_config.clone()) != canonical_config
2131        {
2132            return Err(VNextError::InvalidModelConfig {
2133                family_id: family_id.to_string(),
2134                field: "config".to_owned(),
2135                reason: "prepared config must be a canonical JSON object".to_owned(),
2136            });
2137        }
2138        let config_bytes =
2139            serde_json::to_vec(&canonical_config).map_err(|error| VNextError::Serialization {
2140                context: "serialize canonical model family config",
2141                message: error.to_string(),
2142            })?;
2143        let config_fingerprint = format!("{:x}", Sha256::digest(config_bytes));
2144        weight_schema.validate(&family_id)?;
2145        weight_schema.normalize();
2146        weight_schema.validate(&family_id)?;
2147        if program.family_id() != &family_id {
2148            return Err(VNextError::InvalidModelConfig {
2149                family_id: family_id.to_string(),
2150                field: "program.family_id".to_owned(),
2151                reason: "program family does not match prepared family".to_owned(),
2152            });
2153        }
2154        weight_schema.validate_program_references(&family_id, &program)?;
2155        Self::validate_metadata(&family_id, &metadata)?;
2156        Ok(Self {
2157            family_id,
2158            external_metadata_id,
2159            canonical_config,
2160            config_fingerprint,
2161            weight_schema,
2162            program,
2163            metadata,
2164        })
2165    }
2166
2167    fn validate_metadata(
2168        family_id: &ModelFamilyId,
2169        metadata: &ModelSemanticMetadata,
2170    ) -> Result<(), VNextError> {
2171        let source = metadata.template.source_file.as_str();
2172        let valid_source = !source.is_empty()
2173            && !source.starts_with('/')
2174            && !source.contains('\\')
2175            && source
2176                .split('/')
2177                .all(|component| !matches!(component, "" | "." | ".."));
2178        if metadata.template.template.is_empty()
2179            || !valid_source
2180            || !is_canonical_sha256(&metadata.template.sha256)
2181            || metadata.special_tokens.eos_token_ids.is_empty()
2182        {
2183            return Err(VNextError::InvalidModelConfig {
2184                family_id: family_id.to_string(),
2185                field: "semantic_metadata".to_owned(),
2186                reason: "template, source, checksum, and end tokens must be explicit and valid"
2187                    .to_owned(),
2188            });
2189        }
2190        Ok(())
2191    }
2192
2193    pub fn family_id(&self) -> &ModelFamilyId {
2194        &self.family_id
2195    }
2196
2197    pub fn external_metadata_id(&self) -> &ExternalModelMetadataId {
2198        &self.external_metadata_id
2199    }
2200
2201    pub fn canonical_config(&self) -> &serde_json::Value {
2202        &self.canonical_config
2203    }
2204
2205    pub fn config_fingerprint(&self) -> &str {
2206        &self.config_fingerprint
2207    }
2208
2209    pub fn weight_schema(&self) -> &WeightSchema {
2210        &self.weight_schema
2211    }
2212
2213    pub fn program(&self) -> &ModelProgram {
2214        &self.program
2215    }
2216
2217    pub fn metadata(&self) -> &ModelSemanticMetadata {
2218        &self.metadata
2219    }
2220
2221    pub fn fingerprint(&self) -> Result<String, VNextError> {
2222        let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
2223            context: "serialize prepared model family",
2224            message: error.to_string(),
2225        })?;
2226        Ok(format!("{:x}", Sha256::digest(bytes)))
2227    }
2228
2229    pub fn decode_untrusted(bytes: &[u8]) -> Result<UnvalidatedPreparedModelFamily, VNextError> {
2230        if bytes.len() > MAX_PREPARED_MODEL_FAMILY_WIRE_BYTES {
2231            return Err(VNextError::Serialization {
2232                context: "decode untrusted prepared model family",
2233                message: format!(
2234                    "payload has {} bytes; maximum is {MAX_PREPARED_MODEL_FAMILY_WIRE_BYTES}",
2235                    bytes.len()
2236                ),
2237            });
2238        }
2239        serde_json::from_slice::<PreparedModelFamilyWire>(bytes)
2240            .map(Into::into)
2241            .map_err(|error| VNextError::Serialization {
2242                context: "decode untrusted prepared model family",
2243                message: error.to_string(),
2244            })
2245    }
2246
2247    pub fn from_json_validated(
2248        bytes: &[u8],
2249        registry: &dyn ModelFamilyRegistry,
2250    ) -> Result<Self, VNextError> {
2251        Self::decode_untrusted(bytes)?.revalidate(registry)
2252    }
2253}
2254
2255fn canonicalize_json(value: serde_json::Value) -> serde_json::Value {
2256    match value {
2257        serde_json::Value::Array(values) => {
2258            serde_json::Value::Array(values.into_iter().map(canonicalize_json).collect())
2259        }
2260        serde_json::Value::Object(values) => {
2261            let sorted = values
2262                .into_iter()
2263                .map(|(key, value)| (key, canonicalize_json(value)))
2264                .collect::<BTreeMap<_, _>>();
2265            serde_json::Value::Object(sorted.into_iter().collect())
2266        }
2267        other => other,
2268    }
2269}
2270
2271fn validate_raw_config_consumed(
2272    family_id: &ModelFamilyId,
2273    raw: &serde_json::Value,
2274    typed: &serde_json::Value,
2275) -> Result<(), VNextError> {
2276    // Typed serialization may add explicit provider defaults. Every caller-
2277    // supplied value must still survive at the same path with the same JSON
2278    // value; serde's default unknown-field behavior cannot erase input here.
2279    fn walk(raw: &serde_json::Value, typed: &serde_json::Value, path: &str) -> Option<String> {
2280        match (raw, typed) {
2281            (serde_json::Value::Object(raw), serde_json::Value::Object(typed)) => {
2282                for (key, raw_value) in raw {
2283                    let next = if path.is_empty() {
2284                        format!("/{key}")
2285                    } else {
2286                        format!("{path}/{key}")
2287                    };
2288                    let Some(typed_value) = typed.get(key) else {
2289                        return Some(next);
2290                    };
2291                    if let Some(rejected) = walk(raw_value, typed_value, &next) {
2292                        return Some(rejected);
2293                    }
2294                }
2295                None
2296            }
2297            (serde_json::Value::Array(raw), serde_json::Value::Array(typed))
2298                if raw.len() == typed.len() =>
2299            {
2300                raw.iter()
2301                    .zip(typed)
2302                    .enumerate()
2303                    .find_map(|(index, (raw, typed))| walk(raw, typed, &format!("{path}/{index}")))
2304            }
2305            _ if raw == typed => None,
2306            _ => Some(path.to_owned()),
2307        }
2308    }
2309
2310    if !raw.is_object() || !typed.is_object() {
2311        return Err(VNextError::InvalidModelConfig {
2312            family_id: family_id.to_string(),
2313            field: "config".to_owned(),
2314            reason: "raw and typed model configurations must be JSON objects".to_owned(),
2315        });
2316    }
2317    if let Some(path) = walk(raw, typed, "") {
2318        return Err(VNextError::InvalidModelConfig {
2319            family_id: family_id.to_string(),
2320            field: if path.is_empty() {
2321                "config".to_owned()
2322            } else {
2323                path
2324            },
2325            reason: "raw configuration field was ignored or changed by typed parsing".to_owned(),
2326        });
2327    }
2328    Ok(())
2329}
2330
2331fn is_canonical_sha256(value: &str) -> bool {
2332    value.len() == 64
2333        && value
2334            .bytes()
2335            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2336}
2337
2338/// Object-safe loading-time trampoline for a heterogeneous family catalog.
2339/// The raw JSON exists only at the configuration boundary; a typed provider
2340/// validates it before producing the backend-free package.
2341pub trait ModelFamilyRegistration: Send + Sync {
2342    fn family_id(&self) -> &ModelFamilyId;
2343
2344    fn external_metadata_ids(&self) -> BTreeSet<ExternalModelMetadataId>;
2345
2346    fn prepare(&self, raw_config: &serde_json::Value) -> Result<PreparedModelFamily, VNextError>;
2347}
2348
2349pub struct TypedFamilyRegistration<P> {
2350    provider: P,
2351}
2352
2353impl<P> TypedFamilyRegistration<P> {
2354    pub fn new(provider: P) -> Self {
2355        Self { provider }
2356    }
2357}
2358
2359impl<P: ModelFamilyProvider> ModelFamilyRegistration for TypedFamilyRegistration<P> {
2360    fn family_id(&self) -> &ModelFamilyId {
2361        self.provider.family_id()
2362    }
2363
2364    fn external_metadata_ids(&self) -> BTreeSet<ExternalModelMetadataId> {
2365        self.provider.external_metadata_ids()
2366    }
2367
2368    fn prepare(&self, raw_config: &serde_json::Value) -> Result<PreparedModelFamily, VNextError> {
2369        let external_metadata_ids = self.provider.external_metadata_ids();
2370        if external_metadata_ids.is_empty() {
2371            return Err(VNextError::InvalidModelConfig {
2372                family_id: self.provider.family_id().to_string(),
2373                field: "external_metadata_ids".to_owned(),
2374                reason: "model family must declare at least one external metadata identity"
2375                    .to_owned(),
2376            });
2377        }
2378        let config = self.provider.parse_config(raw_config)?;
2379        let external_metadata_id = self
2380            .provider
2381            .validated_external_metadata_id(raw_config, &config)?;
2382        if !external_metadata_ids.contains(&external_metadata_id) {
2383            return Err(VNextError::InvalidModelConfig {
2384                family_id: self.provider.family_id().to_string(),
2385                field: "external_metadata_id".to_owned(),
2386                reason: format!(
2387                    "provider selected undeclared external metadata identity `{external_metadata_id}`"
2388                ),
2389            });
2390        }
2391        let typed_config = canonicalize_json(serde_json::to_value(&config).map_err(|error| {
2392            VNextError::Serialization {
2393                context: "serialize typed model configuration",
2394                message: error.to_string(),
2395            }
2396        })?);
2397        validate_raw_config_consumed(self.provider.family_id(), raw_config, &typed_config)?;
2398        let weight_schema = self.provider.weight_schema(&config)?;
2399        let program = self.provider.semantic_program(&config)?;
2400        let metadata = self.provider.semantic_metadata(&config)?;
2401        PreparedModelFamily::from_canonical_config(
2402            self.provider.family_id().clone(),
2403            external_metadata_id,
2404            typed_config,
2405            weight_schema,
2406            program,
2407            metadata,
2408        )
2409    }
2410}
2411
2412pub trait ModelFamilyRegistry: Send + Sync {
2413    /// Returns the complete trusted catalog. Core owns all identity lookup so a
2414    /// registry cannot silently fall back or hide ambiguous registrations.
2415    fn registrations(&self) -> Vec<&dyn ModelFamilyRegistration>;
2416}
2417
2418impl dyn ModelFamilyRegistry + '_ {
2419    pub fn resolve(
2420        &self,
2421        family_id: &ModelFamilyId,
2422    ) -> Result<&dyn ModelFamilyRegistration, VNextError> {
2423        let matches = self
2424            .registrations()
2425            .into_iter()
2426            .filter(|registration| registration.family_id() == family_id)
2427            .collect::<Vec<_>>();
2428        match matches.as_slice() {
2429            [] => Err(VNextError::UnknownModelFamily {
2430                family_id: family_id.to_string(),
2431            }),
2432            [registration] => Ok(*registration),
2433            _ => Err(VNextError::AmbiguousModelFamilyRegistration {
2434                identity_kind: "internal family",
2435                identity: family_id.to_string(),
2436                matches: matches.len(),
2437            }),
2438        }
2439    }
2440
2441    pub fn resolve_external(
2442        &self,
2443        metadata_id: &ExternalModelMetadataId,
2444    ) -> Result<&dyn ModelFamilyRegistration, VNextError> {
2445        let matches = self
2446            .registrations()
2447            .into_iter()
2448            .filter(|registration| registration.external_metadata_ids().contains(metadata_id))
2449            .collect::<Vec<_>>();
2450        match matches.as_slice() {
2451            [] => Err(VNextError::UnknownExternalModelMetadata {
2452                metadata_id: metadata_id.to_string(),
2453            }),
2454            [registration] => Ok(*registration),
2455            _ => Err(VNextError::AmbiguousModelFamilyRegistration {
2456                identity_kind: "external metadata",
2457                identity: metadata_id.to_string(),
2458                matches: matches.len(),
2459            }),
2460        }
2461    }
2462}
2463
2464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2465pub struct TokenizerDescriptor {
2466    pub tokenizer_id: TokenizerId,
2467    pub source_file: String,
2468    pub sha256: String,
2469    pub vocabulary_size: u64,
2470}