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