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