1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5use super::super::{
6 CanonicalRational, ContractVersion, QuantizationFormatId, VNextError, WeightFormatId, WeightId,
7 WeightLayoutId,
8};
9use super::ElementType;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum QuantizationPacking {
14 Linear,
15 Interleaved,
16 Tiled,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(tag = "kind", rename_all = "snake_case")]
26pub enum QuantizationGrouping {
27 Fixed { size: u32 },
28 WholeAxis,
29}
30
31impl QuantizationGrouping {
32 pub const fn fixed(size: u32) -> Self {
33 Self::Fixed { size }
34 }
35
36 pub const fn fixed_size(self) -> Option<u32> {
37 match self {
38 Self::Fixed { size } => Some(size),
39 Self::WholeAxis => None,
40 }
41 }
42
43 pub const fn resolved_size(self, axis_extent: u64) -> u64 {
44 match self {
45 Self::Fixed { size } => size as u64,
46 Self::WholeAxis => axis_extent,
47 }
48 }
49
50 const fn is_valid(self) -> bool {
51 match self {
52 Self::Fixed { size } => size != 0 && size.is_power_of_two(),
53 Self::WholeAxis => true,
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct QuantizationSpec {
60 pub format_id: QuantizationFormatId,
61 pub bits_per_weight: u8,
62 pub grouping: QuantizationGrouping,
63 pub packing: QuantizationPacking,
64 pub scale_type: ElementType,
65 pub zero_point_type: Option<ElementType>,
66}
67
68impl QuantizationSpec {
69 pub fn validate(&self) -> Result<(), VNextError> {
70 if !(1..=8).contains(&self.bits_per_weight)
71 || !self.grouping.is_valid()
72 || !matches!(
73 self.scale_type,
74 ElementType::F16 | ElementType::Bf16 | ElementType::F32
75 )
76 || self.zero_point_type.is_some_and(|element_type| {
77 !matches!(
78 element_type,
79 ElementType::U8 | ElementType::U32 | ElementType::I8 | ElementType::I32
80 )
81 })
82 {
83 return Err(VNextError::InvalidExecutionPlan {
84 reason: format!("invalid quantization format `{}`", self.format_id),
85 });
86 }
87 Ok(())
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96pub struct BlockQuantizationSpec {
97 pub format_id: QuantizationFormatId,
98 pub logical_values_per_block: u32,
99 pub bytes_per_block: u32,
100}
101
102impl BlockQuantizationSpec {
103 pub fn validate(&self) -> Result<(), VNextError> {
104 if self.logical_values_per_block == 0 || self.bytes_per_block == 0 {
105 return Err(VNextError::InvalidExecutionPlan {
106 reason: format!("invalid block quantization format `{}`", self.format_id),
107 });
108 }
109 Ok(())
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum WeightEncoding {
116 Dense {
117 element_type: ElementType,
118 },
119 DenseAffine {
124 element_type: ElementType,
125 scale: CanonicalRational,
126 bias: CanonicalRational,
127 },
128 Quantized(QuantizationSpec),
129 BlockQuantized(BlockQuantizationSpec),
130}
131
132impl WeightEncoding {
133 pub const fn dense_element_type(&self) -> Option<ElementType> {
134 match self {
135 Self::Dense { element_type } | Self::DenseAffine { element_type, .. } => {
136 Some(*element_type)
137 }
138 Self::Quantized(_) | Self::BlockQuantized(_) => None,
139 }
140 }
141
142 pub(crate) fn physical_bytes(
143 &self,
144 dimensions: &[u64],
145 component_id: &WeightId,
146 ) -> Result<u64, VNextError> {
147 let elements =
148 checked_elements(dimensions).ok_or_else(|| VNextError::InvalidExecutionPlan {
149 reason: format!("physical component `{component_id}` size overflows u64"),
150 })?;
151 match self {
152 Self::Dense { element_type } | Self::DenseAffine { element_type, .. } => elements
153 .checked_mul(element_type.size_bytes())
154 .ok_or_else(|| VNextError::InvalidExecutionPlan {
155 reason: format!("physical component `{component_id}` byte size overflows u64"),
156 }),
157 Self::Quantized(_) => Ok(elements),
158 Self::BlockQuantized(spec) => {
159 spec.validate()?;
160 elements
161 .checked_mul(u64::from(spec.bytes_per_block))
162 .ok_or_else(|| VNextError::InvalidExecutionPlan {
163 reason: format!(
164 "physical block component `{component_id}` byte size overflows u64"
165 ),
166 })
167 }
168 }
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
175#[serde(rename_all = "snake_case")]
176pub enum WeightComponentRole {
177 Values,
178 PackedValues,
179 Scales,
180 ZeroPoints,
181 Indices,
182 Permutation,
183 Codebook,
184 Metadata,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(rename_all = "snake_case")]
193pub enum PhysicalWeightPadding {
194 Exact,
195 ZeroFill { padded_dimensions: Vec<u64> },
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205#[serde(rename_all = "snake_case")]
206pub enum PhysicalStorageLayout {
207 Contiguous {
208 padding: PhysicalWeightPadding,
209 },
210 Strided {
211 strides_in_elements: Vec<u64>,
212 padding: PhysicalWeightPadding,
213 },
214 Tiled {
215 tile_shape: Vec<u64>,
216 axis_order: Vec<u32>,
218 tile_strides_in_elements: Vec<u64>,
219 padding: PhysicalWeightPadding,
220 },
221}
222
223impl PhysicalStorageLayout {
224 pub fn exact_contiguous() -> Self {
225 Self::Contiguous {
226 padding: PhysicalWeightPadding::Exact,
227 }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
232pub struct PhysicalWeightComponentBinding {
233 pub component_id: WeightId,
234 pub storage: PhysicalStorageLayout,
235}
236
237impl PhysicalWeightComponentBinding {
238 pub fn exact_contiguous(component_id: WeightId) -> Self {
239 Self {
240 component_id,
241 storage: PhysicalStorageLayout::exact_contiguous(),
242 }
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub struct AxisWeightComponent {
248 pub component: PhysicalWeightComponentBinding,
249 pub axis: u32,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub struct CompositeWeightPart {
254 pub layout: Box<PhysicalWeightLayout>,
255 pub logical_offsets: Vec<u64>,
256 pub extents: Vec<u64>,
257}
258
259pub const MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH: usize = 16;
263pub const MAX_PHYSICAL_WEIGHT_LAYOUT_NODES: usize = 4096;
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(rename_all = "snake_case")]
271pub enum PhysicalWeightLayout {
272 Dense {
275 component_id: WeightId,
276 },
277 Stored {
278 component: PhysicalWeightComponentBinding,
279 },
280 Composite {
281 parts: Vec<CompositeWeightPart>,
282 },
283 Quantized {
284 packed_values: PhysicalWeightComponentBinding,
285 packed_dimensions: Vec<u64>,
289 scales: PhysicalWeightComponentBinding,
290 zero_points: Option<PhysicalWeightComponentBinding>,
291 zero_point_packed_dimensions: Option<Vec<u64>>,
297 axis_indices: Option<AxisWeightComponent>,
301 permutation: Option<AxisWeightComponent>,
302 codebook: Option<PhysicalWeightComponentBinding>,
303 group_axis: u32,
304 group_padding: PhysicalWeightPadding,
305 },
306 BlockQuantized {
310 blocks: PhysicalWeightComponentBinding,
311 block_axis: u32,
312 block_padding: PhysicalWeightPadding,
313 },
314 AxisReshapePermutation {
319 values: Box<PhysicalWeightLayout>,
320 axis: u32,
321 logical_offset: u64,
322 extent: u64,
323 reshape: Vec<u64>,
324 stored_axis_order: Vec<u32>,
327 },
328 Indexed {
329 indices: AxisWeightComponent,
330 values: Box<PhysicalWeightLayout>,
331 source_axis_extent: u64,
332 },
333 ExpertStack {
334 experts: Vec<PhysicalWeightLayout>,
335 expert_axis: u32,
336 },
337}
338
339impl PhysicalWeightLayout {
340 pub(crate) fn normalize(&mut self) {
341 match self {
342 Self::Composite { parts } => {
343 for part in parts.iter_mut() {
344 part.layout.normalize();
345 }
346 parts.sort_by(|left, right| {
350 left.logical_offsets
351 .cmp(&right.logical_offsets)
352 .then_with(|| left.extents.cmp(&right.extents))
353 });
354 }
355 Self::AxisReshapePermutation { values, .. } | Self::Indexed { values, .. } => {
356 values.normalize()
357 }
358 Self::ExpertStack { experts, .. } => {
359 for expert in experts {
362 expert.normalize();
363 }
364 }
365 Self::Dense { .. }
366 | Self::Stored { .. }
367 | Self::Quantized { .. }
368 | Self::BlockQuantized { .. } => {}
369 }
370 }
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
377pub struct ResolvedWeightComponentLayout {
378 component_id: WeightId,
379 role: WeightComponentRole,
380 physical_dimensions: Vec<u64>,
381 encoding: WeightEncoding,
382}
383
384impl ResolvedWeightComponentLayout {
385 pub(crate) fn from_parts(
386 component_id: WeightId,
387 role: WeightComponentRole,
388 physical_dimensions: Vec<u64>,
389 encoding: WeightEncoding,
390 ) -> Self {
391 Self {
392 component_id,
393 role,
394 physical_dimensions,
395 encoding,
396 }
397 }
398
399 pub fn component_id(&self) -> &WeightId {
400 &self.component_id
401 }
402
403 pub const fn role(&self) -> WeightComponentRole {
404 self.role
405 }
406
407 pub fn physical_dimensions(&self) -> &[u64] {
408 &self.physical_dimensions
409 }
410
411 pub fn encoding(&self) -> &WeightEncoding {
412 &self.encoding
413 }
414
415 pub fn physical_bytes(&self) -> Result<u64, VNextError> {
416 self.encoding
417 .physical_bytes(&self.physical_dimensions, &self.component_id)
418 }
419
420 pub fn physical_element_type(&self) -> ElementType {
421 self.encoding
422 .dense_element_type()
423 .unwrap_or(ElementType::U8)
424 }
425}
426
427#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
436pub struct ResolvedWeightBinding {
437 weight_id: WeightId,
438 #[serde(rename = "format_id")]
439 schema_format_id: WeightFormatId,
440 layout_id: WeightLayoutId,
441 schema_version: ContractVersion,
442 physical_layout: PhysicalWeightLayout,
443 components: Vec<ResolvedWeightComponentLayout>,
444}
445
446pub(crate) trait ResolvedWeightLogicalValidation {
453 fn validate_logical_contract(
454 &self,
455 logical_dimensions: &[u64],
456 logical_element_type: ElementType,
457 ) -> Result<(), VNextError>;
458}
459
460#[derive(Deserialize)]
461#[serde(deny_unknown_fields)]
462struct ResolvedWeightBindingWire {
463 weight_id: WeightId,
464 format_id: WeightFormatId,
465 layout_id: WeightLayoutId,
466 schema_version: ContractVersion,
467 physical_layout: PhysicalWeightLayout,
468 components: Vec<ResolvedWeightComponentLayout>,
469}
470
471impl<'de> Deserialize<'de> for ResolvedWeightBinding {
472 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
473 where
474 D: Deserializer<'de>,
475 {
476 let wire = ResolvedWeightBindingWire::deserialize(deserializer)?;
477 Self::from_parts(
478 wire.weight_id,
479 wire.format_id,
480 wire.layout_id,
481 wire.schema_version,
482 wire.physical_layout,
483 wire.components,
484 )
485 .map_err(serde::de::Error::custom)
486 }
487}
488
489impl ResolvedWeightBinding {
490 pub(crate) fn from_parts(
491 weight_id: WeightId,
492 schema_format_id: WeightFormatId,
493 layout_id: WeightLayoutId,
494 schema_version: ContractVersion,
495 physical_layout: PhysicalWeightLayout,
496 components: Vec<ResolvedWeightComponentLayout>,
497 ) -> Result<Self, VNextError> {
498 let binding = Self {
499 weight_id,
500 schema_format_id,
501 layout_id,
502 schema_version,
503 physical_layout,
504 components,
505 };
506 binding.validate_structure()?;
507 Ok(binding)
508 }
509
510 pub(crate) fn validate_structure(&self) -> Result<(), VNextError> {
511 validate_physical_layout_budget(&self.physical_layout).map_err(|reason| {
512 VNextError::InvalidExecutionPlan {
513 reason: format!("resolved weight `{}` layout: {reason}", self.weight_id),
514 }
515 })?;
516 let referenced = physical_component_ids(&self.physical_layout).map_err(|reason| {
517 VNextError::InvalidExecutionPlan {
518 reason: format!("resolved weight `{}` layout: {reason}", self.weight_id),
519 }
520 })?;
521 let component_ids = self
522 .components
523 .iter()
524 .map(|component| component.component_id.clone())
525 .collect::<BTreeSet<_>>();
526 let canonical_components = self
527 .components
528 .windows(2)
529 .all(|pair| pair[0].component_id < pair[1].component_id);
530 if self.schema_version.major == 0
531 || self.components.is_empty()
532 || !canonical_components
533 || component_ids.len() != self.components.len()
534 || component_ids != referenced
535 || self.components.iter().any(|component| {
536 component.physical_dimensions.is_empty()
537 || component
538 .physical_dimensions
539 .iter()
540 .any(|extent| *extent == 0)
541 || component.physical_bytes().is_err()
542 })
543 {
544 return Err(VNextError::InvalidExecutionPlan {
545 reason: format!(
546 "resolved weight `{}` physical identity is invalid or non-canonical",
547 self.weight_id
548 ),
549 });
550 }
551 Ok(())
552 }
553
554 pub fn weight_id(&self) -> &WeightId {
555 &self.weight_id
556 }
557
558 pub(crate) fn schema_format_id(&self) -> &WeightFormatId {
562 &self.schema_format_id
563 }
564
565 pub fn layout_id(&self) -> &WeightLayoutId {
566 &self.layout_id
567 }
568
569 pub const fn schema_version(&self) -> ContractVersion {
570 self.schema_version
571 }
572
573 pub fn physical_layout(&self) -> &PhysicalWeightLayout {
574 &self.physical_layout
575 }
576
577 pub fn components(&self) -> &[ResolvedWeightComponentLayout] {
578 &self.components
579 }
580
581 pub fn quantization_formats(&self) -> BTreeSet<QuantizationFormatId> {
582 self.components
583 .iter()
584 .filter_map(|component| match &component.encoding {
585 WeightEncoding::Quantized(spec) => Some(spec.format_id.clone()),
586 WeightEncoding::BlockQuantized(spec) => Some(spec.format_id.clone()),
587 WeightEncoding::Dense { .. } | WeightEncoding::DenseAffine { .. } => None,
588 })
589 .collect()
590 }
591}
592
593fn push_physical_layout_child<'a>(
594 stack: &mut Vec<(&'a PhysicalWeightLayout, usize)>,
595 child: &'a PhysicalWeightLayout,
596 child_depth: usize,
597 visited: usize,
598) -> Result<(), String> {
599 if visited
600 .checked_add(stack.len())
601 .is_none_or(|pending| pending >= MAX_PHYSICAL_WEIGHT_LAYOUT_NODES)
602 {
603 return Err(format!(
604 "physical layout node count exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_NODES}"
605 ));
606 }
607 stack.push((child, child_depth));
608 Ok(())
609}
610
611pub(crate) fn validate_physical_layout_budget(layout: &PhysicalWeightLayout) -> Result<(), String> {
612 let mut stack = vec![(layout, 1_usize)];
613 let mut visited = 0_usize;
614 while let Some((node, depth)) = stack.pop() {
615 if depth > MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH {
616 return Err(format!(
617 "physical layout depth exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH}"
618 ));
619 }
620 let direct_bindings = match node {
621 PhysicalWeightLayout::Dense { .. } | PhysicalWeightLayout::Stored { .. } => 1,
622 PhysicalWeightLayout::Quantized {
623 zero_points,
624 axis_indices,
625 permutation,
626 codebook,
627 ..
628 } => {
629 2 + usize::from(zero_points.is_some())
630 + usize::from(axis_indices.is_some())
631 + usize::from(permutation.is_some())
632 + usize::from(codebook.is_some())
633 }
634 PhysicalWeightLayout::BlockQuantized { .. } => 1,
635 PhysicalWeightLayout::AxisReshapePermutation { .. } => 0,
636 PhysicalWeightLayout::Indexed { .. } => 1,
637 PhysicalWeightLayout::Composite { .. } | PhysicalWeightLayout::ExpertStack { .. } => 0,
638 };
639 visited = visited
640 .checked_add(1 + direct_bindings)
641 .ok_or_else(|| "physical layout node count overflows usize".to_owned())?;
642 if visited > MAX_PHYSICAL_WEIGHT_LAYOUT_NODES {
643 return Err(format!(
644 "physical layout node count exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_NODES}"
645 ));
646 }
647 let child_depth = depth
648 .checked_add(1)
649 .ok_or_else(|| "physical layout depth overflows usize".to_owned())?;
650 match node {
651 PhysicalWeightLayout::Composite { parts } => {
652 for part in parts {
653 push_physical_layout_child(&mut stack, &part.layout, child_depth, visited)?;
654 }
655 }
656 PhysicalWeightLayout::AxisReshapePermutation { values, .. }
657 | PhysicalWeightLayout::Indexed { values, .. } => {
658 push_physical_layout_child(&mut stack, values, child_depth, visited)?;
659 }
660 PhysicalWeightLayout::ExpertStack { experts, .. } => {
661 for expert in experts {
662 push_physical_layout_child(&mut stack, expert, child_depth, visited)?;
663 }
664 }
665 PhysicalWeightLayout::Dense { .. }
666 | PhysicalWeightLayout::Stored { .. }
667 | PhysicalWeightLayout::Quantized { .. }
668 | PhysicalWeightLayout::BlockQuantized { .. } => {}
669 }
670 }
671 Ok(())
672}
673
674pub(crate) fn physical_component_ids(
675 layout: &PhysicalWeightLayout,
676) -> Result<BTreeSet<WeightId>, String> {
677 validate_physical_layout_budget(layout)?;
678 let mut ids = BTreeSet::new();
679 let mut stack = vec![layout];
680 while let Some(node) = stack.pop() {
681 let mut insert_binding = |binding: &PhysicalWeightComponentBinding| {
682 ids.insert(binding.component_id.clone());
683 };
684 match node {
685 PhysicalWeightLayout::Dense { component_id } => {
686 ids.insert(component_id.clone());
687 }
688 PhysicalWeightLayout::Stored { component } => insert_binding(component),
689 PhysicalWeightLayout::Composite { parts } => {
690 stack.extend(parts.iter().map(|part| part.layout.as_ref()));
691 }
692 PhysicalWeightLayout::Quantized {
693 packed_values,
694 scales,
695 zero_points,
696 axis_indices,
697 permutation,
698 codebook,
699 ..
700 } => {
701 insert_binding(packed_values);
702 insert_binding(scales);
703 if let Some(binding) = zero_points {
704 insert_binding(binding);
705 }
706 if let Some(axis_component) = axis_indices {
707 insert_binding(&axis_component.component);
708 }
709 if let Some(axis_component) = permutation {
710 insert_binding(&axis_component.component);
711 }
712 if let Some(binding) = codebook {
713 insert_binding(binding);
714 }
715 }
716 PhysicalWeightLayout::BlockQuantized { blocks, .. } => insert_binding(blocks),
717 PhysicalWeightLayout::AxisReshapePermutation { values, .. } => stack.push(values),
718 PhysicalWeightLayout::Indexed {
719 indices, values, ..
720 } => {
721 insert_binding(&indices.component);
722 stack.push(values);
723 }
724 PhysicalWeightLayout::ExpertStack { experts, .. } => {
725 stack.extend(experts);
726 }
727 }
728 }
729 Ok(ids)
730}
731
732pub(crate) fn checked_elements(dimensions: &[u64]) -> Option<u64> {
733 dimensions
734 .iter()
735 .try_fold(1_u64, |elements, extent| elements.checked_mul(*extent))
736}