Skip to main content

ferrum_interfaces/vnext/operation/
weight_contract.rs

1use std::collections::BTreeSet;
2use std::num::NonZeroU32;
3
4use serde::{Deserialize, Deserializer, Serialize};
5
6use super::super::{
7    CanonicalRational, ContractVersion, QuantizationFormatId, VNextError, WeightFormatId, WeightId,
8    WeightLayoutId,
9};
10use super::ElementType;
11
12mod hadamard;
13pub(crate) use hadamard::same_shared_transform_sign_component;
14pub use hadamard::{
15    GroupedFeatureTranspose, HadamardApplication, HadamardSigns, HadamardTransformSpec,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum QuantizationPacking {
21    Linear,
22    Interleaved,
23    Tiled,
24}
25
26/// How values are partitioned by a quantized physical layout.
27///
28/// `WholeAxis` is shape-relative by design: all values on the group axis
29/// share one scale. This represents channelwise quantization without making a
30/// matrix dimension part of the otherwise stable quantization-format ABI.
31/// `Block2d` carries the exact rectangular source block shape; the physical
32/// layout separately binds its two dimensions to ordered logical axes.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(tag = "kind", rename_all = "snake_case")]
35pub enum QuantizationGrouping {
36    Fixed { size: u32 },
37    WholeAxis,
38    Block2d { block_shape: [NonZeroU32; 2] },
39}
40
41impl QuantizationGrouping {
42    pub const fn fixed(size: u32) -> Self {
43        Self::Fixed { size }
44    }
45
46    pub const fn fixed_size(self) -> Option<u32> {
47        match self {
48            Self::Fixed { size } => Some(size),
49            Self::WholeAxis | Self::Block2d { .. } => None,
50        }
51    }
52
53    pub const fn block_2d(block_shape: [NonZeroU32; 2]) -> Self {
54        Self::Block2d { block_shape }
55    }
56
57    pub const fn block_shape_2d(self) -> Option<[NonZeroU32; 2]> {
58        match self {
59            Self::Block2d { block_shape } => Some(block_shape),
60            Self::Fixed { .. } | Self::WholeAxis => None,
61        }
62    }
63
64    pub const fn resolved_size(self, axis_extent: u64) -> u64 {
65        match self {
66            Self::Fixed { size } => size as u64,
67            Self::WholeAxis => axis_extent,
68            // Multidimensional grouping has no meaningful single-axis size.
69            // Zero makes accidental use by a legacy single-axis validator
70            // fail closed instead of silently choosing one block dimension.
71            Self::Block2d { .. } => 0,
72        }
73    }
74
75    const fn is_valid(self) -> bool {
76        match self {
77            Self::Fixed { size } => size != 0 && size.is_power_of_two(),
78            Self::WholeAxis => true,
79            Self::Block2d { block_shape } => {
80                block_shape[0].get().is_power_of_two() && block_shape[1].get().is_power_of_two()
81            }
82        }
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct QuantizationSpec {
88    pub format_id: QuantizationFormatId,
89    pub bits_per_weight: u8,
90    pub grouping: QuantizationGrouping,
91    pub packing: QuantizationPacking,
92    pub scale_type: ElementType,
93    pub zero_point_type: Option<ElementType>,
94}
95
96impl QuantizationSpec {
97    pub fn validate(&self) -> Result<(), VNextError> {
98        if !(1..=8).contains(&self.bits_per_weight)
99            || !self.grouping.is_valid()
100            || !matches!(
101                self.scale_type,
102                ElementType::U8 | ElementType::F16 | ElementType::Bf16 | ElementType::F32
103            )
104            || self.zero_point_type.is_some_and(|element_type| {
105                !matches!(
106                    element_type,
107                    ElementType::U8 | ElementType::U32 | ElementType::I8 | ElementType::I32
108                )
109            })
110        {
111            return Err(VNextError::InvalidExecutionPlan {
112                reason: format!("invalid quantization format `{}`", self.format_id),
113            });
114        }
115        Ok(())
116    }
117}
118
119/// Self-contained fixed-size quantization blocks such as GGML/GGUF Q4_K and
120/// Q6_K. Per-block scales, minima, and packed values are part of the opaque
121/// block ABI identified by `format_id`; providers must not reinterpret these
122/// bytes as the separate-scale [`QuantizationSpec`] representation.
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct BlockQuantizationSpec {
125    pub format_id: QuantizationFormatId,
126    pub logical_values_per_block: u32,
127    pub bytes_per_block: u32,
128}
129
130impl BlockQuantizationSpec {
131    pub fn validate(&self) -> Result<(), VNextError> {
132        if self.logical_values_per_block == 0 || self.bytes_per_block == 0 {
133            return Err(VNextError::InvalidExecutionPlan {
134                reason: format!("invalid block quantization format `{}`", self.format_id),
135            });
136        }
137        Ok(())
138    }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "snake_case")]
143pub enum WeightEncoding {
144    Dense {
145        element_type: ElementType,
146    },
147    /// Dense floating-point values materialized after applying
148    /// `logical = physical * scale + bias` element-wise. This keeps checkpoint
149    /// representation semantics in the typed weight schema rather than in a
150    /// backend provider or model-name branch.
151    DenseAffine {
152        element_type: ElementType,
153        scale: CanonicalRational,
154        bias: CanonicalRational,
155    },
156    Quantized(QuantizationSpec),
157    BlockQuantized(BlockQuantizationSpec),
158}
159
160impl WeightEncoding {
161    pub const fn dense_element_type(&self) -> Option<ElementType> {
162        match self {
163            Self::Dense { element_type } | Self::DenseAffine { element_type, .. } => {
164                Some(*element_type)
165            }
166            Self::Quantized(_) | Self::BlockQuantized(_) => None,
167        }
168    }
169
170    pub(crate) fn physical_bytes(
171        &self,
172        dimensions: &[u64],
173        component_id: &WeightId,
174    ) -> Result<u64, VNextError> {
175        let elements =
176            checked_elements(dimensions).ok_or_else(|| VNextError::InvalidExecutionPlan {
177                reason: format!("physical component `{component_id}` size overflows u64"),
178            })?;
179        match self {
180            Self::Dense { element_type } | Self::DenseAffine { element_type, .. } => elements
181                .checked_mul(element_type.size_bytes())
182                .ok_or_else(|| VNextError::InvalidExecutionPlan {
183                    reason: format!("physical component `{component_id}` byte size overflows u64"),
184                }),
185            Self::Quantized(_) => Ok(elements),
186            Self::BlockQuantized(spec) => {
187                spec.validate()?;
188                elements
189                    .checked_mul(u64::from(spec.bytes_per_block))
190                    .ok_or_else(|| VNextError::InvalidExecutionPlan {
191                        reason: format!(
192                            "physical block component `{component_id}` byte size overflows u64"
193                        ),
194                    })
195            }
196        }
197    }
198}
199
200/// Structural role of a physical component in a weight format. The role is
201/// intentionally independent of any named quantization or model family.
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204pub enum WeightComponentRole {
205    Values,
206    PackedValues,
207    Scales,
208    ZeroPoints,
209    Indices,
210    Permutation,
211    Codebook,
212    Metadata,
213    /// Immutable F32 signs shared only by typed Hadamard wrappers.
214    TransformSigns,
215}
216
217/// Padding is always explicit and carries the exact semantic padded shape.
218/// `Exact` has no hidden storage extension. `ZeroFill` must increase at least
219/// one dimension and, for tiled or grouped storage, must be the unique minimal
220/// shape implied by that contract.
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222#[serde(rename_all = "snake_case")]
223pub enum PhysicalWeightPadding {
224    Exact,
225    ZeroFill { padded_dimensions: Vec<u64> },
226}
227
228/// Storage geometry for one physical component binding. Strides are measured
229/// in the component's schema storage unit: elements for dense encodings,
230/// bytes for separate-component packing, and blocks for block quantization.
231/// The component's declared dimensions describe its raw stored span, while
232/// this geometry maps the semantic component shape onto that span without
233/// inference or hidden padding.
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(rename_all = "snake_case")]
236pub enum PhysicalStorageLayout {
237    Contiguous {
238        padding: PhysicalWeightPadding,
239    },
240    Strided {
241        strides_in_elements: Vec<u64>,
242        padding: PhysicalWeightPadding,
243    },
244    Tiled {
245        tile_shape: Vec<u64>,
246        /// Physical tile-grid axis -> semantic component axis.
247        axis_order: Vec<u32>,
248        tile_strides_in_elements: Vec<u64>,
249        padding: PhysicalWeightPadding,
250    },
251}
252
253impl PhysicalStorageLayout {
254    pub fn exact_contiguous() -> Self {
255        Self::Contiguous {
256            padding: PhysicalWeightPadding::Exact,
257        }
258    }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
262pub struct PhysicalWeightComponentBinding {
263    pub component_id: WeightId,
264    pub storage: PhysicalStorageLayout,
265}
266
267impl PhysicalWeightComponentBinding {
268    pub fn exact_contiguous(component_id: WeightId) -> Self {
269        Self {
270            component_id,
271            storage: PhysicalStorageLayout::exact_contiguous(),
272        }
273    }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277pub struct AxisWeightComponent {
278    pub component: PhysicalWeightComponentBinding,
279    pub axis: u32,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283pub struct CompositeWeightPart {
284    pub layout: Box<PhysicalWeightLayout>,
285    pub logical_offsets: Vec<u64>,
286    pub extents: Vec<u64>,
287}
288
289/// Hard bounds keep directly constructed and deserialized recursive schemas
290/// cheap to validate. Ownership makes cycles unrepresentable; these limits
291/// additionally bound adversarial depth and fan-out.
292pub const MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH: usize = 16;
293pub const MAX_PHYSICAL_WEIGHT_LAYOUT_NODES: usize = 4096;
294
295/// Typed physical storage tree for one logical weight. Every leaf binds one
296/// physical value component exactly once. Immutable `TransformSigns` may be
297/// shared by Hadamard wrappers. Recursive composition allows indexing or
298/// expert stacking around dense, tiled, strided, or quantized values without
299/// architecture-specific cases.
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "snake_case")]
302pub enum PhysicalWeightLayout {
303    /// Exact, contiguous dense values. This common leaf deliberately remains
304    /// compact; use `Stored` for explicit stride, tile, or padding geometry.
305    Dense {
306        component_id: WeightId,
307    },
308    Stored {
309        component: PhysicalWeightComponentBinding,
310    },
311    Composite {
312        parts: Vec<CompositeWeightPart>,
313    },
314    Quantized {
315        packed_values: PhysicalWeightComponentBinding,
316        /// Semantic packed-storage shape before the binding's optional
317        /// stride/tile mapping. Its element product must equal the exact byte
318        /// count implied by logical elements and `bits_per_weight`.
319        packed_dimensions: Vec<u64>,
320        scales: PhysicalWeightComponentBinding,
321        zero_points: Option<PhysicalWeightComponentBinding>,
322        /// Optional packed storage shape for asymmetric zero points. When
323        /// absent, one dense zero-point scalar is stored per quantization
324        /// group. When present, it must contain exactly `bits_per_weight`
325        /// bits per group and the bound component dtype describes the packed
326        /// storage word (for example I32 words containing eight INT4 values).
327        zero_point_packed_dimensions: Option<Vec<u64>>,
328        /// Per-coordinate group assignment. Its semantic shape is the one
329        /// dimensional logical axis extent, not the multi-dimensional group
330        /// tensor shape.
331        axis_indices: Option<AxisWeightComponent>,
332        permutation: Option<AxisWeightComponent>,
333        codebook: Option<PhysicalWeightComponentBinding>,
334        group_axis: u32,
335        group_padding: PhysicalWeightPadding,
336    },
337    /// Separate-scale quantization over a rectangular two-dimensional block
338    /// grid. `block_axes` are ordered and canonical; the corresponding block
339    /// sizes live losslessly in [`QuantizationGrouping::Block2d`]. Boundary
340    /// blocks may be partial, so scale extents use ceiling division without
341    /// implying padded packed-value storage.
342    QuantizedBlockGrid {
343        packed_values: PhysicalWeightComponentBinding,
344        /// Semantic packed-storage shape. Its element product must equal the
345        /// exact byte count implied by the unpadded logical tensor.
346        packed_dimensions: Vec<u64>,
347        scales: PhysicalWeightComponentBinding,
348        block_axes: [u32; 2],
349    },
350    /// One opaque, self-contained quantization block represents a fixed
351    /// number of logical values along `block_axis`. The bound component shape
352    /// is the padded logical shape with that axis divided by the block width.
353    BlockQuantized {
354        blocks: PhysicalWeightComponentBinding,
355        block_axis: u32,
356        block_padding: PhysicalWeightPadding,
357    },
358    /// Stored values use a declared activation basis. Place this wrapper on
359    /// individual composite projections when only some consume that basis.
360    /// Transforming an already-transformed descendant is not supported.
361    Hadamard {
362        values: Box<PhysicalWeightLayout>,
363        transform: HadamardTransformSpec,
364    },
365    /// A contiguous logical subrange on one axis is stored by reshaping that
366    /// subrange and permuting the reshape axes. This captures checkpoint
367    /// layouts such as grouped-to-tiled head order without a model flag,
368    /// synthetic index tensor, or eager repack.
369    AxisReshapePermutation {
370        values: Box<PhysicalWeightLayout>,
371        axis: u32,
372        logical_offset: u64,
373        extent: u64,
374        reshape: Vec<u64>,
375        /// Stored axis position -> reshaped logical axis, matching an
376        /// n-dimensional transpose/permute order.
377        stored_axis_order: Vec<u32>,
378    },
379    Indexed {
380        indices: AxisWeightComponent,
381        values: Box<PhysicalWeightLayout>,
382        source_axis_extent: u64,
383    },
384    ExpertStack {
385        experts: Vec<PhysicalWeightLayout>,
386        expert_axis: u32,
387    },
388}
389
390impl PhysicalWeightLayout {
391    pub(crate) fn normalize(&mut self) {
392        match self {
393            Self::Composite { parts } => {
394                for part in parts.iter_mut() {
395                    part.layout.normalize();
396                }
397                // Offsets make composite placement semantic and order-free.
398                // Validation subsequently proves that no two placements
399                // overlap, so this order cannot reorder an ordered sequence.
400                parts.sort_by(|left, right| {
401                    left.logical_offsets
402                        .cmp(&right.logical_offsets)
403                        .then_with(|| left.extents.cmp(&right.extents))
404                });
405            }
406            Self::Hadamard { values, .. }
407            | Self::AxisReshapePermutation { values, .. }
408            | Self::Indexed { values, .. } => values.normalize(),
409            Self::ExpertStack { experts, .. } => {
410                // Expert vector position is the expert index and is therefore
411                // semantic. Normalize descendants without sorting the vector.
412                for expert in experts {
413                    expert.normalize();
414                }
415            }
416            Self::Dense { .. }
417            | Self::Stored { .. }
418            | Self::Quantized { .. }
419            | Self::QuantizedBlockGrid { .. }
420            | Self::BlockQuantized { .. } => {}
421        }
422    }
423}
424
425/// Provider-visible physical identity for one component of a resolved weight.
426/// Source file names are intentionally excluded: source provenance belongs to
427/// the prepared family fingerprint, while providers need shape, role, and ABI.
428#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
429pub struct ResolvedWeightComponentLayout {
430    component_id: WeightId,
431    role: WeightComponentRole,
432    physical_dimensions: Vec<u64>,
433    encoding: WeightEncoding,
434}
435
436impl ResolvedWeightComponentLayout {
437    pub(crate) fn from_parts(
438        component_id: WeightId,
439        role: WeightComponentRole,
440        physical_dimensions: Vec<u64>,
441        encoding: WeightEncoding,
442    ) -> Self {
443        Self {
444            component_id,
445            role,
446            physical_dimensions,
447            encoding,
448        }
449    }
450
451    pub fn component_id(&self) -> &WeightId {
452        &self.component_id
453    }
454
455    pub const fn role(&self) -> WeightComponentRole {
456        self.role
457    }
458
459    pub fn physical_dimensions(&self) -> &[u64] {
460        &self.physical_dimensions
461    }
462
463    pub fn encoding(&self) -> &WeightEncoding {
464        &self.encoding
465    }
466
467    pub fn physical_bytes(&self) -> Result<u64, VNextError> {
468        self.encoding
469            .physical_bytes(&self.physical_dimensions, &self.component_id)
470    }
471
472    pub fn physical_element_type(&self) -> ElementType {
473        self.encoding
474            .dense_element_type()
475            .unwrap_or(ElementType::U8)
476    }
477}
478
479/// Immutable physical weight contract carried by an execution-plan binding.
480/// This prevents the provider boundary from collapsing a quantized/composite
481/// layout into only resource ranges and a synthetic `u8` dtype.
482///
483/// `schema_format_id` identifies the enclosing source or materialized schema.
484/// It is a planning compatibility key, not the physical ABI of every component
485/// referenced by this binding. Providers must decode components from
486/// `physical_layout` and `components`.
487#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
488pub struct ResolvedWeightBinding {
489    weight_id: WeightId,
490    #[serde(rename = "format_id")]
491    schema_format_id: WeightFormatId,
492    layout_id: WeightLayoutId,
493    schema_version: ContractVersion,
494    physical_layout: PhysicalWeightLayout,
495    components: Vec<ResolvedWeightComponentLayout>,
496}
497
498/// Operation-owned capability for validating a resolved physical binding
499/// against the logical tensor contract supplied by the model layer.
500///
501/// The physical ABI stays independent of model schemas; the model owner
502/// provides the schema-aware implementation without introducing an
503/// operation-to-model dependency.
504pub(crate) trait ResolvedWeightLogicalValidation {
505    fn validate_logical_contract(
506        &self,
507        logical_dimensions: &[u64],
508        logical_element_type: ElementType,
509    ) -> Result<(), VNextError>;
510}
511
512#[derive(Deserialize)]
513#[serde(deny_unknown_fields)]
514struct ResolvedWeightBindingWire {
515    weight_id: WeightId,
516    format_id: WeightFormatId,
517    layout_id: WeightLayoutId,
518    schema_version: ContractVersion,
519    physical_layout: PhysicalWeightLayout,
520    components: Vec<ResolvedWeightComponentLayout>,
521}
522
523impl<'de> Deserialize<'de> for ResolvedWeightBinding {
524    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
525    where
526        D: Deserializer<'de>,
527    {
528        let wire = ResolvedWeightBindingWire::deserialize(deserializer)?;
529        Self::from_parts(
530            wire.weight_id,
531            wire.format_id,
532            wire.layout_id,
533            wire.schema_version,
534            wire.physical_layout,
535            wire.components,
536        )
537        .map_err(serde::de::Error::custom)
538    }
539}
540
541impl ResolvedWeightBinding {
542    pub(crate) fn from_parts(
543        weight_id: WeightId,
544        schema_format_id: WeightFormatId,
545        layout_id: WeightLayoutId,
546        schema_version: ContractVersion,
547        physical_layout: PhysicalWeightLayout,
548        components: Vec<ResolvedWeightComponentLayout>,
549    ) -> Result<Self, VNextError> {
550        let binding = Self {
551            weight_id,
552            schema_format_id,
553            layout_id,
554            schema_version,
555            physical_layout,
556            components,
557        };
558        binding.validate_structure()?;
559        Ok(binding)
560    }
561
562    pub(crate) fn validate_structure(&self) -> Result<(), VNextError> {
563        validate_physical_layout_budget(&self.physical_layout).map_err(|reason| {
564            VNextError::InvalidExecutionPlan {
565                reason: format!("resolved weight `{}` layout: {reason}", self.weight_id),
566            }
567        })?;
568        let referenced = physical_component_ids(&self.physical_layout).map_err(|reason| {
569            VNextError::InvalidExecutionPlan {
570                reason: format!("resolved weight `{}` layout: {reason}", self.weight_id),
571            }
572        })?;
573        let component_ids = self
574            .components
575            .iter()
576            .map(|component| component.component_id.clone())
577            .collect::<BTreeSet<_>>();
578        let canonical_components = self
579            .components
580            .windows(2)
581            .all(|pair| pair[0].component_id < pair[1].component_id);
582        if self.schema_version.major == 0
583            || self.components.is_empty()
584            || !canonical_components
585            || component_ids.len() != self.components.len()
586            || component_ids != referenced
587            || self.components.iter().any(|component| {
588                component.physical_dimensions.is_empty()
589                    || component
590                        .physical_dimensions
591                        .iter()
592                        .any(|extent| *extent == 0)
593                    || component.physical_bytes().is_err()
594            })
595        {
596            return Err(VNextError::InvalidExecutionPlan {
597                reason: format!(
598                    "resolved weight `{}` physical identity is invalid or non-canonical",
599                    self.weight_id
600                ),
601            });
602        }
603        Ok(())
604    }
605
606    pub fn weight_id(&self) -> &WeightId {
607        &self.weight_id
608    }
609
610    /// Enclosing schema/container identity used by provider selection.
611    ///
612    /// This must not be used to infer a bound component's physical encoding.
613    pub(crate) fn schema_format_id(&self) -> &WeightFormatId {
614        &self.schema_format_id
615    }
616
617    pub fn layout_id(&self) -> &WeightLayoutId {
618        &self.layout_id
619    }
620
621    pub const fn schema_version(&self) -> ContractVersion {
622        self.schema_version
623    }
624
625    pub fn physical_layout(&self) -> &PhysicalWeightLayout {
626        &self.physical_layout
627    }
628
629    pub fn components(&self) -> &[ResolvedWeightComponentLayout] {
630        &self.components
631    }
632
633    pub fn quantization_formats(&self) -> BTreeSet<QuantizationFormatId> {
634        self.components
635            .iter()
636            .filter_map(|component| match &component.encoding {
637                WeightEncoding::Quantized(spec) => Some(spec.format_id.clone()),
638                WeightEncoding::BlockQuantized(spec) => Some(spec.format_id.clone()),
639                WeightEncoding::Dense { .. } | WeightEncoding::DenseAffine { .. } => None,
640            })
641            .collect()
642    }
643}
644
645fn push_physical_layout_child<'a>(
646    stack: &mut Vec<(&'a PhysicalWeightLayout, usize)>,
647    child: &'a PhysicalWeightLayout,
648    child_depth: usize,
649    visited: usize,
650) -> Result<(), String> {
651    if visited
652        .checked_add(stack.len())
653        .is_none_or(|pending| pending >= MAX_PHYSICAL_WEIGHT_LAYOUT_NODES)
654    {
655        return Err(format!(
656            "physical layout node count exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_NODES}"
657        ));
658    }
659    stack.push((child, child_depth));
660    Ok(())
661}
662
663pub(crate) fn validate_physical_layout_budget(layout: &PhysicalWeightLayout) -> Result<(), String> {
664    let mut stack = vec![(layout, 1_usize)];
665    let mut visited = 0_usize;
666    while let Some((node, depth)) = stack.pop() {
667        if depth > MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH {
668            return Err(format!(
669                "physical layout depth exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_DEPTH}"
670            ));
671        }
672        let direct_bindings = match node {
673            PhysicalWeightLayout::Dense { .. } | PhysicalWeightLayout::Stored { .. } => 1,
674            PhysicalWeightLayout::Quantized {
675                zero_points,
676                axis_indices,
677                permutation,
678                codebook,
679                ..
680            } => {
681                2 + usize::from(zero_points.is_some())
682                    + usize::from(axis_indices.is_some())
683                    + usize::from(permutation.is_some())
684                    + usize::from(codebook.is_some())
685            }
686            PhysicalWeightLayout::QuantizedBlockGrid { .. } => 2,
687            PhysicalWeightLayout::BlockQuantized { .. } => 1,
688            PhysicalWeightLayout::Hadamard { transform, .. } => {
689                usize::from(matches!(transform.signs, HadamardSigns::Explicit(_)))
690            }
691            PhysicalWeightLayout::AxisReshapePermutation { .. } => 0,
692            PhysicalWeightLayout::Indexed { .. } => 1,
693            PhysicalWeightLayout::Composite { .. } | PhysicalWeightLayout::ExpertStack { .. } => 0,
694        };
695        visited = visited
696            .checked_add(1 + direct_bindings)
697            .ok_or_else(|| "physical layout node count overflows usize".to_owned())?;
698        if visited > MAX_PHYSICAL_WEIGHT_LAYOUT_NODES {
699            return Err(format!(
700                "physical layout node count exceeds {MAX_PHYSICAL_WEIGHT_LAYOUT_NODES}"
701            ));
702        }
703        let child_depth = depth
704            .checked_add(1)
705            .ok_or_else(|| "physical layout depth overflows usize".to_owned())?;
706        match node {
707            PhysicalWeightLayout::Composite { parts } => {
708                for part in parts {
709                    push_physical_layout_child(&mut stack, &part.layout, child_depth, visited)?;
710                }
711            }
712            PhysicalWeightLayout::Hadamard { values, .. }
713            | PhysicalWeightLayout::AxisReshapePermutation { values, .. }
714            | PhysicalWeightLayout::Indexed { values, .. } => {
715                push_physical_layout_child(&mut stack, values, child_depth, visited)?;
716            }
717            PhysicalWeightLayout::ExpertStack { experts, .. } => {
718                for expert in experts {
719                    push_physical_layout_child(&mut stack, expert, child_depth, visited)?;
720                }
721            }
722            PhysicalWeightLayout::Dense { .. }
723            | PhysicalWeightLayout::Stored { .. }
724            | PhysicalWeightLayout::Quantized { .. }
725            | PhysicalWeightLayout::QuantizedBlockGrid { .. }
726            | PhysicalWeightLayout::BlockQuantized { .. } => {}
727        }
728    }
729    Ok(())
730}
731
732pub(crate) fn physical_component_ids(
733    layout: &PhysicalWeightLayout,
734) -> Result<BTreeSet<WeightId>, String> {
735    validate_physical_layout_budget(layout)?;
736    let mut ids = BTreeSet::new();
737    let mut stack = vec![layout];
738    while let Some(node) = stack.pop() {
739        let mut insert_binding = |binding: &PhysicalWeightComponentBinding| {
740            ids.insert(binding.component_id.clone());
741        };
742        match node {
743            PhysicalWeightLayout::Dense { component_id } => {
744                ids.insert(component_id.clone());
745            }
746            PhysicalWeightLayout::Stored { component } => insert_binding(component),
747            PhysicalWeightLayout::Composite { parts } => {
748                stack.extend(parts.iter().map(|part| part.layout.as_ref()));
749            }
750            PhysicalWeightLayout::Quantized {
751                packed_values,
752                scales,
753                zero_points,
754                axis_indices,
755                permutation,
756                codebook,
757                ..
758            } => {
759                insert_binding(packed_values);
760                insert_binding(scales);
761                if let Some(binding) = zero_points {
762                    insert_binding(binding);
763                }
764                if let Some(axis_component) = axis_indices {
765                    insert_binding(&axis_component.component);
766                }
767                if let Some(axis_component) = permutation {
768                    insert_binding(&axis_component.component);
769                }
770                if let Some(binding) = codebook {
771                    insert_binding(binding);
772                }
773            }
774            PhysicalWeightLayout::QuantizedBlockGrid {
775                packed_values,
776                scales,
777                ..
778            } => {
779                insert_binding(packed_values);
780                insert_binding(scales);
781            }
782            PhysicalWeightLayout::BlockQuantized { blocks, .. } => insert_binding(blocks),
783            PhysicalWeightLayout::Hadamard { values, transform } => {
784                if let HadamardSigns::Explicit(signs) = &transform.signs {
785                    insert_binding(signs);
786                }
787                stack.push(values);
788            }
789            PhysicalWeightLayout::AxisReshapePermutation { values, .. } => stack.push(values),
790            PhysicalWeightLayout::Indexed {
791                indices, values, ..
792            } => {
793                insert_binding(&indices.component);
794                stack.push(values);
795            }
796            PhysicalWeightLayout::ExpertStack { experts, .. } => {
797                stack.extend(experts);
798            }
799        }
800    }
801    Ok(ids)
802}
803
804pub(crate) fn checked_elements(dimensions: &[u64]) -> Option<u64> {
805    dimensions
806        .iter()
807        .try_fold(1_u64, |elements, extent| elements.checked_mul(*extent))
808}