1use std::{collections::BTreeMap, marker::PhantomData};
4
5use eredu_checkpoint::recipe::{DerivedWeightRecipe, RecipeMetadata};
6use eredu_nn::{NeuralBackend, Tensor};
7
8use crate::{
9 LayerWeightResidency, LayeredArchitecture, LayerwisePolicy, LayerwiseRuntime, ParameterBackend,
10 ParameterGroupOwner, RealtimeWeightComponentRequirement, RealtimeWeightComponentRole,
11 RealtimeWeightLoweringRequirement, RuntimeState, SelectedRealtimeRealization,
12 SubmissionBackend, WeightBinding, WeightBindingPlan,
13};
14
15#[derive(Debug, Clone, Eq, PartialEq)]
17pub struct RealtimeArchitectureConstructionIdentity {
18 architecture: crate::RealtimeIdentity,
19 speech_schedule: crate::RealtimeIdentity,
20 state_layout: crate::RealtimeIdentity,
21}
22
23impl RealtimeArchitectureConstructionIdentity {
24 pub fn new(
26 architecture: crate::RealtimeIdentity,
27 speech_schedule: crate::RealtimeIdentity,
28 state_layout: crate::RealtimeIdentity,
29 ) -> Self {
30 Self {
31 architecture,
32 speech_schedule,
33 state_layout,
34 }
35 }
36}
37
38pub trait RealtimeArchitectureIdentity {
40 fn realtime_construction_identity(
42 &self,
43 ) -> Result<RealtimeArchitectureConstructionIdentity, String>;
44}
45
46pub struct RealizedRealtimeState<S> {
48 state: S,
49 realization: crate::SelectedRealtimeStateRealization,
50}
51
52impl<S> RealizedRealtimeState<S> {
53 pub fn new(state: S, realization: crate::SelectedRealtimeStateRealization) -> Self {
55 Self { state, realization }
56 }
57
58 pub fn into_parts(self) -> (S, crate::SelectedRealtimeStateRealization) {
60 (self.state, self.realization)
61 }
62}
63
64pub struct RealizedRealtimePolicy<P> {
66 policy: P,
67 residency: LayerWeightResidency,
68}
69
70impl<P> RealizedRealtimePolicy<P> {
71 pub fn new(policy: P, residency: LayerWeightResidency) -> Self {
73 Self { policy, residency }
74 }
75
76 pub fn into_parts(self) -> (P, LayerWeightResidency) {
78 (self.policy, self.residency)
79 }
80}
81
82#[derive(Debug, Clone, Eq, PartialEq)]
84pub struct RealtimeMaterializationComponent {
85 requirement: RealtimeWeightComponentRequirement,
86 source_provenance: Vec<eredu_checkpoint::store::TensorSourceProvenance>,
87}
88
89impl RealtimeMaterializationComponent {
90 pub fn new(
92 requirement: RealtimeWeightComponentRequirement,
93 source_provenance: impl IntoIterator<Item = eredu_checkpoint::store::TensorSourceProvenance>,
94 ) -> Result<Self, RealtimeModelContractError> {
95 let source_provenance = source_provenance.into_iter().collect::<Vec<_>>();
96 if requirement.source_occurrences().len() != source_provenance.len()
97 || requirement
98 .source_occurrences()
99 .iter()
100 .zip(&source_provenance)
101 .any(|(expected, actual)| expected.as_str() != actual.catalog_key)
102 {
103 return Err(RealtimeModelContractError::SourceProvenanceMismatch {
104 target: requirement.target().as_str().to_owned(),
105 });
106 }
107 Ok(Self {
108 requirement,
109 source_provenance,
110 })
111 }
112
113 pub const fn requirement(&self) -> &RealtimeWeightComponentRequirement {
115 &self.requirement
116 }
117
118 pub const fn recipe(&self) -> Option<&DerivedWeightRecipe> {
120 self.requirement.recipe()
121 }
122
123 pub const fn recipe_output(&self) -> Option<&RecipeMetadata> {
125 self.requirement.recipe_output()
126 }
127
128 pub fn source_provenance(&self) -> &[eredu_checkpoint::store::TensorSourceProvenance] {
130 &self.source_provenance
131 }
132}
133
134#[derive(Debug, Clone, Eq, PartialEq)]
136pub struct RealtimeMaterializationTask {
137 lowering: RealtimeWeightLoweringRequirement,
138 owner: ParameterGroupOwner,
139 components: Vec<RealtimeMaterializationComponent>,
140}
141
142impl RealtimeMaterializationTask {
143 pub fn new(
145 lowering: RealtimeWeightLoweringRequirement,
146 owner: ParameterGroupOwner,
147 components: impl IntoIterator<Item = RealtimeMaterializationComponent>,
148 ) -> Result<Self, RealtimeModelContractError> {
149 let components = components.into_iter().collect::<Vec<_>>();
150 let expected = lowering.components();
151 if components.len() != expected.len()
152 || components
153 .iter()
154 .zip(expected)
155 .any(|(actual, expected)| actual.requirement() != expected)
156 {
157 return Err(RealtimeModelContractError::ComponentCoverageMismatch {
158 target: lowering.target().as_str().to_owned(),
159 });
160 }
161 let primary = components
162 .iter()
163 .find(|component| {
164 component.requirement().role() == RealtimeWeightComponentRole::Primary
165 })
166 .expect("selected lowering validation guarantees one primary component");
167 if primary
168 .recipe_output()
169 .is_some_and(|output| output.shape != lowering.descriptor().physical_shape())
170 {
171 return Err(RealtimeModelContractError::RecipeGeometryMismatch {
172 target: lowering.target().as_str().to_owned(),
173 });
174 }
175 if let (Some(output), eredu_checkpoint::SourceTensorEncoding::Safetensors(stored)) =
176 (primary.recipe_output(), lowering.descriptor().source())
177 {
178 if output.dtype != eredu_checkpoint::recipe::RecipeDtype::from(stored.clone()) {
179 return Err(RealtimeModelContractError::RecipeEncodingMismatch {
180 target: lowering.target().as_str().to_owned(),
181 });
182 }
183 }
184 Ok(Self {
185 lowering,
186 owner,
187 components,
188 })
189 }
190
191 pub const fn lowering(&self) -> &RealtimeWeightLoweringRequirement {
193 &self.lowering
194 }
195
196 pub const fn owner(&self) -> &ParameterGroupOwner {
198 &self.owner
199 }
200
201 pub fn components(&self) -> &[RealtimeMaterializationComponent] {
203 &self.components
204 }
205}
206
207#[derive(Debug, Clone)]
209pub struct RealtimeTaskBindingPlan {
210 pinned: Vec<WeightBinding>,
211 units: BTreeMap<ParameterGroupOwner, Vec<WeightBinding>>,
212}
213
214impl RealtimeTaskBindingPlan {
215 pub fn into_parts(
217 self,
218 ) -> (
219 Vec<WeightBinding>,
220 BTreeMap<ParameterGroupOwner, Vec<WeightBinding>>,
221 ) {
222 (self.pinned, self.units)
223 }
224}
225
226pub fn preflight_realtime_materialization_tasks<B: ParameterBackend>(
231 tasks: &[RealtimeMaterializationTask],
232 source: &dyn eredu_checkpoint::store::CheckpointSource,
233) -> Result<(), RealtimeModelContractError> {
234 for task in tasks {
235 let transformed = matches!(
236 task.lowering().kind(),
237 crate::WeightLoweringKind::Transform | crate::WeightLoweringKind::DerivedTransform
238 );
239 let targets = task
240 .components()
241 .iter()
242 .map(|component| component.requirement().target().as_str())
243 .collect::<std::collections::BTreeSet<_>>();
244 for component in task.components() {
245 for admitted in component.source_provenance() {
246 let actual = source
247 .source_provenance(&admitted.catalog_key)
248 .map_err(|error| RealtimeModelContractError::BindingPlan {
249 detail: error.to_string(),
250 })?;
251 if &actual != admitted {
252 return Err(RealtimeModelContractError::BindingPlan {
253 detail: format!(
254 "realtime component {:?} differs from admitted source provenance",
255 component.requirement().target().as_str()
256 ),
257 });
258 }
259 }
260 if let Some(owner) = component.requirement().recipe_owner() {
261 if owner != component.requirement().target() && !targets.contains(owner.as_str()) {
262 return Err(RealtimeModelContractError::BindingPlan {
263 detail: format!("realtime alias owner {:?} is absent", owner.as_str()),
264 });
265 }
266 }
267 if let Some(recipe) = component.recipe() {
268 let actual = recipe.infer(source).map_err(|error| {
269 RealtimeModelContractError::BindingPlan {
270 detail: error.to_string(),
271 }
272 })?;
273 if component.recipe_output() != Some(&actual) {
274 return Err(RealtimeModelContractError::BindingPlan {
275 detail: format!(
276 "realtime component {:?} recipe output drifted",
277 component.requirement().target().as_str()
278 ),
279 });
280 }
281 B::preflight_recipe(recipe, source).map_err(|error| {
282 RealtimeModelContractError::BindingPlan {
283 detail: error.to_string(),
284 }
285 })?;
286 } else if component.requirement().recipe_owner().is_none() && !transformed {
287 return Err(RealtimeModelContractError::BindingPlan {
288 detail: format!(
289 "realtime component {:?} has neither recipe nor alias owner",
290 component.requirement().target().as_str()
291 ),
292 });
293 }
294 }
295 }
296 Ok(())
297}
298
299pub fn realtime_task_binding_plan(
301 tasks: &[RealtimeMaterializationTask],
302 source: &dyn eredu_checkpoint::store::CheckpointSource,
303) -> Result<RealtimeTaskBindingPlan, RealtimeModelContractError> {
304 let mut pinned = Vec::new();
305 let mut units = BTreeMap::<ParameterGroupOwner, Vec<WeightBinding>>::new();
306 for task in tasks {
307 let destination = match task.owner() {
308 ParameterGroupOwner::StaticRole(_) | ParameterGroupOwner::StaticAnyOf(_) => &mut pinned,
309 ParameterGroupOwner::ExecutionUnit { .. } => {
310 units.entry(task.owner().clone()).or_default()
311 }
312 };
313 let transformed = matches!(
314 task.lowering().kind(),
315 crate::WeightLoweringKind::Transform | crate::WeightLoweringKind::DerivedTransform
316 );
317 for component in task.components() {
318 let requirement = component.requirement();
319 let target = requirement.target().as_str();
320 let binding = if transformed {
321 if !source.is_authoritative_materialized_key(target) {
322 return Err(RealtimeModelContractError::BindingPlan {
323 detail: format!(
324 "transformed realtime output {target:?} is not authoritative"
325 ),
326 });
327 }
328 let metadata = source.source_metadata(target).map_err(|error| {
329 RealtimeModelContractError::BindingPlan {
330 detail: error.to_string(),
331 }
332 })?;
333 WeightBinding::new(
334 target,
335 target,
336 eredu_checkpoint::store::TensorSelection::Full,
337 metadata.encoded_byte_len,
338 )
339 } else {
340 let output = component.recipe_output().ok_or_else(|| {
341 RealtimeModelContractError::BindingPlan {
342 detail: format!("realtime recipe component {target:?} has no output"),
343 }
344 })?;
345 match requirement.recipe_owner() {
346 Some(owner) if owner != requirement.target() => {
347 WeightBinding::alias(target, owner.as_str(), output.byte_len())
348 }
349 _ => WeightBinding::from_recipe(
350 target,
351 component.recipe().cloned().ok_or_else(|| {
352 RealtimeModelContractError::BindingPlan {
353 detail: format!("realtime component {target:?} has no recipe"),
354 }
355 })?,
356 output.byte_len(),
357 ),
358 }
359 }
360 .map_err(|error| RealtimeModelContractError::BindingPlan {
361 detail: error.to_string(),
362 })?
363 .with_logical_target(task.lowering().target().as_str())
364 .map_err(|error| RealtimeModelContractError::BindingPlan {
365 detail: error.to_string(),
366 })?;
367 destination.push(binding);
368 }
369 }
370 WeightBindingPlan::new(&pinned).map_err(|error| RealtimeModelContractError::BindingPlan {
371 detail: error.to_string(),
372 })?;
373 for bindings in units.values() {
374 WeightBindingPlan::new(bindings).map_err(|error| {
375 RealtimeModelContractError::BindingPlan {
376 detail: error.to_string(),
377 }
378 })?;
379 }
380 Ok(RealtimeTaskBindingPlan { pinned, units })
381}
382
383#[derive(Debug)]
385pub struct PreparedRealtimeModelContract {
386 selected: SelectedRealtimeRealization,
387 tasks: Vec<RealtimeMaterializationTask>,
388}
389
390impl PreparedRealtimeModelContract {
391 pub fn new(
393 selected: SelectedRealtimeRealization,
394 tasks: impl IntoIterator<Item = RealtimeMaterializationTask>,
395 ) -> Result<Self, RealtimeModelContractError> {
396 let tasks = tasks.into_iter().collect::<Vec<_>>();
397 if tasks.len() != selected.weight_lowerings().len() {
398 return Err(RealtimeModelContractError::TaskCoverageMismatch);
399 }
400 for (task, selected_lowering) in tasks.iter().zip(selected.weight_lowerings()) {
401 if selected_lowering != &task.lowering {
402 return Err(RealtimeModelContractError::TaskSelectionMismatch {
403 target: task.lowering.target().as_str().to_owned(),
404 });
405 }
406 let expected_owner = selected
407 .execution_parameters()
408 .groups()
409 .iter()
410 .find_map(|group| {
411 group
412 .group()
413 .members()
414 .iter()
415 .any(|member| member.target() == task.lowering.target().as_str())
416 .then(|| group.owner())
417 })
418 .ok_or_else(|| RealtimeModelContractError::TaskOwnerUnavailable {
419 target: task.lowering.target().as_str().to_owned(),
420 })?;
421 if expected_owner != &task.owner {
422 return Err(RealtimeModelContractError::TaskOwnerMismatch {
423 target: task.lowering.target().as_str().to_owned(),
424 });
425 }
426 }
427 Ok(Self { selected, tasks })
428 }
429
430 pub const fn selected(&self) -> &SelectedRealtimeRealization {
432 &self.selected
433 }
434
435 pub fn tasks(&self) -> &[RealtimeMaterializationTask] {
437 &self.tasks
438 }
439
440 pub fn into_parts(
442 self,
443 ) -> (
444 SelectedRealtimeRealization,
445 Vec<RealtimeMaterializationTask>,
446 ) {
447 (self.selected, self.tasks)
448 }
449}
450
451pub trait RealtimeModelConstructionMechanisms<A, B>
453where
454 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
455 A: LayeredArchitecture<B, Self::State>,
456 Self::State: RuntimeState<B>,
457 Self::ResidentPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>,
458 Self::BoundedPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>,
459{
460 type State: RuntimeState<B>;
462 type PolicyError;
464 type ResidentPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>;
466 type BoundedPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>;
468 type Error;
470
471 #[allow(clippy::too_many_arguments)]
473 fn prepare_resident_materialization(
474 &mut self,
475 architecture: &mut A,
476 units: &mut [A::Unit],
477 source_architecture: Option<&mut A>,
478 source_units: Option<&mut [A::Unit]>,
479 tasks: &[RealtimeMaterializationTask],
480 selected: &SelectedRealtimeRealization,
481 context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
482 ) -> Result<(), Self::Error>;
483
484 fn resident_policy(
486 &mut self,
487 architecture: &mut A,
488 units: Vec<A::Unit>,
489 selected: &SelectedRealtimeRealization,
490 context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
491 ) -> Result<RealizedRealtimePolicy<Self::ResidentPolicy>, Self::Error>;
492
493 #[allow(clippy::too_many_arguments)]
495 fn bounded_policy(
496 &mut self,
497 architecture: &mut A,
498 source_architecture: Option<&mut A>,
499 tasks: &[RealtimeMaterializationTask],
500 selected: &SelectedRealtimeRealization,
501 context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
502 ) -> Result<RealizedRealtimePolicy<Self::BoundedPolicy>, Self::Error>;
503
504 fn realize_state(
506 &mut self,
507 selected: &crate::SelectedRealtimeStateRealization,
508 context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
509 ) -> Result<RealizedRealtimeState<Self::State>, Self::Error>;
510}
511
512pub enum RealtimeLayerwiseRuntime<A, B, S, R, P>
514where
515 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
516 S: RuntimeState<B>,
517 A: LayeredArchitecture<B, S>,
518 R: LayerwisePolicy<B, A::Unit>,
519 P: LayerwisePolicy<B, A::Unit>,
520{
521 Resident(LayerwiseRuntime<A, B, S, R>),
523 Bounded(LayerwiseRuntime<A, B, S, P>),
525}
526
527pub struct ConstructedRealtimeExecution<A, B, M>
529where
530 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
531 M: RealtimeModelConstructionMechanisms<A, B>,
532 A: LayeredArchitecture<B, M::State>,
533{
534 selected: SelectedRealtimeRealization,
535 execution: RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy>,
536 mechanisms: M,
537 backend: PhantomData<fn() -> B>,
538}
539
540impl<A, B, M> ConstructedRealtimeExecution<A, B, M>
541where
542 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
543 M: RealtimeModelConstructionMechanisms<A, B>,
544 A: LayeredArchitecture<B, M::State>,
545{
546 pub const fn selected(&self) -> &SelectedRealtimeRealization {
548 &self.selected
549 }
550
551 pub const fn execution(
553 &self,
554 ) -> &RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
555 &self.execution
556 }
557
558 pub fn execution_mut(
560 &mut self,
561 ) -> &mut RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
562 &mut self.execution
563 }
564
565 pub const fn mechanisms(&self) -> &M {
567 &self.mechanisms
568 }
569
570 pub fn mechanisms_mut(&mut self) -> &mut M {
572 &mut self.mechanisms
573 }
574
575 #[allow(clippy::type_complexity)]
578 pub fn into_parts(
579 self,
580 ) -> (
581 SelectedRealtimeRealization,
582 RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy>,
583 M,
584 ) {
585 (self.selected, self.execution, self.mechanisms)
586 }
587}
588
589pub struct ConstructedRealtimeModel<A, B, M>
591where
592 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
593 M: RealtimeModelConstructionMechanisms<A, B>,
594 A: LayeredArchitecture<B, M::State>,
595{
596 execution: ConstructedRealtimeExecution<A, B, M>,
597 state: M::State,
598}
599
600impl<A, B, M> ConstructedRealtimeModel<A, B, M>
601where
602 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
603 M: RealtimeModelConstructionMechanisms<A, B>,
604 A: LayeredArchitecture<B, M::State>,
605{
606 pub const fn selected(&self) -> &SelectedRealtimeRealization {
608 self.execution.selected()
609 }
610
611 pub const fn execution(
613 &self,
614 ) -> &RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
615 self.execution.execution()
616 }
617
618 pub fn execution_mut(
620 &mut self,
621 ) -> &mut RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
622 self.execution.execution_mut()
623 }
624
625 pub const fn state(&self) -> &M::State {
627 &self.state
628 }
629
630 pub fn state_mut(&mut self) -> &mut M::State {
632 &mut self.state
633 }
634
635 #[allow(clippy::type_complexity)]
637 pub fn execution_and_state_mut(
638 &mut self,
639 ) -> (
640 &mut RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy>,
641 &mut M::State,
642 ) {
643 (self.execution.execution_mut(), &mut self.state)
644 }
645
646 #[allow(clippy::type_complexity)]
648 pub fn constructed_execution_and_state_mut(
649 &mut self,
650 ) -> (&mut ConstructedRealtimeExecution<A, B, M>, &mut M::State) {
651 (&mut self.execution, &mut self.state)
652 }
653
654 pub const fn mechanisms(&self) -> &M {
656 self.execution.mechanisms()
657 }
658
659 pub fn mechanisms_mut(&mut self) -> &mut M {
661 self.execution.mechanisms_mut()
662 }
663
664 pub fn into_execution_and_state(self) -> (ConstructedRealtimeExecution<A, B, M>, M::State) {
666 (self.execution, self.state)
667 }
668}
669
670#[allow(clippy::type_complexity)]
672pub fn construct_realtime_model<A, B, M>(
673 mut architecture: A,
674 mut source_architecture: Option<A>,
675 prepared: PreparedRealtimeModelContract,
676 mut mechanisms: M,
677 context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
678) -> Result<
679 ConstructedRealtimeModel<A, B, M>,
680 RealtimeModelConstructionError<A::Error, M::PolicyError, M::Error>,
681>
682where
683 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
684 M: RealtimeModelConstructionMechanisms<A, B>,
685 A: LayeredArchitecture<B, M::State> + RealtimeArchitectureIdentity,
686 A::Error: std::fmt::Display,
687 M::PolicyError: std::fmt::Display,
688 M::Error: std::fmt::Display,
689{
690 let (selected, tasks) = prepared.into_parts();
691 let requires_source_architecture = selected.weight_lowerings().iter().any(|lowering| {
692 matches!(
693 lowering.kind(),
694 crate::WeightLoweringKind::Transform | crate::WeightLoweringKind::DerivedTransform
695 )
696 });
697 if requires_source_architecture != source_architecture.is_some() {
698 return Err(RealtimeModelConstructionError::Contract(
699 "selected realtime lowering and source-format architecture presence differ".into(),
700 ));
701 }
702 validate_architecture::<A, B, M::State>(&architecture, &selected, false, context)
703 .map_err(widen_error)?;
704 if let Some(source) = source_architecture.as_ref() {
705 validate_architecture::<A, B, M::State>(source, &selected, true, context)
706 .map_err(widen_error)?;
707 }
708 let (state, state_realization) = mechanisms
709 .realize_state(selected.state(), context)
710 .map_err(RealtimeModelConstructionError::Mechanism)?
711 .into_parts();
712 if &state_realization != selected.state() || state.layout() != selected.state().layout() {
713 return Err(RealtimeModelConstructionError::Contract(
714 "realized realtime state differs from selection".into(),
715 ));
716 }
717 let execution = match selected.residency() {
718 LayerWeightResidency::FullyResident => {
719 let mut units = construct_units::<A, B, M::State>(&architecture, &selected, context)
720 .map_err(widen_error)?;
721 let mut source_units = source_architecture
722 .as_ref()
723 .map(|source| construct_units::<A, B, M::State>(source, &selected, context))
724 .transpose()
725 .map_err(widen_error)?;
726 mechanisms
727 .prepare_resident_materialization(
728 &mut architecture,
729 &mut units,
730 source_architecture.as_mut(),
731 source_units.as_deref_mut(),
732 &tasks,
733 &selected,
734 context,
735 )
736 .map_err(RealtimeModelConstructionError::Mechanism)?;
737 let (policy, residency) = mechanisms
738 .resident_policy(&mut architecture, units, &selected, context)
739 .map_err(RealtimeModelConstructionError::Mechanism)?
740 .into_parts();
741 if residency != selected.residency() {
742 return Err(RealtimeModelConstructionError::Contract(
743 "realized realtime weight residency differs from selection".into(),
744 ));
745 }
746 RealtimeLayerwiseRuntime::Resident(LayerwiseRuntime::new(architecture, policy))
747 }
748 LayerWeightResidency::LayerwiseHost(_) | LayerWeightResidency::DenseDiskStream(_) => {
749 let (policy, residency) = mechanisms
750 .bounded_policy(
751 &mut architecture,
752 source_architecture.as_mut(),
753 &tasks,
754 &selected,
755 context,
756 )
757 .map_err(RealtimeModelConstructionError::Mechanism)?
758 .into_parts();
759 if residency != selected.residency() {
760 return Err(RealtimeModelConstructionError::Contract(
761 "realized realtime weight residency differs from selection".into(),
762 ));
763 }
764 RealtimeLayerwiseRuntime::Bounded(LayerwiseRuntime::new(architecture, policy))
765 }
766 };
767 Ok(ConstructedRealtimeModel {
768 execution: ConstructedRealtimeExecution {
769 selected,
770 execution,
771 mechanisms,
772 backend: PhantomData,
773 },
774 state,
775 })
776}
777
778fn widen_error<A, P, M>(
779 error: RealtimeModelConstructionError<A, std::convert::Infallible, std::convert::Infallible>,
780) -> RealtimeModelConstructionError<A, P, M>
781where
782 A: std::fmt::Display,
783 P: std::fmt::Display,
784 M: std::fmt::Display,
785{
786 match error {
787 RealtimeModelConstructionError::Architecture(error) => {
788 RealtimeModelConstructionError::Architecture(error)
789 }
790 RealtimeModelConstructionError::Contract(error) => {
791 RealtimeModelConstructionError::Contract(error)
792 }
793 RealtimeModelConstructionError::Mechanism(error) => match error {},
794 RealtimeModelConstructionError::Policy(error) => match error {},
795 }
796}
797
798fn construct_units<A, B, S>(
799 architecture: &A,
800 selected: &SelectedRealtimeRealization,
801 context: &<B::Tensor as Tensor>::Context,
802) -> Result<
803 Vec<A::Unit>,
804 RealtimeModelConstructionError<A::Error, std::convert::Infallible, std::convert::Infallible>,
805>
806where
807 B: NeuralBackend,
808 S: RuntimeState<B>,
809 A: LayeredArchitecture<B, S>,
810 A::Error: std::fmt::Display,
811{
812 (0..selected.execution_units().len())
813 .map(|ordinal| {
814 let address = selected
815 .execution_units()
816 .address(ordinal)
817 .expect("selected execution ordinal has an address");
818 architecture
819 .build_unit(address.group(), address.index(), context)
820 .map_err(RealtimeModelConstructionError::Architecture)
821 })
822 .collect()
823}
824
825fn validate_architecture<A, B, S>(
826 architecture: &A,
827 selected: &SelectedRealtimeRealization,
828 source: bool,
829 context: &<B::Tensor as Tensor>::Context,
830) -> Result<
831 (),
832 RealtimeModelConstructionError<A::Error, std::convert::Infallible, std::convert::Infallible>,
833>
834where
835 B: NeuralBackend,
836 S: RuntimeState<B>,
837 A: LayeredArchitecture<B, S> + RealtimeArchitectureIdentity,
838 A::Error: std::fmt::Display,
839{
840 if !source {
841 let actual = architecture
842 .realtime_construction_identity()
843 .map_err(RealtimeModelConstructionError::Contract)?;
844 let requirements = selected.requirements();
845 let expected = RealtimeArchitectureConstructionIdentity::new(
846 requirements.architecture().clone(),
847 requirements.speech_schedule_identity().clone(),
848 requirements.state_layout_identity().clone(),
849 );
850 if actual != expected {
851 return Err(RealtimeModelConstructionError::Contract(
852 "constructed realtime architecture identities differ from selection".into(),
853 ));
854 }
855 }
856 if architecture
857 .execution_graph()
858 .map_err(RealtimeModelConstructionError::Architecture)?
859 != *selected.execution_graph()
860 {
861 return Err(RealtimeModelConstructionError::Contract(
862 "constructed realtime execution graph differs from selection".into(),
863 ));
864 }
865 for group in 0..selected.execution_graph().groups().len() {
866 let actual = architecture
867 .group_unit_count(group)
868 .map_err(RealtimeModelConstructionError::Architecture)?;
869 let expected = selected
870 .execution_units()
871 .group_range(group)
872 .expect("selected layout contains every graph group")
873 .len();
874 if actual != expected {
875 return Err(RealtimeModelConstructionError::Contract(format!(
876 "constructed realtime group {group} unit count differs from selection"
877 )));
878 }
879 }
880 let actual = architecture
881 .parameter_description(context)
882 .map_err(RealtimeModelConstructionError::Architecture)?;
883 let expected = if source {
884 selected.source_parameters()
885 } else {
886 selected.execution_parameters()
887 };
888 if &actual != expected {
889 return Err(RealtimeModelConstructionError::Contract(
890 "constructed realtime parameter topology differs from selection".into(),
891 ));
892 }
893 if !source
894 && architecture
895 .state_layout()
896 .map_err(RealtimeModelConstructionError::Architecture)?
897 != *selected.state().layout()
898 {
899 return Err(RealtimeModelConstructionError::Contract(
900 "constructed realtime state layout differs from selection".into(),
901 ));
902 }
903 Ok(())
904}
905
906#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
908#[non_exhaustive]
909pub enum RealtimeModelContractError {
910 #[error("realtime source provenance differs for target {target}")]
912 SourceProvenanceMismatch {
913 target: String,
915 },
916 #[error("realtime recipe geometry differs for target {target}")]
918 RecipeGeometryMismatch {
919 target: String,
921 },
922 #[error("realtime recipe encoding differs for target {target}")]
924 RecipeEncodingMismatch {
925 target: String,
927 },
928 #[error("realtime component payload differs for target {target}")]
930 ComponentCoverageMismatch {
931 target: String,
933 },
934 #[error("realtime materialization tasks do not exactly cover selected targets")]
936 TaskCoverageMismatch,
937 #[error("realtime materialization task differs from selection for target {target}")]
939 TaskSelectionMismatch {
940 target: String,
942 },
943 #[error("realtime materialization target {target} has no execution owner")]
945 TaskOwnerUnavailable {
946 target: String,
948 },
949 #[error("realtime materialization owner differs from selection for target {target}")]
951 TaskOwnerMismatch {
952 target: String,
954 },
955 #[error("realtime binding plan is invalid: {detail}")]
957 BindingPlan {
958 detail: String,
960 },
961}
962
963#[derive(Debug, thiserror::Error)]
965pub enum RealtimeModelConstructionError<A, P, M>
966where
967 A: std::fmt::Display,
968 P: std::fmt::Display,
969 M: std::fmt::Display,
970{
971 #[error("realtime architecture construction failed: {0}")]
973 Architecture(A),
974 #[error("invalid realtime model construction: {0}")]
976 Contract(String),
977 #[error("realtime model mechanism failed: {0}")]
979 Mechanism(M),
980 #[error("realtime residency policy failed: {0}")]
982 Policy(P),
983}