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