1use std::{collections::BTreeMap, marker::PhantomData};
4
5use eredu_checkpoint::{
6 recipe::{AtomicRecipeSet, DerivedWeightRecipe, RecipeCatalog, RecipeDtype, RecipeError},
7 store::{
8 CheckpointSource, ReadPolicy, SharedCheckpointSource, StoreError, TensorReadRequest,
9 TensorSelection,
10 },
11};
12use eredu_nn::{
13 ParameterId, ParameterMetadata, ParameterVisitor, ParameterVisitorMut, Parameterized,
14};
15
16use crate::{
17 ParameterBackend, ReplicatedTextMaterializationTask, ResidencyDeclarationError, WeightBinding,
18 WeightBindingPlan, WeightLoweringKind,
19};
20
21#[derive(Debug, Clone, Eq, PartialEq)]
23pub struct PlannedBinding {
24 pub target_name: String,
26 pub expected_shape: Vec<usize>,
28 pub expected_dtype: RecipeDtype,
30 pub recipe: DerivedWeightRecipe,
32}
33
34impl PlannedBinding {
35 pub fn direct(
37 target_name: impl Into<String>,
38 source_key: impl Into<String>,
39 expected_shape: impl Into<Vec<usize>>,
40 expected_dtype: RecipeDtype,
41 ) -> Self {
42 Self {
43 target_name: target_name.into(),
44 expected_shape: expected_shape.into(),
45 expected_dtype,
46 recipe: DerivedWeightRecipe::source(source_key, TensorSelection::Full),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Eq, PartialEq)]
53pub struct BindingPlan {
54 bindings: Vec<PlannedBinding>,
55 shared_source_keys: std::collections::BTreeSet<String>,
56 permitted_dtype_conversions: BTreeMap<String, Vec<RecipeDtype>>,
57}
58
59impl BindingPlan {
60 pub fn new(bindings: Vec<PlannedBinding>) -> Result<Self, BindingPlanError> {
62 Self::with_explicit_exceptions(bindings, std::collections::BTreeSet::new(), BTreeMap::new())
63 }
64
65 pub fn allowing_shared_sources(
67 bindings: Vec<PlannedBinding>,
68 shared_source_keys: std::collections::BTreeSet<String>,
69 ) -> Result<Self, BindingPlanError> {
70 Self::with_explicit_exceptions(bindings, shared_source_keys, BTreeMap::new())
71 }
72
73 pub fn with_explicit_exceptions(
78 mut bindings: Vec<PlannedBinding>,
79 shared_source_keys: std::collections::BTreeSet<String>,
80 permitted_dtype_conversions: BTreeMap<String, Vec<RecipeDtype>>,
81 ) -> Result<Self, BindingPlanError> {
82 bindings.sort_by(|left, right| left.target_name.cmp(&right.target_name));
83 let mut targets = std::collections::BTreeSet::new();
84 let mut claims = BTreeMap::<String, String>::new();
85 for binding in &bindings {
86 if binding.target_name.trim().is_empty() {
87 return Err(BindingPlanError::EmptyTarget);
88 }
89 if binding.expected_shape.contains(&0) {
90 return Err(BindingPlanError::InvalidTargetShape {
91 target: binding.target_name.clone(),
92 shape: binding.expected_shape.clone(),
93 });
94 }
95 binding
96 .expected_shape
97 .iter()
98 .try_fold(1usize, |count, dimension| count.checked_mul(*dimension))
99 .ok_or_else(|| BindingPlanError::TargetShapeOverflow {
100 target: binding.target_name.clone(),
101 })?;
102 if !targets.insert(binding.target_name.clone()) {
103 return Err(BindingPlanError::DuplicateTarget {
104 target: binding.target_name.clone(),
105 });
106 }
107 let source_keys = binding.recipe.source_keys();
108 if source_keys.is_empty() {
109 return Err(BindingPlanError::EmptyRecipeSources {
110 target: binding.target_name.clone(),
111 });
112 }
113 for source in source_keys {
114 if source.trim().is_empty() {
115 return Err(BindingPlanError::InvalidSourceKey {
116 target: binding.target_name.clone(),
117 });
118 }
119 if shared_source_keys.contains(source) {
120 continue;
121 }
122 if let Some(first) = claims.insert(source.into(), binding.target_name.clone()) {
123 return Err(BindingPlanError::DuplicateSourceClaim {
124 source_key: source.into(),
125 first,
126 second: binding.target_name.clone(),
127 });
128 }
129 }
130 }
131 Ok(Self {
132 bindings,
133 shared_source_keys,
134 permitted_dtype_conversions,
135 })
136 }
137
138 pub fn bindings(&self) -> &[PlannedBinding] {
140 &self.bindings
141 }
142
143 pub fn source_keys(&self) -> Vec<&str> {
145 let mut keys = std::collections::BTreeSet::new();
146 for binding in &self.bindings {
147 keys.extend(binding.recipe.source_keys());
148 }
149 keys.into_iter().collect()
150 }
151
152 pub fn shared_source_keys(&self) -> &std::collections::BTreeSet<String> {
154 &self.shared_source_keys
155 }
156
157 pub fn build_bindings(
161 &self,
162 source: &dyn CheckpointSource,
163 ) -> Result<Vec<WeightBinding>, BindingPlanError> {
164 let mut output = Vec::with_capacity(self.bindings.len());
165 for planned in &self.bindings {
166 let recipe = if source.is_authoritative_materialized_key(&planned.target_name) {
167 DerivedWeightRecipe::source(&planned.target_name, TensorSelection::Full)
168 } else {
169 planned.recipe.clone()
170 };
171 let metadata = recipe.infer(source)?;
172 if metadata.shape() != planned.expected_shape {
173 return Err(BindingPlanError::ShapeMismatch {
174 target: planned.target_name.clone(),
175 expected: planned.expected_shape.clone(),
176 actual: metadata.shape().to_vec(),
177 });
178 }
179 let explicitly_permitted = self
180 .permitted_dtype_conversions
181 .get(&planned.target_name)
182 .is_some_and(|dtypes| dtypes.contains(metadata.dtype()));
183 if planned.expected_dtype != *metadata.dtype() && !explicitly_permitted {
184 return Err(BindingPlanError::DtypeMismatch {
185 target: planned.target_name.clone(),
186 expected: planned.expected_dtype.clone(),
187 actual: metadata.dtype().clone(),
188 });
189 }
190 let binding = match recipe {
191 DerivedWeightRecipe::Source { key, selection } => {
192 WeightBinding::new(&planned.target_name, key, selection, metadata.byte_len())?
193 }
194 recipe => {
195 WeightBinding::from_recipe(&planned.target_name, recipe, metadata.byte_len())?
196 }
197 };
198 output.push(binding.with_logical_target(&planned.target_name)?);
199 }
200 Ok(output)
201 }
202}
203
204#[derive(Debug, thiserror::Error)]
206pub enum BindingPlanError {
207 #[error("binding target must not be empty")]
209 EmptyTarget,
210 #[error("binding target {target:?} has invalid shape {shape:?}")]
212 InvalidTargetShape {
213 target: String,
215 shape: Vec<usize>,
217 },
218 #[error("binding target {target:?} shape element count overflows")]
220 TargetShapeOverflow {
221 target: String,
223 },
224 #[error("binding plan contains duplicate target {target:?}")]
226 DuplicateTarget {
227 target: String,
229 },
230 #[error("binding target {target:?} has a recipe with no checkpoint sources")]
232 EmptyRecipeSources {
233 target: String,
235 },
236 #[error("binding target {target:?} has an empty checkpoint source key")]
238 InvalidSourceKey {
239 target: String,
241 },
242 #[error("checkpoint source {source_key:?} is claimed by both {first:?} and {second:?}")]
244 DuplicateSourceClaim {
245 source_key: String,
247 first: String,
249 second: String,
251 },
252 #[error("binding target {target:?} expects shape {expected:?}, recipe produces {actual:?}")]
254 ShapeMismatch {
255 target: String,
257 expected: Vec<usize>,
259 actual: Vec<usize>,
261 },
262 #[error("binding target {target:?} expects dtype {expected:?}, recipe produces {actual:?}")]
264 DtypeMismatch {
265 target: String,
267 expected: RecipeDtype,
269 actual: RecipeDtype,
271 },
272 #[error(transparent)]
274 Recipe(#[from] RecipeError),
275 #[error(transparent)]
277 Declaration(#[from] ResidencyDeclarationError),
278}
279
280#[derive(Debug, Clone, Eq, PartialEq)]
282pub struct ParameterBindingTarget {
283 pub shape: Vec<usize>,
285 pub dtype: RecipeDtype,
287 pub permitted_source_dtypes: Vec<RecipeDtype>,
292}
293
294#[derive(Debug, Clone)]
296pub struct ModuleBindingPlan {
297 plan: BindingPlan,
298 logical_targets: BTreeMap<String, String>,
299}
300
301impl ModuleBindingPlan {
302 pub const fn plan(&self) -> &BindingPlan {
304 &self.plan
305 }
306
307 pub fn build_bindings(
309 &self,
310 source: &dyn CheckpointSource,
311 ) -> Result<Vec<WeightBinding>, ModuleBindingPlanError> {
312 self.plan
313 .build_bindings(source)?
314 .into_iter()
315 .map(|binding| {
316 let target = self
317 .logical_targets
318 .get(binding.name())
319 .expect("module binding declaration retains every target");
320 binding
321 .with_logical_target(target)
322 .map_err(ModuleBindingPlanError::from)
323 })
324 .collect()
325 }
326}
327
328pub fn build_module_binding_plan<P, M, F, D>(
333 module: &M,
334 prefix: &str,
335 source: &dyn CheckpointSource,
336 mut recipes: BTreeMap<String, DerivedWeightRecipe>,
337 shared_source_keys: std::collections::BTreeSet<String>,
338 excluded: F,
339 describe: D,
340) -> Result<ModuleBindingPlan, ModuleBindingPlanError>
341where
342 P: 'static,
343 M: Parameterized<P>,
344 F: Fn(&str) -> bool,
345 D: Fn(&P) -> Option<ParameterBindingTarget>,
346{
347 struct Collector<'a, P> {
348 parameters: BTreeMap<String, &'a P>,
349 duplicate: Option<String>,
350 }
351
352 impl<'a, P: 'a> ParameterVisitor<'a, P> for Collector<'a, P> {
353 fn visit(&mut self, metadata: ParameterMetadata, parameter: &'a P) {
354 let name = metadata.id.as_str().to_owned();
355 if self.parameters.insert(name.clone(), parameter).is_some() {
356 self.duplicate = Some(name);
357 }
358 }
359 }
360
361 let mut collector = Collector {
362 parameters: BTreeMap::new(),
363 duplicate: None,
364 };
365 module.visit_parameters(&mut collector);
366 if let Some(parameter) = collector.duplicate {
367 return Err(ModuleBindingPlanError::DuplicateParameter { parameter });
368 }
369
370 recipes.retain(|name, _| !excluded(name));
371 let source_keys = source
372 .source_keys()
373 .into_iter()
374 .collect::<std::collections::BTreeSet<_>>();
375 let mut declarations = Vec::new();
376 let mut logical_targets = BTreeMap::new();
377 let mut permitted_dtype_conversions = BTreeMap::new();
378
379 for (local_name, parameter) in collector
380 .parameters
381 .into_iter()
382 .filter(|(name, _)| !excluded(name))
383 {
384 let destination = qualify_parameter(prefix, &local_name);
385 logical_targets.insert(local_name.clone(), destination.clone());
386 let description = describe(parameter).ok_or_else(|| {
387 ModuleBindingPlanError::InvalidParameterRepresentation {
388 parameter: destination.clone(),
389 }
390 })?;
391
392 let recipe = if source.is_authoritative_materialized_key(&destination) {
393 recipes.remove(&local_name);
394 DerivedWeightRecipe::source(destination.clone(), TensorSelection::Full)
395 } else if let Some(recipe) = recipes.remove(&local_name) {
396 recipe
397 } else if source_keys.contains(&destination) {
398 DerivedWeightRecipe::source(destination.clone(), TensorSelection::Full)
399 } else {
400 return Err(ModuleBindingPlanError::MissingParameter { destination });
401 };
402 if !description.permitted_source_dtypes.is_empty() {
403 permitted_dtype_conversions
404 .insert(local_name.clone(), description.permitted_source_dtypes);
405 }
406 declarations.push(PlannedBinding {
407 target_name: local_name,
408 expected_shape: description.shape,
409 expected_dtype: description.dtype,
410 recipe,
411 });
412 }
413
414 if !recipes.is_empty() {
415 return Err(ModuleBindingPlanError::UnknownRecipeParameters {
416 parameters: recipes.into_keys().collect(),
417 });
418 }
419 Ok(ModuleBindingPlan {
420 plan: BindingPlan::with_explicit_exceptions(
421 declarations,
422 shared_source_keys,
423 permitted_dtype_conversions,
424 )?,
425 logical_targets,
426 })
427}
428
429pub fn build_exact_replicated_text_bindings<P, M, D, L, E>(
439 module: &M,
440 source: &dyn CheckpointSource,
441 tasks: &[&ReplicatedTextMaterializationTask],
442 addressable_parameters: &std::collections::BTreeSet<String>,
443 local_layout: Option<&crate::LocalModelLayout>,
444 describe: D,
445 mut lower_mxfp4: L,
446) -> Result<Vec<WeightBinding>, ModuleBindingPlanError>
447where
448 P: 'static,
449 M: Parameterized<P>,
450 D: Fn(&P) -> Option<ParameterBindingTarget>,
451 L: FnMut(
452 &ReplicatedTextMaterializationTask,
453 DerivedWeightRecipe,
454 &dyn CheckpointSource,
455 ) -> Result<DerivedWeightRecipe, E>,
456 E: std::fmt::Display,
457{
458 struct Collector<'a, P> {
459 parameters: BTreeMap<String, (&'a P, ParameterBindingTarget)>,
460 duplicate: Option<String>,
461 }
462 impl<'a, P: 'a> ParameterVisitor<'a, P> for Collector<'a, P> {
463 fn visit(&mut self, metadata: ParameterMetadata, parameter: &'a P) {
464 let name = metadata.id.as_str().to_owned();
465 if self
468 .parameters
469 .insert(
470 name.clone(),
471 (
472 parameter,
473 ParameterBindingTarget {
474 shape: Vec::new(),
475 dtype: RecipeDtype::Other(String::new()),
476 permitted_source_dtypes: Vec::new(),
477 },
478 ),
479 )
480 .is_some()
481 {
482 self.duplicate = Some(name);
483 }
484 }
485 }
486
487 let mut collector = Collector {
488 parameters: BTreeMap::new(),
489 duplicate: None,
490 };
491 module.visit_parameters(&mut collector);
492 if let Some(parameter) = collector.duplicate {
493 return Err(ModuleBindingPlanError::DuplicateParameter { parameter });
494 }
495 for (name, (parameter, description)) in &mut collector.parameters {
496 *description = describe(parameter).ok_or_else(|| {
497 ModuleBindingPlanError::InvalidParameterRepresentation {
498 parameter: name.clone(),
499 }
500 })?;
501 }
502
503 let parameter_names = collector
504 .parameters
505 .keys()
506 .cloned()
507 .collect::<std::collections::BTreeSet<_>>();
508 let mut covered = std::collections::BTreeSet::new();
509 let mut declarations = Vec::new();
510 let mut conversions = BTreeMap::new();
511 let mut locally_materialized = std::collections::BTreeSet::new();
512 let shared_source_keys = tasks
513 .iter()
514 .flat_map(|task| task.shared_source_keys().iter().cloned())
515 .collect();
516
517 for task in tasks {
518 let candidates = std::iter::once(task.name())
519 .chain(task.aliases().iter().map(String::as_str))
520 .filter(|candidate| parameter_names.contains(*candidate))
521 .collect::<std::collections::BTreeSet<_>>();
522 if candidates.len() != 1 {
523 return Err(ModuleBindingPlanError::ExactTask {
524 details: format!(
525 "task {:?} resolves to {} module destinations through its canonical identity and aliases: {candidates:?}",
526 task.name(),
527 candidates.len()
528 ),
529 });
530 }
531 let target = (*candidates.first().expect("one candidate was validated")).to_owned();
532 if !covered.insert(target.clone()) {
533 return Err(ModuleBindingPlanError::ExactTask {
534 details: format!("module destination {target:?} is claimed more than once"),
535 });
536 }
537 validate_exact_task_provenance(task, source)?;
538 let is_local_transform = matches!(
539 task.lowering(),
540 WeightLoweringKind::Transform | WeightLoweringKind::DerivedTransform
541 );
542 if is_local_transform {
543 locally_materialized.insert(target.clone());
544 }
545 let recipe = exact_task_recipe(task, source, &mut lower_mxfp4)?;
546 push_exact_declaration(
547 &collector.parameters,
548 &mut declarations,
549 &mut conversions,
550 target,
551 recipe,
552 task.permitted_native_source_dtypes(),
553 )?;
554
555 for companion in task.output_companions() {
556 let target = companion.name().to_owned();
557 if is_local_transform {
558 locally_materialized.insert(target.clone());
559 }
560 if !parameter_names.contains(&target) {
561 return Err(ModuleBindingPlanError::ExactTask {
562 details: format!(
563 "task {:?} companion {:?} has no module destination",
564 task.name(),
565 companion.name()
566 ),
567 });
568 }
569 if !covered.insert(target.clone()) {
570 return Err(ModuleBindingPlanError::ExactTask {
571 details: format!("companion destination {target:?} is claimed more than once"),
572 });
573 }
574 let recipe = if let Some(exact) = companion.materialization_task() {
575 validate_exact_task_provenance(exact, source)?;
576 exact_task_recipe(exact, source, &mut lower_mxfp4)?
577 } else if let (Some(recipe), Some(expected)) =
578 (companion.derived_recipe(), companion.derived_output())
579 {
580 let actual = recipe.infer(source)?;
581 if &actual != expected {
582 return Err(ModuleBindingPlanError::ExactTask {
583 details: format!(
584 "companion {:?} differs from its admitted derived output",
585 companion.name()
586 ),
587 });
588 }
589 recipe.clone()
590 } else if let Some(physical) = companion.catalog_source() {
591 validate_physical_source(physical, source)?;
592 DerivedWeightRecipe::source(physical.catalog_key(), TensorSelection::Full)
593 } else if matches!(
594 task.lowering(),
595 WeightLoweringKind::Transform | WeightLoweringKind::DerivedTransform
596 ) || matches!(
597 task.source_encoding(),
598 eredu_checkpoint::SourceTensorEncoding::Gguf { .. }
599 ) {
600 if !source.is_authoritative_materialized_key(companion.name()) {
601 return Err(ModuleBindingPlanError::ExactTask {
602 details: format!(
603 "generated companion {:?} has no authoritative materialized output",
604 companion.name()
605 ),
606 });
607 }
608 DerivedWeightRecipe::source(companion.name(), TensorSelection::Full)
609 } else {
610 return Err(ModuleBindingPlanError::ExactTask {
611 details: format!(
612 "companion {:?} has neither an exact task nor causal source",
613 companion.name()
614 ),
615 });
616 };
617 push_exact_declaration(
618 &collector.parameters,
619 &mut declarations,
620 &mut conversions,
621 target,
622 recipe,
623 companion.materialization_task().map_or(
624 &[][..],
625 ReplicatedTextMaterializationTask::permitted_native_source_dtypes,
626 ),
627 )?;
628 }
629 }
630
631 let missing = parameter_names
632 .difference(&covered)
633 .filter(|name| !addressable_parameters.contains(*name))
634 .cloned()
635 .collect::<Vec<_>>();
636 if !missing.is_empty() {
637 return Err(ModuleBindingPlanError::ExactTask {
638 details: format!("module parameters have no exact materialization task: {missing:?}"),
639 });
640 }
641
642 if let Some(layout) = local_layout {
646 let bindings = declarations
647 .iter()
648 .filter(|declaration| !locally_materialized.contains(&declaration.target_name))
649 .map(|declaration| {
650 Ok(WeightBinding::from_recipe(
651 &declaration.target_name,
652 declaration.recipe.clone(),
653 declaration.recipe.infer(source)?.byte_len(),
654 )?
655 .with_logical_target(&declaration.target_name)?)
656 })
657 .collect::<Result<Vec<_>, ModuleBindingPlanError>>()?;
658 let placed = crate::place_weight_bindings(bindings, source, layout).map_err(|error| {
659 ModuleBindingPlanError::ExactTask {
660 details: error.to_string(),
661 }
662 })?;
663 for (declaration, binding) in declarations
666 .iter_mut()
667 .filter(|declaration| !locally_materialized.contains(&declaration.target_name))
668 .zip(placed)
669 {
670 declaration.recipe = binding.source_recipe();
671 }
672 }
673
674 BindingPlan::with_explicit_exceptions(declarations, shared_source_keys, conversions)?
675 .build_bindings(source)
676 .map_err(Into::into)
677}
678
679fn push_exact_declaration<P>(
680 parameters: &BTreeMap<String, (&P, ParameterBindingTarget)>,
681 declarations: &mut Vec<PlannedBinding>,
682 conversions: &mut BTreeMap<String, Vec<RecipeDtype>>,
683 target: String,
684 recipe: DerivedWeightRecipe,
685 explicitly_permitted_source_dtypes: &[RecipeDtype],
686) -> Result<(), ModuleBindingPlanError> {
687 let description = ¶meters
688 .get(&target)
689 .expect("validated module destination remains present")
690 .1;
691 let mut permitted = description.permitted_source_dtypes.clone();
692 for dtype in explicitly_permitted_source_dtypes {
693 if !permitted.contains(dtype) {
694 permitted.push(dtype.clone());
695 }
696 }
697 if !permitted.is_empty() {
698 conversions.insert(target.clone(), permitted);
699 }
700 declarations.push(PlannedBinding {
701 target_name: target,
702 expected_shape: description.shape.clone(),
703 expected_dtype: description.dtype.clone(),
704 recipe,
705 });
706 Ok(())
707}
708
709fn validate_exact_task_provenance(
710 task: &ReplicatedTextMaterializationTask,
711 source: &dyn CheckpointSource,
712) -> Result<(), ModuleBindingPlanError> {
713 if matches!(
714 task.lowering(),
715 WeightLoweringKind::Transform | WeightLoweringKind::DerivedTransform
716 ) {
717 return Ok(());
718 }
719 let declared = task
720 .sources()
721 .iter()
722 .map(String::as_str)
723 .collect::<std::collections::BTreeSet<_>>();
724 let physical = task
725 .physical_sources()
726 .iter()
727 .map(|source| source.catalog_key())
728 .collect::<std::collections::BTreeSet<_>>();
729 if declared != physical || physical.len() != task.physical_sources().len() {
730 return Err(ModuleBindingPlanError::ExactTask {
731 details: format!(
732 "task {:?} has inconsistent exact physical-source coverage",
733 task.name()
734 ),
735 });
736 }
737 for physical in task.physical_sources() {
738 validate_physical_source(physical, source)?;
739 }
740 Ok(())
741}
742
743fn validate_physical_source(
744 physical: &crate::ReplicatedTextPhysicalSource,
745 source: &dyn CheckpointSource,
746) -> Result<(), ModuleBindingPlanError> {
747 let provenance = source.source_provenance(physical.catalog_key())?;
748 let metadata = source.source_metadata(physical.catalog_key())?;
749 if provenance.catalog_key != physical.catalog_key()
750 || provenance.physical_tensor != physical.tensor()
751 || provenance.output != physical.output()
752 || provenance.backing_shard.as_deref() != Some(physical.shard())
753 || provenance.source_encoding != *physical.source_encoding()
754 || metadata.encoded_byte_len != physical.encoded_byte_len()
755 {
756 return Err(ModuleBindingPlanError::ExactTask {
757 details: format!(
758 "physical source {:?} differs from admitted provenance",
759 physical.catalog_key()
760 ),
761 });
762 }
763 Ok(())
764}
765
766fn exact_task_recipe<L, E>(
767 task: &ReplicatedTextMaterializationTask,
768 source: &dyn CheckpointSource,
769 lower_mxfp4: &mut L,
770) -> Result<DerivedWeightRecipe, ModuleBindingPlanError>
771where
772 L: FnMut(
773 &ReplicatedTextMaterializationTask,
774 DerivedWeightRecipe,
775 &dyn CheckpointSource,
776 ) -> Result<DerivedWeightRecipe, E>,
777 E: std::fmt::Display,
778{
779 match task.lowering() {
780 WeightLoweringKind::Transform | WeightLoweringKind::DerivedTransform => {
781 if !source.is_authoritative_materialized_key(task.name()) {
782 return Err(ModuleBindingPlanError::ExactTask {
783 details: format!(
784 "transformed task {:?} has no authoritative materialized output",
785 task.name()
786 ),
787 });
788 }
789 Ok(DerivedWeightRecipe::source(
790 task.name(),
791 TensorSelection::Full,
792 ))
793 }
794 WeightLoweringKind::Direct => {
795 let [key] = task.sources() else {
796 return Err(ModuleBindingPlanError::ExactTask {
797 details: format!("direct task {:?} must name exactly one source", task.name()),
798 });
799 };
800 Ok(DerivedWeightRecipe::source(key, TensorSelection::Full))
801 }
802 WeightLoweringKind::Derived => {
803 let recipe =
804 task.source_recipe()
805 .map_err(|error| ModuleBindingPlanError::ExactTask {
806 details: error.to_string(),
807 })?;
808 let admitted = recipe.infer(source)?;
809 if task
810 .derived_output()
811 .is_some_and(|expected| expected != &admitted)
812 {
813 return Err(ModuleBindingPlanError::ExactTask {
814 details: format!(
815 "derived task {:?} differs from its admitted output",
816 task.name()
817 ),
818 });
819 }
820 if task.executable() == eredu_checkpoint::LinearFormat::MxFp4
821 && admitted.dtype() == &RecipeDtype::F4
822 {
823 lower_mxfp4(task, recipe, source).map_err(|error| {
824 ModuleBindingPlanError::ExactTask {
825 details: format!(
826 "MXFP4 recipe lowering for {:?} failed: {error}",
827 task.name()
828 ),
829 }
830 })
831 } else {
832 Ok(recipe)
833 }
834 }
835 }
836}
837
838fn qualify_parameter(prefix: &str, name: &str) -> String {
839 if prefix.is_empty() {
840 name.to_owned()
841 } else {
842 format!("{prefix}.{name}")
843 }
844}
845
846#[derive(Debug, thiserror::Error)]
848pub enum ModuleBindingPlanError {
849 #[error("module traversal repeats parameter {parameter:?}")]
851 DuplicateParameter {
852 parameter: String,
854 },
855 #[error("parameter {parameter:?} has an invalid native representation")]
857 InvalidParameterRepresentation {
858 parameter: String,
860 },
861 #[error("checkpoint is missing parameter {destination:?}")]
863 MissingParameter {
864 destination: String,
866 },
867 #[error("recipes target unknown or excluded parameters: {parameters:?}")]
869 UnknownRecipeParameters {
870 parameters: Vec<String>,
872 },
873 #[error("exact materialization task is invalid: {details}")]
875 ExactTask {
876 details: String,
878 },
879 #[error(transparent)]
881 Store(#[from] StoreError),
882 #[error(transparent)]
884 Recipe(#[from] RecipeError),
885 #[error(transparent)]
887 Plan(#[from] BindingPlanError),
888 #[error(transparent)]
890 Declaration(#[from] ResidencyDeclarationError),
891}
892
893pub fn bindings_from_recipe_set<C: RecipeCatalog + ?Sized>(
896 catalog: &C,
897 set: AtomicRecipeSet,
898) -> Result<Vec<WeightBinding>, RecipeBindingError> {
899 let (outputs, aliases) = set.into_parts();
900 let mut bytes = BTreeMap::new();
901 for (name, recipe) in &outputs {
902 bytes.insert(name.clone(), recipe.infer(catalog)?.byte_len());
903 }
904 let mut bindings = outputs
905 .into_iter()
906 .map(|(name, recipe)| {
907 let expected = bytes[&name];
908 WeightBinding::from_recipe(name, recipe, expected)
909 })
910 .collect::<Result<Vec<_>, _>>()?;
911 for (alias, owner) in aliases {
912 bindings.push(WeightBinding::alias(alias, owner.clone(), bytes[&owner])?);
913 }
914 WeightBindingPlan::new(&bindings)?;
915 Ok(bindings)
916}
917
918#[derive(Debug, thiserror::Error)]
920pub enum RecipeBindingError {
921 #[error(transparent)]
923 Recipe(#[from] RecipeError),
924 #[error(transparent)]
926 Declaration(#[from] ResidencyDeclarationError),
927}
928
929pub struct MaterializedUnit<B: ParameterBackend> {
931 weights: BTreeMap<ParameterId, B::MaterializedWeight>,
932}
933
934pub struct SelectedBindingPlan<B: ParameterBackend> {
940 source: SharedCheckpointSource,
941 bindings: Vec<WeightBinding>,
942 backend: PhantomData<fn() -> B>,
943}
944
945impl<B: ParameterBackend> std::fmt::Debug for SelectedBindingPlan<B> {
946 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
947 formatter
948 .debug_struct("SelectedBindingPlan")
949 .field("bindings", &self.bindings)
950 .finish_non_exhaustive()
951 }
952}
953
954fn validated_binding_plan<'a, B: ParameterBackend>(
955 source: &dyn CheckpointSource,
956 bindings: &'a [WeightBinding],
957) -> Result<WeightBindingPlan<'a>, ParameterOrchestrationError<B::ParameterError>> {
958 let plan = WeightBindingPlan::new(bindings)?;
959 for binding in plan.owners() {
960 let inferred = binding.source_recipe().infer(source)?;
961 if inferred.byte_len() != binding.expected_bytes() {
962 return Err(ParameterOrchestrationError::ByteMismatch {
963 parameter: binding.name().to_owned(),
964 expected: binding.expected_bytes(),
965 actual: inferred.byte_len(),
966 });
967 }
968 }
969 for binding in plan.owners() {
970 B::preflight_recipe(&binding.source_recipe(), source)
971 .map_err(ParameterOrchestrationError::Backend)?;
972 }
973 Ok(plan)
974}
975
976pub fn preflight_bindings<B: ParameterBackend>(
978 source: &dyn CheckpointSource,
979 bindings: &[WeightBinding],
980) -> Result<(), ParameterOrchestrationError<B::ParameterError>> {
981 validated_binding_plan::<B>(source, bindings).map(|_| ())
982}
983
984pub fn select_bindings<B: ParameterBackend>(
991 source: SharedCheckpointSource,
992 bindings: Vec<WeightBinding>,
993) -> Result<SelectedBindingPlan<B>, ParameterOrchestrationError<B::ParameterError>> {
994 validated_binding_plan::<B>(source.as_ref(), &bindings)?;
995 Ok(SelectedBindingPlan {
996 source,
997 bindings,
998 backend: PhantomData,
999 })
1000}
1001
1002impl<B: ParameterBackend> MaterializedUnit<B> {
1003 pub fn try_from_weights(
1008 weights: impl IntoIterator<Item = (ParameterId, B::MaterializedWeight)>,
1009 ) -> Result<Self, ParameterOrchestrationError<B::ParameterError>> {
1010 let mut collected = BTreeMap::new();
1011 for (id, weight) in weights {
1012 if collected.insert(id.clone(), weight).is_some() {
1013 return Err(ParameterOrchestrationError::DuplicateBinding { parameter: id });
1014 }
1015 }
1016 Ok(Self { weights: collected })
1017 }
1018
1019 pub fn len(&self) -> usize {
1021 self.weights.len()
1022 }
1023
1024 pub fn is_empty(&self) -> bool {
1026 self.weights.is_empty()
1027 }
1028
1029 pub fn contains(&self, id: &ParameterId) -> bool {
1031 self.weights.contains_key(id)
1032 }
1033}
1034
1035pub fn materialize_bindings<B: ParameterBackend>(
1041 source: &dyn CheckpointSource,
1042 bindings: &[WeightBinding],
1043 context: &B::MaterializationContext,
1044) -> Result<MaterializedUnit<B>, ParameterOrchestrationError<B::ParameterError>> {
1045 let plan = validated_binding_plan::<B>(source, bindings)?;
1046 materialize_validated_plan::<B>(source, plan, context)
1047}
1048
1049pub fn materialize_selected_bindings<B: ParameterBackend>(
1052 selected: SelectedBindingPlan<B>,
1053 context: &B::MaterializationContext,
1054) -> Result<MaterializedUnit<B>, ParameterOrchestrationError<B::ParameterError>> {
1055 let SelectedBindingPlan {
1056 source,
1057 bindings,
1058 backend: _,
1059 } = selected;
1060 let plan = WeightBindingPlan::new(&bindings)
1061 .expect("selected binding declarations remain immutable after admission");
1062 materialize_validated_plan::<B>(source.as_ref(), plan, context)
1063}
1064
1065fn materialize_validated_plan<B: ParameterBackend>(
1066 source: &dyn CheckpointSource,
1067 plan: WeightBindingPlan<'_>,
1068 context: &B::MaterializationContext,
1069) -> Result<MaterializedUnit<B>, ParameterOrchestrationError<B::ParameterError>> {
1070 let mut weights = BTreeMap::new();
1071 for binding in plan.owners() {
1072 let materialization = match binding.recipe() {
1073 Some(recipe) => B::materialize_recipe(recipe, source, context),
1074 None => {
1075 let lease = source.acquire_lease(TensorReadRequest {
1076 key: binding.checkpoint_key().to_owned(),
1077 selection: binding.selection().clone(),
1078 policy: ReadPolicy::RequireBounded,
1079 })?;
1080 B::materialize(lease, context)
1081 }
1082 }
1083 .map_err(ParameterOrchestrationError::Backend)?;
1084 let weight = B::finish_materialization(materialization)
1085 .map_err(ParameterOrchestrationError::Backend)?;
1086 let id = ParameterId::new(binding.name()).map_err(|error| {
1087 ParameterOrchestrationError::InvalidParameterIdentity(error.to_string())
1088 })?;
1089 if weights.insert(id.clone(), weight).is_some() {
1090 return Err(ParameterOrchestrationError::DuplicateBinding { parameter: id });
1091 }
1092 }
1093 for (alias, owner) in plan.aliases() {
1094 let owner_id = ParameterId::new(owner.name()).map_err(|error| {
1095 ParameterOrchestrationError::InvalidParameterIdentity(error.to_string())
1096 })?;
1097 let weight = weights
1098 .get(&owner_id)
1099 .expect("validated owner was materialized before aliases");
1100 let weight =
1101 B::share_materialized_weight(weight).map_err(ParameterOrchestrationError::Backend)?;
1102 let alias_id = ParameterId::new(alias.name()).map_err(|error| {
1103 ParameterOrchestrationError::InvalidParameterIdentity(error.to_string())
1104 })?;
1105 weights.insert(alias_id, weight);
1106 }
1107 Ok(MaterializedUnit { weights })
1108}
1109
1110pub fn bind_materialized_unit<B, M>(
1116 module: &mut M,
1117 unit: MaterializedUnit<B>,
1118) -> Result<(), ParameterOrchestrationError<B::ParameterError>>
1119where
1120 B: ParameterBackend,
1121 M: Parameterized<B::Parameter>,
1122{
1123 bind_materialized_unit_excluding::<B, M, _>(module, unit, |_| false)
1124}
1125
1126pub fn bind_materialized_unit_excluding<B, M, F>(
1128 module: &mut M,
1129 mut unit: MaterializedUnit<B>,
1130 excluded: F,
1131) -> Result<(), ParameterOrchestrationError<B::ParameterError>>
1132where
1133 B: ParameterBackend,
1134 M: Parameterized<B::Parameter>,
1135 F: Fn(&ParameterId) -> bool,
1136{
1137 struct Validator<'a, B: ParameterBackend> {
1138 weights: &'a BTreeMap<ParameterId, B::MaterializedWeight>,
1139 visited: BTreeMap<ParameterId, ()>,
1140 error: Option<ParameterOrchestrationError<B::ParameterError>>,
1141 excluded: &'a dyn Fn(&ParameterId) -> bool,
1142 }
1143
1144 impl<'a, 'value, B: ParameterBackend> ParameterVisitor<'value, B::Parameter> for Validator<'a, B> {
1145 fn visit(&mut self, metadata: ParameterMetadata, parameter: &'value B::Parameter) {
1146 if self.error.is_some() {
1147 return;
1148 }
1149 if (self.excluded)(&metadata.id) {
1150 return;
1151 }
1152 if self.visited.insert(metadata.id.clone(), ()).is_some() {
1153 self.error = Some(ParameterOrchestrationError::DuplicateParameter {
1154 parameter: metadata.id,
1155 });
1156 return;
1157 }
1158 let Some(weight) = self.weights.get(&metadata.id) else {
1159 self.error = Some(ParameterOrchestrationError::MissingBinding {
1160 parameter: metadata.id,
1161 });
1162 return;
1163 };
1164 if let Err(error) = B::validate_bind(parameter, weight) {
1165 self.error = Some(ParameterOrchestrationError::Backend(error));
1166 }
1167 }
1168 }
1169
1170 let mut validator = Validator::<B> {
1171 weights: &unit.weights,
1172 visited: BTreeMap::new(),
1173 error: None,
1174 excluded: &excluded,
1175 };
1176 module.visit_parameters(&mut validator);
1177 if let Some(error) = validator.error {
1178 return Err(error);
1179 }
1180 let unexpected = unit
1181 .weights
1182 .keys()
1183 .filter(|id| !validator.visited.contains_key(*id))
1184 .cloned()
1185 .collect::<Vec<_>>();
1186 if !unexpected.is_empty() {
1187 return Err(ParameterOrchestrationError::UnexpectedBindings {
1188 parameters: unexpected,
1189 });
1190 }
1191
1192 struct MutableTopologyValidator<'a> {
1193 expected: &'a BTreeMap<ParameterId, ()>,
1194 visited: BTreeMap<ParameterId, ()>,
1195 unexpected: Vec<ParameterId>,
1196 duplicate: Option<ParameterId>,
1197 excluded: &'a dyn Fn(&ParameterId) -> bool,
1198 }
1199
1200 impl<'a, 'value, P: 'value> ParameterVisitorMut<'value, P> for MutableTopologyValidator<'a> {
1201 fn visit_mut(&mut self, metadata: ParameterMetadata, _: &'value mut P) {
1202 if self.duplicate.is_some() {
1203 return;
1204 }
1205 if (self.excluded)(&metadata.id) {
1206 return;
1207 }
1208 if self.visited.insert(metadata.id.clone(), ()).is_some() {
1209 self.duplicate = Some(metadata.id);
1210 } else if !self.expected.contains_key(&metadata.id) {
1211 self.unexpected.push(metadata.id);
1212 }
1213 }
1214 }
1215
1216 let mut mutable_topology = MutableTopologyValidator {
1217 expected: &validator.visited,
1218 visited: BTreeMap::new(),
1219 unexpected: Vec::new(),
1220 duplicate: None,
1221 excluded: &excluded,
1222 };
1223 module.visit_parameters_mut(&mut mutable_topology);
1224 if let Some(parameter) = mutable_topology.duplicate {
1225 return Err(ParameterOrchestrationError::DuplicateParameter { parameter });
1226 }
1227 let mut mismatch = mutable_topology.unexpected;
1228 mismatch.extend(
1229 validator
1230 .visited
1231 .keys()
1232 .filter(|id| !mutable_topology.visited.contains_key(*id))
1233 .cloned(),
1234 );
1235 if !mismatch.is_empty() {
1236 mismatch.sort();
1237 mismatch.dedup();
1238 return Err(ParameterOrchestrationError::ParameterTraversalMismatch {
1239 parameters: mismatch,
1240 });
1241 }
1242
1243 struct Binder<'a, B: ParameterBackend> {
1244 weights: &'a mut BTreeMap<ParameterId, B::MaterializedWeight>,
1245 excluded: &'a dyn Fn(&ParameterId) -> bool,
1246 }
1247
1248 impl<'a, 'value, B: ParameterBackend> ParameterVisitorMut<'value, B::Parameter> for Binder<'a, B> {
1249 fn visit_mut(&mut self, metadata: ParameterMetadata, parameter: &'value mut B::Parameter) {
1250 if (self.excluded)(&metadata.id) {
1251 return;
1252 }
1253 let weight = self
1254 .weights
1255 .remove(&metadata.id)
1256 .expect("prepublication mutable traversal validated every binding identity");
1257 B::bind(parameter, weight);
1258 }
1259 }
1260
1261 let mut binder = Binder::<B> {
1262 weights: &mut unit.weights,
1263 excluded: &excluded,
1264 };
1265 module.visit_parameters_mut(&mut binder);
1266 assert!(
1267 unit.weights.is_empty(),
1268 "prepublication mutable traversal validated complete binding consumption"
1269 );
1270 Ok(())
1271}
1272
1273#[derive(Debug, thiserror::Error)]
1275pub enum ParameterOrchestrationError<E>
1276where
1277 E: std::error::Error + Send + Sync + 'static,
1278{
1279 #[error(transparent)]
1281 Declaration(#[from] ResidencyDeclarationError),
1282 #[error(transparent)]
1284 Store(#[from] StoreError),
1285 #[error(transparent)]
1287 Recipe(#[from] RecipeError),
1288 #[error("invalid runtime parameter identity: {0}")]
1290 InvalidParameterIdentity(String),
1291 #[error("duplicate materialized binding for parameter {parameter}")]
1293 DuplicateBinding {
1294 parameter: ParameterId,
1296 },
1297 #[error("module parameter traversal repeats identity {parameter}")]
1299 DuplicateParameter {
1300 parameter: ParameterId,
1302 },
1303 #[error("immutable and mutable module parameter traversals disagree: {parameters:?}")]
1305 ParameterTraversalMismatch {
1306 parameters: Vec<ParameterId>,
1308 },
1309 #[error("parameter {parameter:?} declares {expected} bytes but its recipe produces {actual}")]
1311 ByteMismatch {
1312 parameter: String,
1314 expected: u64,
1316 actual: u64,
1318 },
1319 #[error("materialized unit has no value for parameter {parameter}")]
1321 MissingBinding {
1322 parameter: ParameterId,
1324 },
1325 #[error("materialized unit contains values for unknown parameters: {parameters:?}")]
1327 UnexpectedBindings {
1328 parameters: Vec<ParameterId>,
1330 },
1331 #[error("backend parameter operation failed: {0}")]
1333 Backend(E),
1334}