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