Skip to main content

ferrum_interfaces/vnext/execution/
compiler.rs

1use sha2::{Digest, Sha256};
2use std::collections::{BTreeMap, BTreeSet};
3
4use super::{
5    invalid_plan, AliasPolicy, BlockedTensorPadding, BufferUsage, CapabilityCatalog, CapabilityId,
6    CompletionRetentionSpec, DimensionConstraint, ElementType, ExecutablePlan, ExecutionPlan,
7    LayoutConstraint, NodeId, OperationPlanningHandle, PlanBuildRequest, PlanNodeResolution,
8    PreparedModelFamily, ProgramTensorSpec, ProgramValueId, ProviderId, ProviderResourcePlan,
9    ResolvedStorageComponent, ResolvedTensorLayout, ResolvedTensorSpec, ResolvedValueBinding,
10    ResolvedValueRole, ResolvedValueStorage, ResolvedWeightBinding, ResourceId, RuntimePolicy,
11    StrideConstraint, TensorContract, TrustedExecutionWeightPlan, VNextError, WeightId,
12    WeightMaterializerId, WeightMaterializerRegistry, WeightMaterializerSelection, WeightSchema,
13    IDENTITY_WEIGHT_MATERIALIZER_ID,
14};
15
16/// Explicit semantic inputs, per-node selection preferences, and optional
17/// completion diagnostics for compiling a model program. Product input
18/// capacities are required because they bound request-lifetime backing; the
19/// compiler never guesses a one-token capacity.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ProgramPlanCompileOptions {
22    tensor_specs: BTreeMap<ProgramValueId, ProgramTensorSpec>,
23    required_capabilities: BTreeMap<NodeId, BTreeSet<CapabilityId>>,
24    preferred_providers: BTreeMap<NodeId, ProviderId>,
25    completion_retention: CompletionRetentionSpec,
26    weight_materializer_selection: WeightMaterializerSelection,
27}
28
29impl ProgramPlanCompileOptions {
30    pub fn new(
31        input_tensor_specs: BTreeMap<ProgramValueId, ProgramTensorSpec>,
32    ) -> Result<Self, VNextError> {
33        for (value_id, tensor) in &input_tensor_specs {
34            tensor.validate(&format!("program input `{value_id}`"))?;
35        }
36        Ok(Self {
37            tensor_specs: input_tensor_specs,
38            required_capabilities: BTreeMap::new(),
39            preferred_providers: BTreeMap::new(),
40            completion_retention: CompletionRetentionSpec::default(),
41            weight_materializer_selection: WeightMaterializerSelection::exact(
42                WeightMaterializerId::new(IDENTITY_WEIGHT_MATERIALIZER_ID)?,
43            ),
44        })
45    }
46
47    /// Adds an explicit tensor for an intermediate/output whose contract is
48    /// intentionally not inferable (for example a non-contiguous layout).
49    pub fn insert_tensor_spec(
50        &mut self,
51        value_id: ProgramValueId,
52        tensor: ProgramTensorSpec,
53    ) -> Result<(), VNextError> {
54        tensor.validate(&format!("program value `{value_id}`"))?;
55        self.tensor_specs.insert(value_id, tensor);
56        Ok(())
57    }
58
59    pub fn require_capability(&mut self, node_id: NodeId, capability: CapabilityId) {
60        self.required_capabilities
61            .entry(node_id)
62            .or_default()
63            .insert(capability);
64    }
65
66    pub fn prefer_provider(&mut self, node_id: NodeId, provider_id: ProviderId) {
67        self.preferred_providers.insert(node_id, provider_id);
68    }
69
70    /// Retains one semantic activation until the terminal completion fence for
71    /// an explicit diagnostic readback. Normal product plans leave this empty.
72    pub fn retain_completion_value(&mut self, value_id: ProgramValueId) -> bool {
73        self.completion_retention.insert(value_id)
74    }
75
76    /// Configures a diagnostic plan whose every operation output remains
77    /// readable after the terminal completion fence.
78    pub fn retain_all_outputs_for_determinism(
79        &mut self,
80        family: &PreparedModelFamily,
81    ) -> Result<(), VNextError> {
82        self.completion_retention = CompletionRetentionSpec::for_determinism_outputs(family)?;
83        Ok(())
84    }
85
86    pub fn completion_retention(&self) -> &CompletionRetentionSpec {
87        &self.completion_retention
88    }
89
90    pub fn tensor_specs(&self) -> &BTreeMap<ProgramValueId, ProgramTensorSpec> {
91        &self.tensor_specs
92    }
93
94    pub fn require_weight_materializer(&mut self, materializer_id: WeightMaterializerId) {
95        self.weight_materializer_selection = WeightMaterializerSelection::exact(materializer_id);
96    }
97
98    pub fn require_weight_materializer_selection(
99        &mut self,
100        selection: WeightMaterializerSelection,
101    ) {
102        self.weight_materializer_selection = selection;
103    }
104
105    pub fn require_weight_materializer_with_numeric_quality_artifact(
106        &mut self,
107        materializer_id: WeightMaterializerId,
108        artifact_bytes: impl Into<Vec<u8>>,
109    ) -> Result<(), VNextError> {
110        self.weight_materializer_selection =
111            WeightMaterializerSelection::numeric_quality_artifact(materializer_id, artifact_bytes)?;
112        Ok(())
113    }
114
115    pub fn weight_materializer_id(&self) -> &WeightMaterializerId {
116        self.weight_materializer_selection.materializer_id()
117    }
118
119    pub fn weight_materializer_selection(&self) -> &WeightMaterializerSelection {
120        &self.weight_materializer_selection
121    }
122}
123
124/// Immutable executable plus the trusted node resolutions used to build it.
125/// Keeping the resolutions allows a product-level plan wrapper to validate the
126/// exact same physical decisions without reconstructing provider evidence.
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct ProgramPlanCompilation {
129    executable: ExecutablePlan,
130    node_resolutions: Vec<PlanNodeResolution>,
131    value_tensors: BTreeMap<ProgramValueId, ResolvedTensorSpec>,
132    completion_retention: CompletionRetentionSpec,
133}
134
135impl ProgramPlanCompilation {
136    pub fn executable(&self) -> &ExecutablePlan {
137        &self.executable
138    }
139
140    pub fn node_resolutions(&self) -> &[PlanNodeResolution] {
141        &self.node_resolutions
142    }
143
144    pub fn value_tensors(&self) -> &BTreeMap<ProgramValueId, ResolvedTensorSpec> {
145        &self.value_tensors
146    }
147
148    pub fn completion_retention(&self) -> &CompletionRetentionSpec {
149        &self.completion_retention
150    }
151
152    pub fn into_parts(
153        self,
154    ) -> (
155        ExecutablePlan,
156        Vec<PlanNodeResolution>,
157        BTreeMap<ProgramValueId, ResolvedTensorSpec>,
158        CompletionRetentionSpec,
159    ) {
160        (
161            self.executable,
162            self.node_resolutions,
163            self.value_tensors,
164            self.completion_retention,
165        )
166    }
167}
168
169/// Backend-neutral compiler from semantic model programs to immutable physical
170/// execution plans. A metadata-only provider pass discovers the initial value
171/// alignment, then aligned dtype arenas are rebuilt until the estimator result
172/// reaches a bounded monotonic fixed point. This avoids both an unproved
173/// alignment guess and one device allocation per weight component.
174pub struct ProgramPlanCompiler;
175
176impl ProgramPlanCompiler {
177    pub fn compile<P: RuntimePolicy>(
178        family: &PreparedModelFamily,
179        catalog: &CapabilityCatalog,
180        policy: &P,
181        planning: &OperationPlanningHandle<'_>,
182        options: &ProgramPlanCompileOptions,
183    ) -> Result<ProgramPlanCompilation, VNextError> {
184        let materializers = WeightMaterializerRegistry::identity_only()?;
185        Self::compile_with_weight_materializers(
186            family,
187            catalog,
188            policy,
189            planning,
190            &materializers,
191            options,
192        )
193    }
194
195    pub fn compile_with_weight_materializers<P: RuntimePolicy>(
196        family: &PreparedModelFamily,
197        catalog: &CapabilityCatalog,
198        policy: &P,
199        planning: &OperationPlanningHandle<'_>,
200        materializers: &WeightMaterializerRegistry,
201        options: &ProgramPlanCompileOptions,
202    ) -> Result<ProgramPlanCompilation, VNextError> {
203        let execution_weights =
204            materializers.select(family, catalog, options.weight_materializer_selection())?;
205        Self::compile_with_execution_weights(
206            family,
207            catalog,
208            policy,
209            planning,
210            options,
211            execution_weights,
212        )
213    }
214
215    fn compile_with_execution_weights<P: RuntimePolicy>(
216        family: &PreparedModelFamily,
217        catalog: &CapabilityCatalog,
218        policy: &P,
219        planning: &OperationPlanningHandle<'_>,
220        options: &ProgramPlanCompileOptions,
221        execution_weights: TrustedExecutionWeightPlan,
222    ) -> Result<ProgramPlanCompilation, VNextError> {
223        validate_compile_options(family, options)?;
224        let value_tensors = infer_value_tensors(family, catalog, options)?;
225        family
226            .numerical_profile()
227            .validate_inferred_boundaries(&value_tensors)?;
228        let family_fingerprint = family.fingerprint()?;
229        let execution_weight_fingerprint = execution_weights.plan().fingerprint()?;
230        let execution_weight_schema = execution_weights.plan().schema();
231
232        let probe_locations = dedicated_weight_locations(
233            family,
234            execution_weight_schema,
235            &execution_weight_fingerprint,
236        )?;
237        let probe_storages = build_value_storages(
238            family,
239            execution_weight_schema,
240            catalog,
241            &value_tensors,
242            &probe_locations,
243            &family_fingerprint,
244        )?;
245        let probe_resolutions = resolve_nodes(
246            family,
247            execution_weight_schema,
248            &family_fingerprint,
249            catalog,
250            policy,
251            planning,
252            options,
253            &value_tensors,
254            &probe_storages,
255        )?;
256        let mut value_alignment_bytes = maximum_value_alignment(&probe_resolutions)?;
257        if !value_alignment_bytes.is_power_of_two() {
258            return Err(invalid_plan(
259                "provider value-alignment evidence is not a power of two",
260            ));
261        }
262
263        let mut final_resolution = None;
264        for _ in 0..=u64::BITS {
265            let arena_locations = arena_weight_locations(
266                family,
267                execution_weight_schema,
268                &execution_weight_fingerprint,
269                value_alignment_bytes,
270            )?;
271            let final_storages = build_value_storages(
272                family,
273                execution_weight_schema,
274                catalog,
275                &value_tensors,
276                &arena_locations,
277                &family_fingerprint,
278            )?;
279            let node_resolutions = resolve_nodes(
280                family,
281                execution_weight_schema,
282                &family_fingerprint,
283                catalog,
284                policy,
285                planning,
286                options,
287                &value_tensors,
288                &final_storages,
289            )?;
290            let observed_alignment = maximum_value_alignment(&node_resolutions)?;
291            if observed_alignment <= value_alignment_bytes {
292                final_resolution = Some(node_resolutions);
293                break;
294            }
295            value_alignment_bytes = observed_alignment;
296        }
297        let node_resolutions = final_resolution.ok_or_else(|| {
298            invalid_plan("provider value alignment did not reach a bounded monotonic fixed point")
299        })?;
300        let plan = ExecutionPlan::build(
301            PlanBuildRequest::new(family, catalog, policy, node_resolutions.clone())?
302                .with_execution_weights(execution_weights)?
303                .with_completion_retention(options.completion_retention.clone())?,
304        )?;
305        let executable = ExecutablePlan::new(plan, catalog.clone())?;
306        Ok(ProgramPlanCompilation {
307            executable,
308            node_resolutions,
309            value_tensors,
310            completion_retention: options.completion_retention.clone(),
311        })
312    }
313}
314
315fn maximum_value_alignment(resolutions: &[PlanNodeResolution]) -> Result<u64, VNextError> {
316    let alignment = resolutions
317        .iter()
318        .flat_map(PlanNodeResolution::provider_resource_candidates)
319        .map(ProviderResourcePlan::value_alignment_bytes)
320        .max()
321        .ok_or_else(|| invalid_plan("program has no provider value-alignment evidence"))?;
322    if alignment == 0 || !alignment.is_power_of_two() {
323        return Err(invalid_plan(
324            "provider value-alignment evidence is not a power of two",
325        ));
326    }
327    Ok(alignment)
328}
329
330#[derive(Debug, Clone)]
331struct WeightComponentLocation {
332    resource_id: ResourceId,
333    offset_bytes: u64,
334    length_bytes: u64,
335    element_type: ElementType,
336}
337
338fn validate_compile_options(
339    family: &PreparedModelFamily,
340    options: &ProgramPlanCompileOptions,
341) -> Result<(), VNextError> {
342    let program = family.program();
343    let nodes = program
344        .blocks()
345        .iter()
346        .flat_map(|block| &block.nodes)
347        .collect::<Vec<_>>();
348    let node_ids = nodes
349        .iter()
350        .map(|node| node.id.clone())
351        .collect::<BTreeSet<_>>();
352    if options
353        .required_capabilities
354        .keys()
355        .chain(options.preferred_providers.keys())
356        .any(|node_id| !node_ids.contains(node_id))
357    {
358        return Err(invalid_plan(
359            "program compile options reference an unknown node",
360        ));
361    }
362
363    let mut known_values = program.inputs().iter().cloned().collect::<BTreeSet<_>>();
364    known_values.extend(program.states().iter().map(|state| state.value_id.clone()));
365    known_values.extend(
366        program
367            .weights()
368            .iter()
369            .map(|weight| weight.value_id.clone()),
370    );
371    known_values.extend(nodes.iter().flat_map(|node| node.outputs.iter().cloned()));
372    if options
373        .tensor_specs
374        .keys()
375        .any(|value_id| !known_values.contains(value_id))
376    {
377        return Err(invalid_plan(
378            "program compile options contain an unknown semantic value",
379        ));
380    }
381    let produced_values = nodes
382        .iter()
383        .flat_map(|node| node.outputs.iter().cloned())
384        .collect::<BTreeSet<_>>();
385    if options
386        .completion_retention
387        .values()
388        .iter()
389        .any(|value_id| !produced_values.contains(value_id))
390    {
391        return Err(invalid_plan(
392            "completion retention must reference a semantic node output",
393        ));
394    }
395    if program
396        .inputs()
397        .iter()
398        .any(|input| !options.tensor_specs.contains_key(input))
399    {
400        return Err(invalid_plan(
401            "every program input requires an explicit canonical tensor capacity",
402        ));
403    }
404    Ok(())
405}
406
407fn infer_value_tensors(
408    family: &PreparedModelFamily,
409    catalog: &CapabilityCatalog,
410    options: &ProgramPlanCompileOptions,
411) -> Result<BTreeMap<ProgramValueId, ResolvedTensorSpec>, VNextError> {
412    let program = family.program();
413    let mut tensors = options
414        .tensor_specs
415        .iter()
416        .map(|(value_id, tensor)| Ok((value_id.clone(), resolved_tensor(tensor)?)))
417        .collect::<Result<BTreeMap<_, _>, VNextError>>()?;
418
419    for weight in program.weights() {
420        insert_exact_tensor(
421            &mut tensors,
422            &weight.value_id,
423            resolved_tensor(&weight.tensor)?,
424            "weight",
425        )?;
426    }
427    for state in program.states() {
428        insert_exact_tensor(
429            &mut tensors,
430            &state.value_id,
431            resolved_tensor(&state.tensor)?,
432            "state",
433        )?;
434    }
435
436    for node in program.blocks().iter().flat_map(|block| &block.nodes) {
437        let operation = catalog.operation_for_node(&node.id, &node.operation_id)?;
438        if node.inputs.len() != operation.inputs.len()
439            || node.outputs.len() != operation.outputs.len()
440        {
441            return Err(invalid_plan(format!(
442                "node `{}` arity differs from operation `{}`",
443                node.id, operation.id
444            )));
445        }
446        // Attribute validation and tensor-shape validation are separate typed
447        // contracts. Equal strings do not create an implicit equation between
448        // an AttributeId and a DimensionConstraint::Symbol.
449        let mut symbols = TensorSymbols::default();
450
451        for (ordinal, (value_id, contract)) in node.inputs.iter().zip(&operation.inputs).enumerate()
452        {
453            let tensor = tensors.get(value_id).ok_or_else(|| {
454                invalid_plan(format!(
455                    "node `{}` input `{value_id}` has no concrete tensor",
456                    node.id
457                ))
458            })?;
459            unify_tensor(
460                contract,
461                tensor,
462                &mut symbols,
463                &node.id,
464                "input",
465                ordinal,
466                value_id,
467            )?;
468        }
469        for (ordinal, (value_id, contract)) in
470            node.outputs.iter().zip(&operation.outputs).enumerate()
471        {
472            if let Some(tensor) = tensors.get(value_id) {
473                unify_tensor(
474                    contract,
475                    tensor,
476                    &mut symbols,
477                    &node.id,
478                    "output",
479                    ordinal,
480                    value_id,
481                )?;
482            }
483        }
484        for (value_id, contract) in node.outputs.iter().zip(&operation.outputs) {
485            if !tensors.contains_key(value_id) {
486                let tensor = infer_tensor(contract, &mut symbols, &node.id)?;
487                tensors.insert(value_id.clone(), tensor);
488            }
489        }
490    }
491    if program
492        .outputs()
493        .iter()
494        .any(|output| !tensors.contains_key(output))
495    {
496        return Err(invalid_plan(
497            "program compilation did not resolve every semantic output",
498        ));
499    }
500    Ok(tensors)
501}
502
503fn insert_exact_tensor(
504    tensors: &mut BTreeMap<ProgramValueId, ResolvedTensorSpec>,
505    value_id: &ProgramValueId,
506    tensor: ResolvedTensorSpec,
507    kind: &str,
508) -> Result<(), VNextError> {
509    if tensors
510        .insert(value_id.clone(), tensor.clone())
511        .is_some_and(|existing| existing != tensor)
512    {
513        return Err(invalid_plan(format!(
514            "explicit {kind} tensor `{value_id}` differs from model semantics"
515        )));
516    }
517    Ok(())
518}
519
520fn resolved_tensor(tensor: &ProgramTensorSpec) -> Result<ResolvedTensorSpec, VNextError> {
521    ResolvedTensorSpec::new(
522        tensor.dimensions.clone(),
523        tensor.element_type,
524        tensor.layout.clone(),
525    )
526}
527
528#[derive(Debug, Clone, Default)]
529struct TensorSymbols {
530    dimensions: BTreeMap<String, u64>,
531    strides: BTreeMap<String, u64>,
532}
533
534fn unify_tensor(
535    contract: &TensorContract,
536    tensor: &ResolvedTensorSpec,
537    symbols: &mut TensorSymbols,
538    node_id: &NodeId,
539    role: &str,
540    ordinal: usize,
541    value_id: &ProgramValueId,
542) -> Result<(), VNextError> {
543    let context = format!("node `{node_id}` {role}[{ordinal}] `{value_id}`");
544    if !contract.element_types().contains(&tensor.element_type())
545        || contract.dimensions().len() != tensor.dimensions().len()
546    {
547        return Err(invalid_plan(format!(
548            "{context} rank or dtype differs from its operation contract: actual rank={} dtype={:?}, expected rank={} dtypes={:?}",
549            tensor.dimensions().len(),
550            tensor.element_type(),
551            contract.dimensions().len(),
552            contract.element_types()
553        )));
554    }
555    unify_dimensions(
556        contract.dimensions(),
557        tensor.dimensions(),
558        &mut symbols.dimensions,
559        &context,
560    )?;
561    if !layout_matches(contract.layouts(), tensor.layout(), &mut symbols.strides)? {
562        return Err(invalid_plan(format!(
563            "{context} layout {:?} differs from its operation contract {:?}",
564            tensor.layout(),
565            contract.layouts()
566        )));
567    }
568    Ok(())
569}
570
571fn unify_dimensions(
572    constraints: &[DimensionConstraint],
573    dimensions: &[u64],
574    symbols: &mut BTreeMap<String, u64>,
575    context: &str,
576) -> Result<(), VNextError> {
577    for (axis, (constraint, extent)) in constraints.iter().zip(dimensions).enumerate() {
578        let valid = match constraint {
579            DimensionConstraint::Exact(expected) => expected == extent,
580            DimensionConstraint::Range { minimum, maximum } => {
581                minimum <= extent && extent <= maximum
582            }
583            DimensionConstraint::Symbol(symbol) => bind_symbol(symbols, symbol, *extent),
584        };
585        if !valid {
586            return Err(invalid_plan(format!(
587                "{context} dimension[{axis}]={extent} violates `{constraint:?}`"
588            )));
589        }
590    }
591    Ok(())
592}
593
594fn bind_symbol(symbols: &mut BTreeMap<String, u64>, symbol: &str, value: u64) -> bool {
595    if value == 0 {
596        return false;
597    }
598    match symbols.get(symbol) {
599        Some(existing) => *existing == value,
600        None => {
601            symbols.insert(symbol.to_owned(), value);
602            true
603        }
604    }
605}
606
607fn infer_tensor(
608    contract: &TensorContract,
609    symbols: &mut TensorSymbols,
610    node_id: &NodeId,
611) -> Result<ResolvedTensorSpec, VNextError> {
612    let element_type = contract
613        .element_types()
614        .iter()
615        .copied()
616        .next()
617        .filter(|_| contract.element_types().len() == 1)
618        .ok_or_else(|| {
619            invalid_plan(format!(
620                "node `{node_id}` output dtype is ambiguous; provide an explicit tensor"
621            ))
622        })?;
623    let dimensions = contract
624        .dimensions()
625        .iter()
626        .map(|dimension| match dimension {
627            DimensionConstraint::Exact(value) => Ok(*value),
628            DimensionConstraint::Range { minimum, maximum } if minimum == maximum => Ok(*minimum),
629            DimensionConstraint::Range { .. } => Err(invalid_plan(format!(
630                "node `{node_id}` output range is ambiguous; provide an explicit tensor"
631            ))),
632            DimensionConstraint::Symbol(symbol) => symbols
633                .dimensions
634                .get(symbol)
635                .copied()
636                .ok_or_else(|| {
637                    invalid_plan(format!(
638                        "node `{node_id}` output symbol `{symbol}` is unresolved; provide an explicit tensor"
639                    ))
640                }),
641        })
642        .collect::<Result<Vec<_>, VNextError>>()?;
643    let layout = infer_layout(contract.layouts(), &dimensions, &symbols.strides, node_id)?;
644    ResolvedTensorSpec::new(dimensions, element_type, layout)
645}
646
647fn infer_layout(
648    layouts: &[LayoutConstraint],
649    dimensions: &[u64],
650    stride_symbols: &BTreeMap<String, u64>,
651    node_id: &NodeId,
652) -> Result<ResolvedTensorLayout, VNextError> {
653    if layouts.len() != 1 {
654        return Err(invalid_plan(format!(
655            "node `{node_id}` output layout is ambiguous; provide an explicit tensor"
656        )));
657    }
658    match &layouts[0] {
659        LayoutConstraint::Contiguous => Ok(ResolvedTensorLayout::Contiguous),
660        LayoutConstraint::Strided { strides } => {
661            let byte_strides = strides
662                .iter()
663                .map(|stride| match stride {
664                    StrideConstraint::ExactBytes(value) => Ok(*value),
665                    StrideConstraint::Symbol(symbol) => {
666                        stride_symbols.get(symbol).copied().ok_or_else(|| {
667                            invalid_plan(format!(
668                                "node `{node_id}` stride symbol `{symbol}` is unresolved"
669                            ))
670                        })
671                    }
672                })
673                .collect::<Result<Vec<_>, VNextError>>()?;
674            Ok(ResolvedTensorLayout::Strided { byte_strides })
675        }
676        LayoutConstraint::Blocked { block, axis_order } => {
677            let divisible = dimensions
678                .iter()
679                .zip(block)
680                .all(|(extent, block)| extent % block == 0);
681            let padding = if divisible {
682                BlockedTensorPadding::Exact
683            } else {
684                let physical_dimensions = axis_order
685                    .iter()
686                    .map(|axis| {
687                        let extent = dimensions[*axis as usize];
688                        let block = block[*axis as usize];
689                        extent
690                            .checked_add(block - 1)
691                            .map(|value| value / block * block)
692                            .ok_or_else(|| invalid_plan("blocked tensor padding overflows u64"))
693                    })
694                    .collect::<Result<Vec<_>, VNextError>>()?;
695                BlockedTensorPadding::ZeroFill {
696                    physical_dimensions,
697                }
698            };
699            Ok(ResolvedTensorLayout::Blocked {
700                block: block.clone(),
701                axis_order: axis_order.clone(),
702                padding,
703            })
704        }
705    }
706}
707
708fn layout_matches(
709    constraints: &[LayoutConstraint],
710    layout: &ResolvedTensorLayout,
711    symbols: &mut BTreeMap<String, u64>,
712) -> Result<bool, VNextError> {
713    for constraint in constraints {
714        let mut candidate_symbols = symbols.clone();
715        let matches = match (constraint, layout) {
716            (LayoutConstraint::Contiguous, ResolvedTensorLayout::Contiguous) => true,
717            (
718                LayoutConstraint::Strided { strides },
719                ResolvedTensorLayout::Strided { byte_strides },
720            ) if strides.len() == byte_strides.len() => {
721                strides
722                    .iter()
723                    .zip(byte_strides)
724                    .all(|(constraint, stride)| match constraint {
725                        StrideConstraint::ExactBytes(expected) => expected == stride,
726                        StrideConstraint::Symbol(symbol) => {
727                            bind_symbol(&mut candidate_symbols, symbol, *stride)
728                        }
729                    })
730            }
731            (
732                LayoutConstraint::Blocked { block, axis_order },
733                ResolvedTensorLayout::Blocked {
734                    block: actual_block,
735                    axis_order: actual_axis_order,
736                    ..
737                },
738            ) => block == actual_block && axis_order == actual_axis_order,
739            _ => false,
740        };
741        if matches {
742            *symbols = candidate_symbols;
743            return Ok(true);
744        }
745    }
746    Ok(false)
747}
748
749fn dedicated_weight_locations(
750    family: &PreparedModelFamily,
751    execution_weight_schema: &WeightSchema,
752    execution_weight_fingerprint: &str,
753) -> Result<BTreeMap<WeightId, WeightComponentLocation>, VNextError> {
754    referenced_weight_components(family, execution_weight_schema)?
755        .into_iter()
756        .map(|component| {
757            let length_bytes = component.physical_bytes()?;
758            Ok((
759                component.id.clone(),
760                WeightComponentLocation {
761                    resource_id: hashed_resource_id(
762                        "weight-probe",
763                        execution_weight_fingerprint,
764                        component.id.as_str(),
765                    )?,
766                    offset_bytes: 0,
767                    length_bytes,
768                    element_type: component.physical_element_type(),
769                },
770            ))
771        })
772        .collect()
773}
774
775fn arena_weight_locations(
776    family: &PreparedModelFamily,
777    execution_weight_schema: &WeightSchema,
778    execution_weight_fingerprint: &str,
779    provider_alignment_bytes: u64,
780) -> Result<BTreeMap<WeightId, WeightComponentLocation>, VNextError> {
781    let components = referenced_weight_components(family, execution_weight_schema)?;
782    let mut arena_ids = BTreeMap::<ElementType, ResourceId>::new();
783    let mut next_offsets = BTreeMap::<ElementType, u64>::new();
784    let mut locations = BTreeMap::new();
785    for component in components {
786        let element_type = component.physical_element_type();
787        let alignment = provider_alignment_bytes.max(element_type.size_bytes());
788        let next_offset = next_offsets.entry(element_type).or_insert(0);
789        let offset_bytes = checked_align_up(*next_offset, alignment)?;
790        let length_bytes = component.physical_bytes()?;
791        *next_offset = offset_bytes
792            .checked_add(length_bytes)
793            .ok_or_else(|| invalid_plan("weight arena byte range overflows u64"))?;
794        let resource_id = arena_ids
795            .entry(element_type)
796            .or_insert(hashed_resource_id(
797                "weight-arena",
798                execution_weight_fingerprint,
799                &serde_json::to_string(&element_type).map_err(|error| {
800                    VNextError::Serialization {
801                        context: "serialize weight arena element type",
802                        message: error.to_string(),
803                    }
804                })?,
805            )?)
806            .clone();
807        locations.insert(
808            component.id.clone(),
809            WeightComponentLocation {
810                resource_id,
811                offset_bytes,
812                length_bytes,
813                element_type,
814            },
815        );
816    }
817    Ok(locations)
818}
819
820fn referenced_weight_components<'schema>(
821    family: &PreparedModelFamily,
822    execution_weight_schema: &'schema WeightSchema,
823) -> Result<Vec<&'schema super::super::WeightComponentSpec>, VNextError> {
824    let referenced = family
825        .program()
826        .weights()
827        .iter()
828        .map(|weight| execution_weight_schema.physical_component_refs(&weight.weight_id))
829        .collect::<Result<Vec<_>, VNextError>>()?
830        .into_iter()
831        .flatten()
832        .map(|component| component.id.clone())
833        .collect::<BTreeSet<_>>();
834    Ok(execution_weight_schema
835        .components
836        .iter()
837        .filter(|component| referenced.contains(&component.id))
838        .collect())
839}
840
841fn checked_align_up(value: u64, alignment: u64) -> Result<u64, VNextError> {
842    if alignment == 0 || !alignment.is_power_of_two() {
843        return Err(invalid_plan("weight arena alignment is invalid"));
844    }
845    value
846        .checked_add(alignment - 1)
847        .map(|sum| sum & !(alignment - 1))
848        .ok_or_else(|| invalid_plan("weight arena alignment overflows u64"))
849}
850
851fn hashed_resource_id(
852    kind: &str,
853    family_fingerprint: &str,
854    semantic_identity: &str,
855) -> Result<ResourceId, VNextError> {
856    let digest =
857        Sha256::digest(format!("{kind}\0{family_fingerprint}\0{semantic_identity}").as_bytes());
858    ResourceId::new(format!("resource/{kind}/sha256/{digest:x}"))
859}
860
861fn build_value_storages(
862    family: &PreparedModelFamily,
863    execution_weight_schema: &WeightSchema,
864    catalog: &CapabilityCatalog,
865    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
866    weight_locations: &BTreeMap<WeightId, WeightComponentLocation>,
867    family_fingerprint: &str,
868) -> Result<BTreeMap<ProgramValueId, ResolvedValueStorage>, VNextError> {
869    let program = family.program();
870    let mut storages = BTreeMap::new();
871    for input in program.inputs() {
872        storages.insert(
873            input.clone(),
874            activation_storage(input, tensors, family_fingerprint)?,
875        );
876    }
877    for weight in program.weights() {
878        let components = execution_weight_schema
879            .physical_component_refs(&weight.weight_id)?
880            .into_iter()
881            .map(|component| {
882                let location = weight_locations.get(&component.id).ok_or_else(|| {
883                    invalid_plan(format!(
884                        "weight component `{}` has no physical location",
885                        component.id
886                    ))
887                })?;
888                ResolvedStorageComponent::new(
889                    Some(component.id.clone()),
890                    location.resource_id.clone(),
891                    location.offset_bytes,
892                    location.length_bytes,
893                    location.element_type,
894                )
895            })
896            .collect::<Result<Vec<_>, VNextError>>()?;
897        storages.insert(
898            weight.value_id.clone(),
899            ResolvedValueStorage::composite(components)?,
900        );
901    }
902    for state in program.states() {
903        let tensor = tensors
904            .get(&state.value_id)
905            .ok_or_else(|| invalid_plan(format!("state `{}` has no resolved tensor", state.id)))?;
906        let tensor_minimum_bytes = tensor.minimum_storage_bytes()?;
907        let state_minimum_bytes = checked_align_up(
908            state.capacity_demand.minimum_bytes(tensor_minimum_bytes)?,
909            tensor.element_type().size_bytes(),
910        )?;
911        storages.insert(
912            state.value_id.clone(),
913            ResolvedValueStorage::single(
914                hashed_resource_id("state", family_fingerprint, state.id.as_str())?,
915                0,
916                state_minimum_bytes,
917                tensor.element_type(),
918            )?,
919        );
920    }
921    for node in program.blocks().iter().flat_map(|block| &block.nodes) {
922        let operation = catalog.operation_for_node(&node.id, &node.operation_id)?;
923        for (value_id, contract) in node.outputs.iter().zip(&operation.outputs) {
924            let storage = match contract.alias() {
925                AliasPolicy::MustAlias { tensor_index } => {
926                    let input = node.inputs.get(*tensor_index as usize).ok_or_else(|| {
927                        invalid_plan(format!("node `{}` aliases an absent input", node.id))
928                    })?;
929                    storages.get(input).cloned().ok_or_else(|| {
930                        invalid_plan(format!(
931                            "node `{}` alias input `{input}` has no storage",
932                            node.id
933                        ))
934                    })?
935                }
936                AliasPolicy::NoAlias | AliasPolicy::MayAlias { .. } => {
937                    activation_storage(value_id, tensors, family_fingerprint)?
938                }
939            };
940            storages.insert(value_id.clone(), storage);
941        }
942    }
943    Ok(storages)
944}
945
946fn activation_storage(
947    value_id: &ProgramValueId,
948    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
949    family_fingerprint: &str,
950) -> Result<ResolvedValueStorage, VNextError> {
951    let tensor = tensors
952        .get(value_id)
953        .ok_or_else(|| invalid_plan(format!("activation `{value_id}` has no resolved tensor")))?;
954    ResolvedValueStorage::single(
955        hashed_resource_id("activation", family_fingerprint, value_id.as_str())?,
956        0,
957        tensor.minimum_storage_bytes()?,
958        tensor.element_type(),
959    )
960}
961
962#[allow(clippy::too_many_arguments)]
963fn resolve_nodes<P: RuntimePolicy>(
964    family: &PreparedModelFamily,
965    execution_weight_schema: &WeightSchema,
966    prepared_family_fingerprint: &str,
967    catalog: &CapabilityCatalog,
968    policy: &P,
969    planning: &OperationPlanningHandle<'_>,
970    options: &ProgramPlanCompileOptions,
971    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
972    storages: &BTreeMap<ProgramValueId, ResolvedValueStorage>,
973) -> Result<Vec<PlanNodeResolution>, VNextError> {
974    family
975        .program()
976        .blocks()
977        .iter()
978        .flat_map(|block| &block.nodes)
979        .map(|node| {
980            let operation = catalog.operation_for_node(&node.id, &node.operation_id)?;
981            let values = node
982                .inputs
983                .iter()
984                .zip(&operation.inputs)
985                .enumerate()
986                .map(|(ordinal, (value_id, contract))| {
987                    resolved_binding(
988                        family,
989                        execution_weight_schema,
990                        value_id,
991                        ResolvedValueRole::Input,
992                        ordinal as u32,
993                        contract,
994                        tensors,
995                        storages,
996                    )
997                })
998                .chain(node.outputs.iter().zip(&operation.outputs).enumerate().map(
999                    |(ordinal, (value_id, contract))| {
1000                        resolved_binding(
1001                            family,
1002                            execution_weight_schema,
1003                            value_id,
1004                            ResolvedValueRole::Output,
1005                            ordinal as u32,
1006                            contract,
1007                            tensors,
1008                            storages,
1009                        )
1010                    },
1011                ))
1012                .collect::<Result<Vec<_>, VNextError>>()?;
1013            PlanNodeResolution::resolve_with_family_fingerprint(
1014                family,
1015                execution_weight_schema,
1016                prepared_family_fingerprint,
1017                catalog,
1018                policy,
1019                planning,
1020                node.id.clone(),
1021                values,
1022                options
1023                    .required_capabilities
1024                    .get(&node.id)
1025                    .cloned()
1026                    .unwrap_or_default(),
1027                options.preferred_providers.get(&node.id).cloned(),
1028            )
1029        })
1030        .collect()
1031}
1032
1033fn resolved_binding(
1034    family: &PreparedModelFamily,
1035    execution_weight_schema: &WeightSchema,
1036    value_id: &ProgramValueId,
1037    role: ResolvedValueRole,
1038    ordinal: u32,
1039    contract: &TensorContract,
1040    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
1041    storages: &BTreeMap<ProgramValueId, ResolvedValueStorage>,
1042) -> Result<ResolvedValueBinding, VNextError> {
1043    let program_weight = family
1044        .program()
1045        .weights()
1046        .iter()
1047        .find(|weight| weight.value_id == *value_id);
1048    let usage = if program_weight.is_some() {
1049        BufferUsage::Weights
1050    } else if family
1051        .program()
1052        .states()
1053        .iter()
1054        .any(|state| state.value_id == *value_id)
1055    {
1056        BufferUsage::State
1057    } else {
1058        BufferUsage::Activations
1059    };
1060    ResolvedValueBinding::new(
1061        value_id.clone(),
1062        role,
1063        ordinal,
1064        tensors
1065            .get(value_id)
1066            .cloned()
1067            .ok_or_else(|| invalid_plan(format!("value `{value_id}` has no resolved tensor")))?,
1068        contract.access(),
1069        if role == ResolvedValueRole::Input {
1070            AliasPolicy::NoAlias
1071        } else {
1072            contract.alias().clone()
1073        },
1074        usage,
1075        program_weight
1076            .map(|weight| {
1077                ResolvedWeightBinding::from_schema(execution_weight_schema, &weight.weight_id)
1078            })
1079            .transpose()?,
1080        storages
1081            .get(value_id)
1082            .cloned()
1083            .ok_or_else(|| invalid_plan(format!("value `{value_id}` has no resolved storage")))?,
1084    )
1085}
1086
1087#[cfg(test)]
1088mod tests {
1089    use crate::vnext::TensorAccess;
1090
1091    use super::{
1092        infer_tensor, unify_tensor, AliasPolicy, BTreeSet, DimensionConstraint, ElementType,
1093        LayoutConstraint, NodeId, ProgramValueId, ResolvedTensorLayout, ResolvedTensorSpec,
1094        StrideConstraint, TensorContract, TensorSymbols,
1095    };
1096
1097    #[test]
1098    fn dimension_and_stride_symbols_are_independent_domains() {
1099        let contract = TensorContract::new(
1100            vec![DimensionConstraint::Symbol("shared".to_owned())],
1101            BTreeSet::from([ElementType::F32]),
1102            vec![LayoutConstraint::Strided {
1103                strides: vec![StrideConstraint::Symbol("shared".to_owned())],
1104            }],
1105            TensorAccess::Read,
1106            AliasPolicy::NoAlias,
1107        )
1108        .unwrap();
1109        let tensor = ResolvedTensorSpec::new(
1110            vec![4],
1111            ElementType::F32,
1112            ResolvedTensorLayout::Strided {
1113                byte_strides: vec![16],
1114            },
1115        )
1116        .unwrap();
1117        let mut symbols = TensorSymbols::default();
1118        let value_id = ProgramValueId::new("value.symbol-domains").unwrap();
1119        unify_tensor(
1120            &contract,
1121            &tensor,
1122            &mut symbols,
1123            &NodeId::new("node.symbol-domains").unwrap(),
1124            "input",
1125            0,
1126            &value_id,
1127        )
1128        .unwrap();
1129        assert_eq!(symbols.dimensions.get("shared"), Some(&4));
1130        assert_eq!(symbols.strides.get("shared"), Some(&16));
1131    }
1132
1133    #[test]
1134    fn ambiguous_output_range_or_layout_requires_an_explicit_tensor() {
1135        let range = TensorContract::new(
1136            vec![DimensionConstraint::Range {
1137                minimum: 1,
1138                maximum: 8,
1139            }],
1140            BTreeSet::from([ElementType::F32]),
1141            vec![LayoutConstraint::Contiguous],
1142            TensorAccess::Write,
1143            AliasPolicy::NoAlias,
1144        )
1145        .unwrap();
1146        let mut symbols = TensorSymbols::default();
1147        let node_id = NodeId::new("node.ambiguous").unwrap();
1148        assert!(infer_tensor(&range, &mut symbols, &node_id).is_err());
1149
1150        let layouts = TensorContract::new(
1151            vec![DimensionConstraint::Exact(4)],
1152            BTreeSet::from([ElementType::F32]),
1153            vec![
1154                LayoutConstraint::Contiguous,
1155                LayoutConstraint::Strided {
1156                    strides: vec![StrideConstraint::ExactBytes(4)],
1157                },
1158            ],
1159            TensorAccess::Write,
1160            AliasPolicy::NoAlias,
1161        )
1162        .unwrap();
1163        assert!(infer_tensor(&layouts, &mut symbols, &node_id).is_err());
1164    }
1165}