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#[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 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 pub fn retain_completion_value(&mut self, value_id: ProgramValueId) -> bool {
73 self.completion_retention.insert(value_id)
74 }
75
76 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#[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
169pub 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 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}