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        let family_fingerprint = family.fingerprint()?;
226        let execution_weight_fingerprint = execution_weights.plan().fingerprint()?;
227        let execution_weight_schema = execution_weights.plan().schema();
228
229        let probe_locations = dedicated_weight_locations(
230            family,
231            execution_weight_schema,
232            &execution_weight_fingerprint,
233        )?;
234        let probe_storages = build_value_storages(
235            family,
236            execution_weight_schema,
237            catalog,
238            &value_tensors,
239            &probe_locations,
240            &family_fingerprint,
241        )?;
242        let probe_resolutions = resolve_nodes(
243            family,
244            execution_weight_schema,
245            &family_fingerprint,
246            catalog,
247            policy,
248            planning,
249            options,
250            &value_tensors,
251            &probe_storages,
252        )?;
253        let mut value_alignment_bytes = maximum_value_alignment(&probe_resolutions)?;
254        if !value_alignment_bytes.is_power_of_two() {
255            return Err(invalid_plan(
256                "provider value-alignment evidence is not a power of two",
257            ));
258        }
259
260        let mut final_resolution = None;
261        for _ in 0..=u64::BITS {
262            let arena_locations = arena_weight_locations(
263                family,
264                execution_weight_schema,
265                &execution_weight_fingerprint,
266                value_alignment_bytes,
267            )?;
268            let final_storages = build_value_storages(
269                family,
270                execution_weight_schema,
271                catalog,
272                &value_tensors,
273                &arena_locations,
274                &family_fingerprint,
275            )?;
276            let node_resolutions = resolve_nodes(
277                family,
278                execution_weight_schema,
279                &family_fingerprint,
280                catalog,
281                policy,
282                planning,
283                options,
284                &value_tensors,
285                &final_storages,
286            )?;
287            let observed_alignment = maximum_value_alignment(&node_resolutions)?;
288            if observed_alignment <= value_alignment_bytes {
289                final_resolution = Some(node_resolutions);
290                break;
291            }
292            value_alignment_bytes = observed_alignment;
293        }
294        let node_resolutions = final_resolution.ok_or_else(|| {
295            invalid_plan("provider value alignment did not reach a bounded monotonic fixed point")
296        })?;
297        let plan = ExecutionPlan::build(
298            PlanBuildRequest::new(family, catalog, policy, node_resolutions.clone())?
299                .with_execution_weights(execution_weights)?
300                .with_completion_retention(options.completion_retention.clone())?,
301        )?;
302        let executable = ExecutablePlan::new(plan, catalog.clone())?;
303        Ok(ProgramPlanCompilation {
304            executable,
305            node_resolutions,
306            value_tensors,
307            completion_retention: options.completion_retention.clone(),
308        })
309    }
310}
311
312fn maximum_value_alignment(resolutions: &[PlanNodeResolution]) -> Result<u64, VNextError> {
313    let alignment = resolutions
314        .iter()
315        .flat_map(PlanNodeResolution::provider_resource_candidates)
316        .map(ProviderResourcePlan::value_alignment_bytes)
317        .max()
318        .ok_or_else(|| invalid_plan("program has no provider value-alignment evidence"))?;
319    if alignment == 0 || !alignment.is_power_of_two() {
320        return Err(invalid_plan(
321            "provider value-alignment evidence is not a power of two",
322        ));
323    }
324    Ok(alignment)
325}
326
327#[derive(Debug, Clone)]
328struct WeightComponentLocation {
329    resource_id: ResourceId,
330    offset_bytes: u64,
331    length_bytes: u64,
332    element_type: ElementType,
333}
334
335fn validate_compile_options(
336    family: &PreparedModelFamily,
337    options: &ProgramPlanCompileOptions,
338) -> Result<(), VNextError> {
339    let program = family.program();
340    let nodes = program
341        .blocks()
342        .iter()
343        .flat_map(|block| &block.nodes)
344        .collect::<Vec<_>>();
345    let node_ids = nodes
346        .iter()
347        .map(|node| node.id.clone())
348        .collect::<BTreeSet<_>>();
349    if options
350        .required_capabilities
351        .keys()
352        .chain(options.preferred_providers.keys())
353        .any(|node_id| !node_ids.contains(node_id))
354    {
355        return Err(invalid_plan(
356            "program compile options reference an unknown node",
357        ));
358    }
359
360    let mut known_values = program.inputs().iter().cloned().collect::<BTreeSet<_>>();
361    known_values.extend(program.states().iter().map(|state| state.value_id.clone()));
362    known_values.extend(
363        program
364            .weights()
365            .iter()
366            .map(|weight| weight.value_id.clone()),
367    );
368    known_values.extend(nodes.iter().flat_map(|node| node.outputs.iter().cloned()));
369    if options
370        .tensor_specs
371        .keys()
372        .any(|value_id| !known_values.contains(value_id))
373    {
374        return Err(invalid_plan(
375            "program compile options contain an unknown semantic value",
376        ));
377    }
378    let produced_values = nodes
379        .iter()
380        .flat_map(|node| node.outputs.iter().cloned())
381        .collect::<BTreeSet<_>>();
382    if options
383        .completion_retention
384        .values()
385        .iter()
386        .any(|value_id| !produced_values.contains(value_id))
387    {
388        return Err(invalid_plan(
389            "completion retention must reference a semantic node output",
390        ));
391    }
392    if program
393        .inputs()
394        .iter()
395        .any(|input| !options.tensor_specs.contains_key(input))
396    {
397        return Err(invalid_plan(
398            "every program input requires an explicit canonical tensor capacity",
399        ));
400    }
401    Ok(())
402}
403
404fn infer_value_tensors(
405    family: &PreparedModelFamily,
406    catalog: &CapabilityCatalog,
407    options: &ProgramPlanCompileOptions,
408) -> Result<BTreeMap<ProgramValueId, ResolvedTensorSpec>, VNextError> {
409    let program = family.program();
410    let mut tensors = options
411        .tensor_specs
412        .iter()
413        .map(|(value_id, tensor)| Ok((value_id.clone(), resolved_tensor(tensor)?)))
414        .collect::<Result<BTreeMap<_, _>, VNextError>>()?;
415
416    for weight in program.weights() {
417        insert_exact_tensor(
418            &mut tensors,
419            &weight.value_id,
420            resolved_tensor(&weight.tensor)?,
421            "weight",
422        )?;
423    }
424    for state in program.states() {
425        insert_exact_tensor(
426            &mut tensors,
427            &state.value_id,
428            resolved_tensor(&state.tensor)?,
429            "state",
430        )?;
431    }
432
433    for node in program.blocks().iter().flat_map(|block| &block.nodes) {
434        let operation = catalog.operation_for_node(&node.id, &node.operation_id)?;
435        if node.inputs.len() != operation.inputs.len()
436            || node.outputs.len() != operation.outputs.len()
437        {
438            return Err(invalid_plan(format!(
439                "node `{}` arity differs from operation `{}`",
440                node.id, operation.id
441            )));
442        }
443        // Attribute validation and tensor-shape validation are separate typed
444        // contracts. Equal strings do not create an implicit equation between
445        // an AttributeId and a DimensionConstraint::Symbol.
446        let mut symbols = TensorSymbols::default();
447
448        for (ordinal, (value_id, contract)) in node.inputs.iter().zip(&operation.inputs).enumerate()
449        {
450            let tensor = tensors.get(value_id).ok_or_else(|| {
451                invalid_plan(format!(
452                    "node `{}` input `{value_id}` has no concrete tensor",
453                    node.id
454                ))
455            })?;
456            unify_tensor(
457                contract,
458                tensor,
459                &mut symbols,
460                &node.id,
461                "input",
462                ordinal,
463                value_id,
464            )?;
465        }
466        for (ordinal, (value_id, contract)) in
467            node.outputs.iter().zip(&operation.outputs).enumerate()
468        {
469            if let Some(tensor) = tensors.get(value_id) {
470                unify_tensor(
471                    contract,
472                    tensor,
473                    &mut symbols,
474                    &node.id,
475                    "output",
476                    ordinal,
477                    value_id,
478                )?;
479            }
480        }
481        for (value_id, contract) in node.outputs.iter().zip(&operation.outputs) {
482            if !tensors.contains_key(value_id) {
483                let tensor = infer_tensor(contract, &mut symbols, &node.id)?;
484                tensors.insert(value_id.clone(), tensor);
485            }
486        }
487    }
488    if program
489        .outputs()
490        .iter()
491        .any(|output| !tensors.contains_key(output))
492    {
493        return Err(invalid_plan(
494            "program compilation did not resolve every semantic output",
495        ));
496    }
497    Ok(tensors)
498}
499
500fn insert_exact_tensor(
501    tensors: &mut BTreeMap<ProgramValueId, ResolvedTensorSpec>,
502    value_id: &ProgramValueId,
503    tensor: ResolvedTensorSpec,
504    kind: &str,
505) -> Result<(), VNextError> {
506    if tensors
507        .insert(value_id.clone(), tensor.clone())
508        .is_some_and(|existing| existing != tensor)
509    {
510        return Err(invalid_plan(format!(
511            "explicit {kind} tensor `{value_id}` differs from model semantics"
512        )));
513    }
514    Ok(())
515}
516
517fn resolved_tensor(tensor: &ProgramTensorSpec) -> Result<ResolvedTensorSpec, VNextError> {
518    ResolvedTensorSpec::new(
519        tensor.dimensions.clone(),
520        tensor.element_type,
521        tensor.layout.clone(),
522    )
523}
524
525#[derive(Debug, Clone, Default)]
526struct TensorSymbols {
527    dimensions: BTreeMap<String, u64>,
528    strides: BTreeMap<String, u64>,
529}
530
531fn unify_tensor(
532    contract: &TensorContract,
533    tensor: &ResolvedTensorSpec,
534    symbols: &mut TensorSymbols,
535    node_id: &NodeId,
536    role: &str,
537    ordinal: usize,
538    value_id: &ProgramValueId,
539) -> Result<(), VNextError> {
540    let context = format!("node `{node_id}` {role}[{ordinal}] `{value_id}`");
541    if !contract.element_types().contains(&tensor.element_type())
542        || contract.dimensions().len() != tensor.dimensions().len()
543    {
544        return Err(invalid_plan(format!(
545            "{context} rank or dtype differs from its operation contract: actual rank={} dtype={:?}, expected rank={} dtypes={:?}",
546            tensor.dimensions().len(),
547            tensor.element_type(),
548            contract.dimensions().len(),
549            contract.element_types()
550        )));
551    }
552    unify_dimensions(
553        contract.dimensions(),
554        tensor.dimensions(),
555        &mut symbols.dimensions,
556        &context,
557    )?;
558    if !layout_matches(contract.layouts(), tensor.layout(), &mut symbols.strides)? {
559        return Err(invalid_plan(format!(
560            "{context} layout {:?} differs from its operation contract {:?}",
561            tensor.layout(),
562            contract.layouts()
563        )));
564    }
565    Ok(())
566}
567
568fn unify_dimensions(
569    constraints: &[DimensionConstraint],
570    dimensions: &[u64],
571    symbols: &mut BTreeMap<String, u64>,
572    context: &str,
573) -> Result<(), VNextError> {
574    for (axis, (constraint, extent)) in constraints.iter().zip(dimensions).enumerate() {
575        let valid = match constraint {
576            DimensionConstraint::Exact(expected) => expected == extent,
577            DimensionConstraint::Range { minimum, maximum } => {
578                minimum <= extent && extent <= maximum
579            }
580            DimensionConstraint::Symbol(symbol) => bind_symbol(symbols, symbol, *extent),
581        };
582        if !valid {
583            return Err(invalid_plan(format!(
584                "{context} dimension[{axis}]={extent} violates `{constraint:?}`"
585            )));
586        }
587    }
588    Ok(())
589}
590
591fn bind_symbol(symbols: &mut BTreeMap<String, u64>, symbol: &str, value: u64) -> bool {
592    if value == 0 {
593        return false;
594    }
595    match symbols.get(symbol) {
596        Some(existing) => *existing == value,
597        None => {
598            symbols.insert(symbol.to_owned(), value);
599            true
600        }
601    }
602}
603
604fn infer_tensor(
605    contract: &TensorContract,
606    symbols: &mut TensorSymbols,
607    node_id: &NodeId,
608) -> Result<ResolvedTensorSpec, VNextError> {
609    let element_type = contract
610        .element_types()
611        .iter()
612        .copied()
613        .next()
614        .filter(|_| contract.element_types().len() == 1)
615        .ok_or_else(|| {
616            invalid_plan(format!(
617                "node `{node_id}` output dtype is ambiguous; provide an explicit tensor"
618            ))
619        })?;
620    let dimensions = contract
621        .dimensions()
622        .iter()
623        .map(|dimension| match dimension {
624            DimensionConstraint::Exact(value) => Ok(*value),
625            DimensionConstraint::Range { minimum, maximum } if minimum == maximum => Ok(*minimum),
626            DimensionConstraint::Range { .. } => Err(invalid_plan(format!(
627                "node `{node_id}` output range is ambiguous; provide an explicit tensor"
628            ))),
629            DimensionConstraint::Symbol(symbol) => symbols
630                .dimensions
631                .get(symbol)
632                .copied()
633                .ok_or_else(|| {
634                    invalid_plan(format!(
635                        "node `{node_id}` output symbol `{symbol}` is unresolved; provide an explicit tensor"
636                    ))
637                }),
638        })
639        .collect::<Result<Vec<_>, VNextError>>()?;
640    let layout = infer_layout(contract.layouts(), &dimensions, &symbols.strides, node_id)?;
641    ResolvedTensorSpec::new(dimensions, element_type, layout)
642}
643
644fn infer_layout(
645    layouts: &[LayoutConstraint],
646    dimensions: &[u64],
647    stride_symbols: &BTreeMap<String, u64>,
648    node_id: &NodeId,
649) -> Result<ResolvedTensorLayout, VNextError> {
650    if layouts.len() != 1 {
651        return Err(invalid_plan(format!(
652            "node `{node_id}` output layout is ambiguous; provide an explicit tensor"
653        )));
654    }
655    match &layouts[0] {
656        LayoutConstraint::Contiguous => Ok(ResolvedTensorLayout::Contiguous),
657        LayoutConstraint::Strided { strides } => {
658            let byte_strides = strides
659                .iter()
660                .map(|stride| match stride {
661                    StrideConstraint::ExactBytes(value) => Ok(*value),
662                    StrideConstraint::Symbol(symbol) => {
663                        stride_symbols.get(symbol).copied().ok_or_else(|| {
664                            invalid_plan(format!(
665                                "node `{node_id}` stride symbol `{symbol}` is unresolved"
666                            ))
667                        })
668                    }
669                })
670                .collect::<Result<Vec<_>, VNextError>>()?;
671            Ok(ResolvedTensorLayout::Strided { byte_strides })
672        }
673        LayoutConstraint::Blocked { block, axis_order } => {
674            let divisible = dimensions
675                .iter()
676                .zip(block)
677                .all(|(extent, block)| extent % block == 0);
678            let padding = if divisible {
679                BlockedTensorPadding::Exact
680            } else {
681                let physical_dimensions = axis_order
682                    .iter()
683                    .map(|axis| {
684                        let extent = dimensions[*axis as usize];
685                        let block = block[*axis as usize];
686                        extent
687                            .checked_add(block - 1)
688                            .map(|value| value / block * block)
689                            .ok_or_else(|| invalid_plan("blocked tensor padding overflows u64"))
690                    })
691                    .collect::<Result<Vec<_>, VNextError>>()?;
692                BlockedTensorPadding::ZeroFill {
693                    physical_dimensions,
694                }
695            };
696            Ok(ResolvedTensorLayout::Blocked {
697                block: block.clone(),
698                axis_order: axis_order.clone(),
699                padding,
700            })
701        }
702    }
703}
704
705fn layout_matches(
706    constraints: &[LayoutConstraint],
707    layout: &ResolvedTensorLayout,
708    symbols: &mut BTreeMap<String, u64>,
709) -> Result<bool, VNextError> {
710    for constraint in constraints {
711        let mut candidate_symbols = symbols.clone();
712        let matches = match (constraint, layout) {
713            (LayoutConstraint::Contiguous, ResolvedTensorLayout::Contiguous) => true,
714            (
715                LayoutConstraint::Strided { strides },
716                ResolvedTensorLayout::Strided { byte_strides },
717            ) if strides.len() == byte_strides.len() => {
718                strides
719                    .iter()
720                    .zip(byte_strides)
721                    .all(|(constraint, stride)| match constraint {
722                        StrideConstraint::ExactBytes(expected) => expected == stride,
723                        StrideConstraint::Symbol(symbol) => {
724                            bind_symbol(&mut candidate_symbols, symbol, *stride)
725                        }
726                    })
727            }
728            (
729                LayoutConstraint::Blocked { block, axis_order },
730                ResolvedTensorLayout::Blocked {
731                    block: actual_block,
732                    axis_order: actual_axis_order,
733                    ..
734                },
735            ) => block == actual_block && axis_order == actual_axis_order,
736            _ => false,
737        };
738        if matches {
739            *symbols = candidate_symbols;
740            return Ok(true);
741        }
742    }
743    Ok(false)
744}
745
746fn dedicated_weight_locations(
747    family: &PreparedModelFamily,
748    execution_weight_schema: &WeightSchema,
749    execution_weight_fingerprint: &str,
750) -> Result<BTreeMap<WeightId, WeightComponentLocation>, VNextError> {
751    referenced_weight_components(family, execution_weight_schema)?
752        .into_iter()
753        .map(|component| {
754            let length_bytes = component.physical_bytes()?;
755            Ok((
756                component.id.clone(),
757                WeightComponentLocation {
758                    resource_id: hashed_resource_id(
759                        "weight-probe",
760                        execution_weight_fingerprint,
761                        component.id.as_str(),
762                    )?,
763                    offset_bytes: 0,
764                    length_bytes,
765                    element_type: component.physical_element_type(),
766                },
767            ))
768        })
769        .collect()
770}
771
772fn arena_weight_locations(
773    family: &PreparedModelFamily,
774    execution_weight_schema: &WeightSchema,
775    execution_weight_fingerprint: &str,
776    provider_alignment_bytes: u64,
777) -> Result<BTreeMap<WeightId, WeightComponentLocation>, VNextError> {
778    let components = referenced_weight_components(family, execution_weight_schema)?;
779    let mut arena_ids = BTreeMap::<ElementType, ResourceId>::new();
780    let mut next_offsets = BTreeMap::<ElementType, u64>::new();
781    let mut locations = BTreeMap::new();
782    for component in components {
783        let element_type = component.physical_element_type();
784        let alignment = provider_alignment_bytes.max(element_type.size_bytes());
785        let next_offset = next_offsets.entry(element_type).or_insert(0);
786        let offset_bytes = checked_align_up(*next_offset, alignment)?;
787        let length_bytes = component.physical_bytes()?;
788        *next_offset = offset_bytes
789            .checked_add(length_bytes)
790            .ok_or_else(|| invalid_plan("weight arena byte range overflows u64"))?;
791        let resource_id = arena_ids
792            .entry(element_type)
793            .or_insert(hashed_resource_id(
794                "weight-arena",
795                execution_weight_fingerprint,
796                &serde_json::to_string(&element_type).map_err(|error| {
797                    VNextError::Serialization {
798                        context: "serialize weight arena element type",
799                        message: error.to_string(),
800                    }
801                })?,
802            )?)
803            .clone();
804        locations.insert(
805            component.id.clone(),
806            WeightComponentLocation {
807                resource_id,
808                offset_bytes,
809                length_bytes,
810                element_type,
811            },
812        );
813    }
814    Ok(locations)
815}
816
817fn referenced_weight_components<'schema>(
818    family: &PreparedModelFamily,
819    execution_weight_schema: &'schema WeightSchema,
820) -> Result<Vec<&'schema super::super::WeightComponentSpec>, VNextError> {
821    let referenced = family
822        .program()
823        .weights()
824        .iter()
825        .map(|weight| execution_weight_schema.physical_component_refs(&weight.weight_id))
826        .collect::<Result<Vec<_>, VNextError>>()?
827        .into_iter()
828        .flatten()
829        .map(|component| component.id.clone())
830        .collect::<BTreeSet<_>>();
831    Ok(execution_weight_schema
832        .components
833        .iter()
834        .filter(|component| referenced.contains(&component.id))
835        .collect())
836}
837
838fn checked_align_up(value: u64, alignment: u64) -> Result<u64, VNextError> {
839    if alignment == 0 || !alignment.is_power_of_two() {
840        return Err(invalid_plan("weight arena alignment is invalid"));
841    }
842    value
843        .checked_add(alignment - 1)
844        .map(|sum| sum & !(alignment - 1))
845        .ok_or_else(|| invalid_plan("weight arena alignment overflows u64"))
846}
847
848fn hashed_resource_id(
849    kind: &str,
850    family_fingerprint: &str,
851    semantic_identity: &str,
852) -> Result<ResourceId, VNextError> {
853    let digest =
854        Sha256::digest(format!("{kind}\0{family_fingerprint}\0{semantic_identity}").as_bytes());
855    ResourceId::new(format!("resource/{kind}/sha256/{digest:x}"))
856}
857
858fn build_value_storages(
859    family: &PreparedModelFamily,
860    execution_weight_schema: &WeightSchema,
861    catalog: &CapabilityCatalog,
862    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
863    weight_locations: &BTreeMap<WeightId, WeightComponentLocation>,
864    family_fingerprint: &str,
865) -> Result<BTreeMap<ProgramValueId, ResolvedValueStorage>, VNextError> {
866    let program = family.program();
867    let mut storages = BTreeMap::new();
868    for input in program.inputs() {
869        storages.insert(
870            input.clone(),
871            activation_storage(input, tensors, family_fingerprint)?,
872        );
873    }
874    for weight in program.weights() {
875        let components = execution_weight_schema
876            .physical_component_refs(&weight.weight_id)?
877            .into_iter()
878            .map(|component| {
879                let location = weight_locations.get(&component.id).ok_or_else(|| {
880                    invalid_plan(format!(
881                        "weight component `{}` has no physical location",
882                        component.id
883                    ))
884                })?;
885                ResolvedStorageComponent::new(
886                    Some(component.id.clone()),
887                    location.resource_id.clone(),
888                    location.offset_bytes,
889                    location.length_bytes,
890                    location.element_type,
891                )
892            })
893            .collect::<Result<Vec<_>, VNextError>>()?;
894        storages.insert(
895            weight.value_id.clone(),
896            ResolvedValueStorage::composite(components)?,
897        );
898    }
899    for state in program.states() {
900        let tensor = tensors
901            .get(&state.value_id)
902            .ok_or_else(|| invalid_plan(format!("state `{}` has no resolved tensor", state.id)))?;
903        let tensor_minimum_bytes = tensor.minimum_storage_bytes()?;
904        let state_minimum_bytes = checked_align_up(
905            state.capacity_demand.minimum_bytes(tensor_minimum_bytes)?,
906            tensor.element_type().size_bytes(),
907        )?;
908        storages.insert(
909            state.value_id.clone(),
910            ResolvedValueStorage::single(
911                hashed_resource_id("state", family_fingerprint, state.id.as_str())?,
912                0,
913                state_minimum_bytes,
914                tensor.element_type(),
915            )?,
916        );
917    }
918    for node in program.blocks().iter().flat_map(|block| &block.nodes) {
919        let operation = catalog.operation_for_node(&node.id, &node.operation_id)?;
920        for (value_id, contract) in node.outputs.iter().zip(&operation.outputs) {
921            let storage = match contract.alias() {
922                AliasPolicy::MustAlias { tensor_index } => {
923                    let input = node.inputs.get(*tensor_index as usize).ok_or_else(|| {
924                        invalid_plan(format!("node `{}` aliases an absent input", node.id))
925                    })?;
926                    storages.get(input).cloned().ok_or_else(|| {
927                        invalid_plan(format!(
928                            "node `{}` alias input `{input}` has no storage",
929                            node.id
930                        ))
931                    })?
932                }
933                AliasPolicy::NoAlias | AliasPolicy::MayAlias { .. } => {
934                    activation_storage(value_id, tensors, family_fingerprint)?
935                }
936            };
937            storages.insert(value_id.clone(), storage);
938        }
939    }
940    Ok(storages)
941}
942
943fn activation_storage(
944    value_id: &ProgramValueId,
945    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
946    family_fingerprint: &str,
947) -> Result<ResolvedValueStorage, VNextError> {
948    let tensor = tensors
949        .get(value_id)
950        .ok_or_else(|| invalid_plan(format!("activation `{value_id}` has no resolved tensor")))?;
951    ResolvedValueStorage::single(
952        hashed_resource_id("activation", family_fingerprint, value_id.as_str())?,
953        0,
954        tensor.minimum_storage_bytes()?,
955        tensor.element_type(),
956    )
957}
958
959#[allow(clippy::too_many_arguments)]
960fn resolve_nodes<P: RuntimePolicy>(
961    family: &PreparedModelFamily,
962    execution_weight_schema: &WeightSchema,
963    prepared_family_fingerprint: &str,
964    catalog: &CapabilityCatalog,
965    policy: &P,
966    planning: &OperationPlanningHandle<'_>,
967    options: &ProgramPlanCompileOptions,
968    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
969    storages: &BTreeMap<ProgramValueId, ResolvedValueStorage>,
970) -> Result<Vec<PlanNodeResolution>, VNextError> {
971    family
972        .program()
973        .blocks()
974        .iter()
975        .flat_map(|block| &block.nodes)
976        .map(|node| {
977            let operation = catalog.operation_for_node(&node.id, &node.operation_id)?;
978            let values = node
979                .inputs
980                .iter()
981                .zip(&operation.inputs)
982                .enumerate()
983                .map(|(ordinal, (value_id, contract))| {
984                    resolved_binding(
985                        family,
986                        execution_weight_schema,
987                        value_id,
988                        ResolvedValueRole::Input,
989                        ordinal as u32,
990                        contract,
991                        tensors,
992                        storages,
993                    )
994                })
995                .chain(node.outputs.iter().zip(&operation.outputs).enumerate().map(
996                    |(ordinal, (value_id, contract))| {
997                        resolved_binding(
998                            family,
999                            execution_weight_schema,
1000                            value_id,
1001                            ResolvedValueRole::Output,
1002                            ordinal as u32,
1003                            contract,
1004                            tensors,
1005                            storages,
1006                        )
1007                    },
1008                ))
1009                .collect::<Result<Vec<_>, VNextError>>()?;
1010            PlanNodeResolution::resolve_with_family_fingerprint(
1011                family,
1012                execution_weight_schema,
1013                prepared_family_fingerprint,
1014                catalog,
1015                policy,
1016                planning,
1017                node.id.clone(),
1018                values,
1019                options
1020                    .required_capabilities
1021                    .get(&node.id)
1022                    .cloned()
1023                    .unwrap_or_default(),
1024                options.preferred_providers.get(&node.id).cloned(),
1025            )
1026        })
1027        .collect()
1028}
1029
1030fn resolved_binding(
1031    family: &PreparedModelFamily,
1032    execution_weight_schema: &WeightSchema,
1033    value_id: &ProgramValueId,
1034    role: ResolvedValueRole,
1035    ordinal: u32,
1036    contract: &TensorContract,
1037    tensors: &BTreeMap<ProgramValueId, ResolvedTensorSpec>,
1038    storages: &BTreeMap<ProgramValueId, ResolvedValueStorage>,
1039) -> Result<ResolvedValueBinding, VNextError> {
1040    let program_weight = family
1041        .program()
1042        .weights()
1043        .iter()
1044        .find(|weight| weight.value_id == *value_id);
1045    let usage = if program_weight.is_some() {
1046        BufferUsage::Weights
1047    } else if family
1048        .program()
1049        .states()
1050        .iter()
1051        .any(|state| state.value_id == *value_id)
1052    {
1053        BufferUsage::State
1054    } else {
1055        BufferUsage::Activations
1056    };
1057    ResolvedValueBinding::new(
1058        value_id.clone(),
1059        role,
1060        ordinal,
1061        tensors
1062            .get(value_id)
1063            .cloned()
1064            .ok_or_else(|| invalid_plan(format!("value `{value_id}` has no resolved tensor")))?,
1065        contract.access(),
1066        if role == ResolvedValueRole::Input {
1067            AliasPolicy::NoAlias
1068        } else {
1069            contract.alias().clone()
1070        },
1071        usage,
1072        program_weight
1073            .map(|weight| {
1074                ResolvedWeightBinding::from_schema(execution_weight_schema, &weight.weight_id)
1075            })
1076            .transpose()?,
1077        storages
1078            .get(value_id)
1079            .cloned()
1080            .ok_or_else(|| invalid_plan(format!("value `{value_id}` has no resolved storage")))?,
1081    )
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086    use crate::vnext::TensorAccess;
1087
1088    use super::{
1089        infer_tensor, unify_tensor, AliasPolicy, BTreeSet, DimensionConstraint, ElementType,
1090        LayoutConstraint, NodeId, ProgramValueId, ResolvedTensorLayout, ResolvedTensorSpec,
1091        StrideConstraint, TensorContract, TensorSymbols,
1092    };
1093
1094    #[test]
1095    fn dimension_and_stride_symbols_are_independent_domains() {
1096        let contract = TensorContract::new(
1097            vec![DimensionConstraint::Symbol("shared".to_owned())],
1098            BTreeSet::from([ElementType::F32]),
1099            vec![LayoutConstraint::Strided {
1100                strides: vec![StrideConstraint::Symbol("shared".to_owned())],
1101            }],
1102            TensorAccess::Read,
1103            AliasPolicy::NoAlias,
1104        )
1105        .unwrap();
1106        let tensor = ResolvedTensorSpec::new(
1107            vec![4],
1108            ElementType::F32,
1109            ResolvedTensorLayout::Strided {
1110                byte_strides: vec![16],
1111            },
1112        )
1113        .unwrap();
1114        let mut symbols = TensorSymbols::default();
1115        let value_id = ProgramValueId::new("value.symbol-domains").unwrap();
1116        unify_tensor(
1117            &contract,
1118            &tensor,
1119            &mut symbols,
1120            &NodeId::new("node.symbol-domains").unwrap(),
1121            "input",
1122            0,
1123            &value_id,
1124        )
1125        .unwrap();
1126        assert_eq!(symbols.dimensions.get("shared"), Some(&4));
1127        assert_eq!(symbols.strides.get("shared"), Some(&16));
1128    }
1129
1130    #[test]
1131    fn ambiguous_output_range_or_layout_requires_an_explicit_tensor() {
1132        let range = TensorContract::new(
1133            vec![DimensionConstraint::Range {
1134                minimum: 1,
1135                maximum: 8,
1136            }],
1137            BTreeSet::from([ElementType::F32]),
1138            vec![LayoutConstraint::Contiguous],
1139            TensorAccess::Write,
1140            AliasPolicy::NoAlias,
1141        )
1142        .unwrap();
1143        let mut symbols = TensorSymbols::default();
1144        let node_id = NodeId::new("node.ambiguous").unwrap();
1145        assert!(infer_tensor(&range, &mut symbols, &node_id).is_err());
1146
1147        let layouts = TensorContract::new(
1148            vec![DimensionConstraint::Exact(4)],
1149            BTreeSet::from([ElementType::F32]),
1150            vec![
1151                LayoutConstraint::Contiguous,
1152                LayoutConstraint::Strided {
1153                    strides: vec![StrideConstraint::ExactBytes(4)],
1154                },
1155            ],
1156            TensorAccess::Write,
1157            AliasPolicy::NoAlias,
1158        )
1159        .unwrap();
1160        assert!(infer_tensor(&layouts, &mut symbols, &node_id).is_err());
1161    }
1162}