1#![allow(clippy::too_many_arguments, clippy::type_complexity)]
4
5use std::collections::BTreeMap;
6
7use eredu_checkpoint::{recipe::DerivedWeightRecipe, store::CheckpointSource};
8use eredu_nn::{NeuralBackend, Parameterized, Tensor};
9
10use crate::{
11 observe_and_intervene, ActivationObserver, ExecutionGraph, ExecutionGroupSchedule,
12 ExecutionScheduleError, ExecutionUnitLayout, ExpertPass, NoAuxiliaryBoundary,
13 ObservedExpertProvider, RoutedExpertProvider, RoutedObservationPoint, RuntimeState,
14 StateLayout, SubmissionBackend,
15};
16
17pub trait StaticParameterVisitor<B: NeuralBackend> {
19 type Error;
21
22 fn visit<M>(&mut self, role: &str, module: &M) -> Result<(), Self::Error>
24 where
25 M: Parameterized<B::Tensor>;
26}
27
28pub trait StaticParameterVisitorMut<B: NeuralBackend> {
30 type Error;
32
33 fn visit_mut<M>(&mut self, role: &str, module: &mut M) -> Result<(), Self::Error>
35 where
36 M: Parameterized<B::Tensor>;
37}
38
39pub trait ArchitectureParameters<B: NeuralBackend> {
45 type DefinitionError;
47
48 fn state_layout(&self) -> Result<StateLayout, Self::DefinitionError>;
50
51 fn state_identity(
56 &self,
57 state: &crate::PartitionState,
58 topology: eredu_core::cache::PromptCacheTopology,
59 ) -> Result<crate::ModelStateIdentity, Self::DefinitionError>;
60
61 fn parameter_description(
63 &self,
64 context: &<B::Tensor as eredu_nn::Tensor>::Context,
65 ) -> Result<crate::ArchitectureParameterDescription, Self::DefinitionError>;
66
67 fn static_parameter_recipes(
69 &self,
70 _source: &dyn CheckpointSource,
71 ) -> Result<BTreeMap<String, DerivedWeightRecipe>, String> {
72 Ok(BTreeMap::new())
73 }
74
75 fn visit_static_parameters<V>(&self, visitor: &mut V) -> Result<(), V::Error>
77 where
78 V: StaticParameterVisitor<B>;
79
80 fn visit_static_parameters_mut<V>(&mut self, visitor: &mut V) -> Result<(), V::Error>
82 where
83 V: StaticParameterVisitorMut<B>;
84}
85
86pub struct LayeredForwardState<T, C> {
88 pub hidden: T,
90 pub context: C,
92}
93
94#[derive(Debug, Clone, Copy, Eq, PartialEq)]
96pub enum ArchitectureGroupKind {
97 Decoder,
99 Prediction,
101 VisionEncoder,
103 AudioEncoder,
105 Projector,
107 Merger,
109 ModalityFinalization,
111}
112
113#[derive(Debug)]
122pub struct LayeredPipelineSchedule<'a> {
123 graph: &'a ExecutionGraph,
124 schedule: ExecutionGroupSchedule<'a>,
125 active: Vec<bool>,
126 completed: usize,
127}
128
129impl<'a> LayeredPipelineSchedule<'a> {
130 pub fn try_new<E>(
138 graph: &'a ExecutionGraph,
139 group_contracts: impl IntoIterator<Item = (ArchitectureGroupKind, bool)>,
140 mut request_group_active: impl FnMut(usize) -> Result<bool, E>,
141 ) -> Result<Self, E>
142 where
143 E: From<LayeredPipelineScheduleError>,
144 {
145 let group_contracts = group_contracts.into_iter().collect::<Vec<_>>();
146 if group_contracts.len() != graph.groups().len() {
147 return Err(LayeredPipelineScheduleError::GroupContractCount {
148 graph: graph.groups().len(),
149 declared: group_contracts.len(),
150 }
151 .into());
152 }
153 let mut active = vec![false; group_contracts.len()];
154 for &group in graph.execution_order() {
155 let (kind, request_optional) = group_contracts[group];
156 if request_optional
157 && (!matches!(
158 kind,
159 ArchitectureGroupKind::VisionEncoder | ArchitectureGroupKind::AudioEncoder
160 ) || !graph.groups()[group].dependencies().is_empty())
161 {
162 return Err(LayeredPipelineScheduleError::InvalidRequestOptionalGroup {
163 group,
164 kind,
165 }
166 .into());
167 }
168 active[group] = match kind {
169 ArchitectureGroupKind::VisionEncoder | ArchitectureGroupKind::AudioEncoder => {
170 !request_optional || request_group_active(group)?
171 }
172 ArchitectureGroupKind::Projector | ArchitectureGroupKind::Merger => graph
173 .dependencies(group)
174 .expect("validated execution order contains a known group")
175 .iter()
176 .any(|&dependency| active[dependency]),
177 ArchitectureGroupKind::ModalityFinalization | ArchitectureGroupKind::Decoder => {
178 true
179 }
180 ArchitectureGroupKind::Prediction => false,
181 };
182 }
183 Ok(Self {
184 graph,
185 schedule: ExecutionGroupSchedule::new(graph),
186 active,
187 completed: 0,
188 })
189 }
190
191 pub fn is_active(&self, group: usize) -> Option<bool> {
193 self.active.get(group).copied()
194 }
195
196 pub fn activity(&self) -> &[bool] {
198 &self.active
199 }
200
201 pub fn ready_groups(&self) -> impl Iterator<Item = usize> + '_ {
203 self.schedule.startable_groups()
204 }
205
206 pub fn compatible_batch(&self, mut compatible: impl FnMut(usize, usize) -> bool) -> Vec<usize> {
208 let mut selected = Vec::new();
209 for candidate in self.ready_groups() {
210 if selected
211 .iter()
212 .copied()
213 .all(|group| compatible(group, candidate))
214 {
215 selected.push(candidate);
216 }
217 }
218 selected
219 }
220
221 pub fn dependencies(&self, group: usize) -> Option<&[usize]> {
223 self.graph.dependencies(group)
224 }
225
226 pub fn started(&mut self, group: usize) -> Result<Vec<usize>, LayeredPipelineScheduleError> {
231 self.schedule.started(group).map_err(Into::into)
232 }
233
234 pub fn ordered(&mut self, group: usize) -> Result<(), LayeredPipelineScheduleError> {
236 self.schedule.ordered(group)?;
237 self.completed += 1;
238 Ok(())
239 }
240
241 pub fn is_complete(&self) -> bool {
243 self.completed == self.active.len()
244 }
245}
246
247#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
249pub enum LayeredPipelineScheduleError {
250 #[error(
252 "execution graph contains {graph} groups but the pipeline declared {declared} group contracts"
253 )]
254 GroupContractCount {
255 graph: usize,
257 declared: usize,
259 },
260 #[error("execution group {group} of kind {kind:?} cannot be request-optional")]
262 InvalidRequestOptionalGroup {
263 group: usize,
265 kind: ArchitectureGroupKind,
267 },
268 #[error(transparent)]
270 Transition(#[from] ExecutionScheduleError),
271}
272
273#[derive(Debug, Clone, Copy, Eq, PartialEq)]
275pub enum ArchitectureGroupPlacement {
276 Pipeline,
278 OutputOwner,
280}
281
282#[derive(Debug, Clone, Copy, Eq, PartialEq)]
284pub enum ArchitectureMergeDestination {
285 LastOwner,
287 FirstPipelineOwner,
289 OutputOwner,
291}
292
293#[derive(Debug, Clone, Copy, Eq, PartialEq)]
295pub enum ArchitectureParallelSubgroup {
296 TensorSharded,
298 Decoder,
300}
301
302#[derive(Debug, Clone, Eq, PartialEq)]
304pub struct ArchitectureGroupTransport {
305 pub placement: ArchitectureGroupPlacement,
307 pub kind: ArchitectureGroupKind,
309 pub first_owner_static_roles: Vec<String>,
311 pub last_owner_static_roles: Vec<String>,
313 pub merge_destination: ArchitectureMergeDestination,
315 pub parallel_subgroup: Option<ArchitectureParallelSubgroup>,
317 pub request_optional: bool,
319}
320
321#[derive(Debug, Clone, Copy, Eq, PartialEq)]
323pub enum LayeredTraversalPoint {
324 Unit {
326 group: usize,
328 index: usize,
330 },
331 Group {
333 group: usize,
335 },
336}
337
338#[derive(Debug, Clone, Copy, Eq, PartialEq)]
340pub enum LayeredUnitAction {
341 Execute,
343 SkipRemainingGroup,
345}
346
347pub trait LayeredTraversalHook<B, C, E>
353where
354 B: NeuralBackend,
355{
356 fn before_unit(
358 &mut self,
359 _group: usize,
360 _index: usize,
361 _remaining_units: usize,
362 _value: &mut B::Tensor,
363 _forward: &mut C,
364 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
365 ) -> Result<LayeredUnitAction, E> {
366 Ok(LayeredUnitAction::Execute)
367 }
368
369 fn after_group_begin(
371 &mut self,
372 _group: usize,
373 _value: &mut B::Tensor,
374 _forward: &mut C,
375 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
376 ) -> Result<(), E> {
377 Ok(())
378 }
379
380 fn after_unit(
382 &mut self,
383 _group: usize,
384 _index: usize,
385 _value: &mut B::Tensor,
386 _forward: &mut C,
387 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
388 ) -> Result<(), E> {
389 Ok(())
390 }
391
392 fn after_group(
394 &mut self,
395 _group: usize,
396 _value: &mut B::Tensor,
397 _forward: &mut C,
398 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
399 ) -> Result<(), E> {
400 Ok(())
401 }
402}
403
404pub struct CompositeLayeredTraversalHook<L, R> {
410 left: L,
411 right: R,
412}
413
414impl<L, R> CompositeLayeredTraversalHook<L, R> {
415 pub const fn new(left: L, right: R) -> Self {
417 Self { left, right }
418 }
419
420 pub fn into_parts(self) -> (L, R) {
422 (self.left, self.right)
423 }
424}
425
426impl<B, C, E, L, R> LayeredTraversalHook<B, C, E> for CompositeLayeredTraversalHook<L, R>
427where
428 B: NeuralBackend,
429 L: LayeredTraversalHook<B, C, E>,
430 R: LayeredTraversalHook<B, C, E>,
431{
432 fn before_unit(
433 &mut self,
434 group: usize,
435 index: usize,
436 remaining_units: usize,
437 value: &mut B::Tensor,
438 forward: &mut C,
439 context: &<B::Tensor as eredu_nn::Tensor>::Context,
440 ) -> Result<LayeredUnitAction, E> {
441 let left = self
442 .left
443 .before_unit(group, index, remaining_units, value, forward, context)?;
444 let right =
445 self.right
446 .before_unit(group, index, remaining_units, value, forward, context)?;
447 Ok(
448 if left == LayeredUnitAction::SkipRemainingGroup
449 || right == LayeredUnitAction::SkipRemainingGroup
450 {
451 LayeredUnitAction::SkipRemainingGroup
452 } else {
453 LayeredUnitAction::Execute
454 },
455 )
456 }
457
458 fn after_unit(
459 &mut self,
460 group: usize,
461 index: usize,
462 value: &mut B::Tensor,
463 forward: &mut C,
464 context: &<B::Tensor as eredu_nn::Tensor>::Context,
465 ) -> Result<(), E> {
466 self.left
467 .after_unit(group, index, value, forward, context)?;
468 self.right.after_unit(group, index, value, forward, context)
469 }
470
471 fn after_group_begin(
472 &mut self,
473 group: usize,
474 value: &mut B::Tensor,
475 forward: &mut C,
476 context: &<B::Tensor as eredu_nn::Tensor>::Context,
477 ) -> Result<(), E> {
478 self.left
479 .after_group_begin(group, value, forward, context)?;
480 self.right.after_group_begin(group, value, forward, context)
481 }
482
483 fn after_group(
484 &mut self,
485 group: usize,
486 value: &mut B::Tensor,
487 forward: &mut C,
488 context: &<B::Tensor as eredu_nn::Tensor>::Context,
489 ) -> Result<(), E> {
490 self.left.after_group(group, value, forward, context)?;
491 self.right.after_group(group, value, forward, context)
492 }
493}
494
495struct NoopLayeredTraversalHook;
496
497impl<B, C, E> LayeredTraversalHook<B, C, E> for NoopLayeredTraversalHook where B: NeuralBackend {}
498
499struct ActivationObserverTraversalHook<'a, O: ?Sized> {
500 observer: std::rc::Rc<std::cell::RefCell<&'a mut O>>,
501 units: Vec<Vec<String>>,
502 group_inputs: Vec<Option<String>>,
503 group_outputs: Vec<Option<String>>,
504}
505
506impl<B, C, E, O> LayeredTraversalHook<B, C, E> for ActivationObserverTraversalHook<'_, O>
507where
508 B: NeuralBackend,
509 O: ActivationObserver<B::Tensor, E> + ?Sized,
510{
511 fn before_unit(
512 &mut self,
513 group: usize,
514 index: usize,
515 _remaining_units: usize,
516 value: &mut B::Tensor,
517 _forward: &mut C,
518 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
519 ) -> Result<LayeredUnitAction, E> {
520 let path = eredu_core::UnitObservation::Input.path(&self.units[group][index]);
521 let mut observer = self.observer.borrow_mut();
522 *value = observe_and_intervene(&mut **observer, &path, value)?;
523 Ok(LayeredUnitAction::Execute)
524 }
525
526 fn after_unit(
527 &mut self,
528 group: usize,
529 index: usize,
530 value: &mut B::Tensor,
531 _forward: &mut C,
532 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
533 ) -> Result<(), E> {
534 let path = eredu_core::UnitObservation::Output.path(&self.units[group][index]);
535 let mut observer = self.observer.borrow_mut();
536 *value = observe_and_intervene(&mut **observer, &path, value)?;
537 Ok(())
538 }
539
540 fn after_group_begin(
541 &mut self,
542 group: usize,
543 value: &mut B::Tensor,
544 _forward: &mut C,
545 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
546 ) -> Result<(), E> {
547 if let Some(path) = self.group_inputs.get(group).and_then(Option::as_deref) {
548 let mut observer = self.observer.borrow_mut();
549 *value = observe_and_intervene(&mut **observer, path, value)?;
550 }
551 Ok(())
552 }
553
554 fn after_group(
555 &mut self,
556 group: usize,
557 value: &mut B::Tensor,
558 _forward: &mut C,
559 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
560 ) -> Result<(), E> {
561 if let Some(path) = self.group_outputs.get(group).and_then(Option::as_deref) {
562 let mut observer = self.observer.borrow_mut();
563 *value = observe_and_intervene(&mut **observer, path, value)?;
564 }
565 Ok(())
566 }
567}
568
569struct AfterUnitTraversalHook<F> {
570 after_unit: F,
571}
572
573struct AfterUnitContextTraversalHook<F> {
574 after_unit: F,
575}
576
577impl<B, C, E, F> LayeredTraversalHook<B, C, E> for AfterUnitTraversalHook<F>
578where
579 B: NeuralBackend,
580 F: FnMut(usize, usize, &B::Tensor, &mut C) -> Result<(), E>,
581{
582 fn after_unit(
583 &mut self,
584 group: usize,
585 index: usize,
586 value: &mut B::Tensor,
587 forward: &mut C,
588 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
589 ) -> Result<(), E> {
590 (self.after_unit)(group, index, value, forward)
591 }
592}
593
594impl<B, C, E, F> LayeredTraversalHook<B, C, E> for AfterUnitContextTraversalHook<F>
595where
596 B: NeuralBackend,
597 F: FnMut(usize, usize, &mut C) -> Result<(), E>,
598{
599 fn after_unit(
600 &mut self,
601 group: usize,
602 index: usize,
603 _value: &mut B::Tensor,
604 forward: &mut C,
605 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
606 ) -> Result<(), E> {
607 (self.after_unit)(group, index, forward)
608 }
609}
610
611pub trait LayeredArchitecture<B, S>:
617 ArchitectureParameters<B, DefinitionError = Self::Error>
618where
619 B: NeuralBackend,
620 S: RuntimeState<B>,
621{
622 type Input<'a>
624 where
625 Self: 'a;
626 type StaticModules: Parameterized<B::Tensor>;
628 type Unit: Parameterized<B::Tensor>;
630 type ForwardContext;
632 type RetainedContextValues<'a>: Iterator<Item = &'a B::Tensor>
634 where
635 Self: 'a,
636 B::Tensor: 'a;
637 type Error;
639
640 fn group_transport(&self, group: usize) -> ArchitectureGroupTransport;
642
643 fn primary_execution_group(&self) -> &str;
650
651 fn prediction_execution_groups(&self) -> Vec<String> {
656 Vec::new()
657 }
658
659 fn prediction_target_capture(_context: &Self::ForwardContext) -> Option<&B::Tensor> {
661 None
662 }
663
664 fn prediction_target_placeholder_shape(
666 &self,
667 _forward: &Self::ForwardContext,
668 ) -> Result<Option<Vec<i32>>, Self::Error> {
669 Ok(None)
670 }
671
672 fn state_partition_plan(&self, layout: &StateLayout) -> crate::ArchitectureStatePartitionPlan;
674
675 fn execution_graph(&self) -> Result<ExecutionGraph, Self::Error>;
677
678 fn group_unit_count(&self, group: usize) -> Result<usize, Self::Error>;
680
681 fn unit_path(&self, group: usize, index: usize) -> Result<String, Self::Error>;
683
684 fn group_input_observation_path(&self, _group: usize) -> Result<Option<String>, Self::Error> {
686 Ok(None)
687 }
688
689 fn group_output_observation_path(&self, _group: usize) -> Result<Option<String>, Self::Error> {
691 Ok(None)
692 }
693
694 fn static_modules(&self) -> &Self::StaticModules;
696
697 fn static_modules_mut(&mut self) -> &mut Self::StaticModules;
699
700 fn build_unit(
702 &self,
703 group: usize,
704 index: usize,
705 context: &<B::Tensor as eredu_nn::Tensor>::Context,
706 ) -> Result<Self::Unit, Self::Error>;
707
708 fn begin_forward<'a>(
710 &mut self,
711 input: Self::Input<'a>,
712 state: &mut S,
713 context: &<B::Tensor as eredu_nn::Tensor>::Context,
714 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
715
716 fn begin_execution_group(
718 &mut self,
719 group: usize,
720 initial: &B::Tensor,
721 dependencies: &[&B::Tensor],
722 state: &mut S,
723 forward: &mut Self::ForwardContext,
724 context: &<B::Tensor as eredu_nn::Tensor>::Context,
725 ) -> Result<B::Tensor, Self::Error>;
726
727 fn should_execute_group(&self, _group: usize, _forward: &Self::ForwardContext) -> bool {
729 true
730 }
731
732 fn state_ordinal(&self, _group: usize, _index: usize, ordinal: usize) -> usize {
738 ordinal
739 }
740
741 fn retained_state_ordinals(
747 &self,
748 group: usize,
749 index: usize,
750 ordinal: usize,
751 ) -> std::ops::Range<usize> {
752 let state = self.state_ordinal(group, index, ordinal);
753 state..state + 1
754 }
755
756 fn forward_unit(
758 &mut self,
759 group: usize,
760 index: usize,
761 unit: &mut Self::Unit,
762 hidden: &B::Tensor,
763 state: &mut S,
764 forward: &mut Self::ForwardContext,
765 context: &<B::Tensor as eredu_nn::Tensor>::Context,
766 ) -> Result<B::Tensor, Self::Error>;
767
768 fn complete_execution_group(
770 &mut self,
771 _group: usize,
772 hidden: &B::Tensor,
773 _state: &mut S,
774 _forward: &mut Self::ForwardContext,
775 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
776 ) -> Result<B::Tensor, Self::Error> {
777 Ok(hidden.clone())
778 }
779
780 fn finish_forward(
782 &mut self,
783 hidden: &B::Tensor,
784 state: &mut S,
785 forward: &Self::ForwardContext,
786 context: &<B::Tensor as eredu_nn::Tensor>::Context,
787 ) -> Result<B::Tensor, Self::Error>;
788
789 fn retained_context_values<'a>(
791 &'a self,
792 forward: &'a Self::ForwardContext,
793 group: usize,
794 index: usize,
795 ) -> Self::RetainedContextValues<'a>;
796}
797
798pub trait ParallelLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
804where
805 B: NeuralBackend,
806 S: RuntimeState<B>,
807{
808 fn begin_forward_parallel<'a>(
810 &mut self,
811 input: Self::Input<'a>,
812 state: &mut S,
813 parallel: &B::ParallelContext,
814 context: &<B::Tensor as eredu_nn::Tensor>::Context,
815 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
816
817 fn forward_unit_parallel(
819 &mut self,
820 group_index: usize,
821 index: usize,
822 unit: &mut Self::Unit,
823 hidden: &B::Tensor,
824 state: &mut S,
825 forward: &mut Self::ForwardContext,
826 parallel: &B::ParallelContext,
827 context: &<B::Tensor as eredu_nn::Tensor>::Context,
828 ) -> Result<B::Tensor, Self::Error>;
829
830 fn begin_execution_group_parallel(
832 &mut self,
833 group_index: usize,
834 initial: &B::Tensor,
835 dependencies: &[&B::Tensor],
836 state: &mut S,
837 forward: &mut Self::ForwardContext,
838 _parallel: &B::ParallelContext,
839 context: &<B::Tensor as eredu_nn::Tensor>::Context,
840 ) -> Result<B::Tensor, Self::Error> {
841 self.begin_execution_group(group_index, initial, dependencies, state, forward, context)
842 }
843
844 fn complete_execution_group_parallel(
846 &mut self,
847 group_index: usize,
848 hidden: &B::Tensor,
849 state: &mut S,
850 forward: &mut Self::ForwardContext,
851 _parallel: &B::ParallelContext,
852 context: &<B::Tensor as eredu_nn::Tensor>::Context,
853 ) -> Result<B::Tensor, Self::Error> {
854 self.complete_execution_group(group_index, hidden, state, forward, context)
855 }
856
857 fn finish_forward_parallel(
859 &mut self,
860 hidden: &B::Tensor,
861 state: &mut S,
862 forward: &Self::ForwardContext,
863 parallel: &B::ParallelContext,
864 context: &<B::Tensor as eredu_nn::Tensor>::Context,
865 ) -> Result<B::Tensor, Self::Error>;
866}
867
868#[derive(Debug)]
873pub enum LayeredPartitionInput<'a, T, A = NoAuxiliaryBoundary> {
874 Tokens(&'a T),
876 Hidden {
878 hidden: T,
880 auxiliary: A,
882 },
883}
884
885pub enum LayeredPartitionOutput<T, A = NoAuxiliaryBoundary> {
891 Final {
893 output: T,
895 retained: Option<T>,
897 },
898 Boundary {
900 hidden: T,
902 auxiliary: A,
904 },
905}
906
907pub trait PartitionedLayeredArchitecture<B, S>: ParallelLayeredArchitecture<B, S>
914where
915 B: NeuralBackend,
916 S: RuntimeState<B>,
917{
918 type Boundary: crate::ArchitectureBoundary;
920
921 fn boundary_schema(&self) -> Result<Self::Boundary, Self::Error>;
923
924 fn begin_partition<'a>(
926 &mut self,
927 input: LayeredPartitionInput<
928 'a,
929 B::Tensor,
930 <Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
931 >,
932 mask: Option<&B::Tensor>,
933 state: &mut S,
934 expected: &crate::StateLayout,
935 first_state_ordinal: usize,
936 context: &<B::Tensor as eredu_nn::Tensor>::Context,
937 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
938
939 #[allow(clippy::too_many_arguments)]
941 fn begin_partition_parallel<'a>(
942 &mut self,
943 input: LayeredPartitionInput<
944 'a,
945 B::Tensor,
946 <Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
947 >,
948 mask: Option<&B::Tensor>,
949 state: &mut S,
950 expected: &crate::StateLayout,
951 first_state_ordinal: usize,
952 parallel: &B::ParallelContext,
953 context: &<B::Tensor as eredu_nn::Tensor>::Context,
954 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
955
956 #[allow(clippy::too_many_arguments)]
961 fn enter_partition_group(
962 &mut self,
963 group: usize,
964 initial: &B::Tensor,
965 state: &mut S,
966 forward: &mut Self::ForwardContext,
967 parallel: Option<&B::ParallelContext>,
968 context: &<B::Tensor as eredu_nn::Tensor>::Context,
969 ) -> Result<B::Tensor, Self::Error> {
970 match parallel {
971 Some(parallel) => self.begin_execution_group_parallel(
972 group,
973 initial,
974 &[],
975 state,
976 forward,
977 parallel,
978 context,
979 ),
980 None => self.begin_execution_group(group, initial, &[], state, forward, context),
981 }
982 }
983
984 #[allow(clippy::too_many_arguments)]
987 fn leave_partition_group(
988 &mut self,
989 group: usize,
990 hidden: &B::Tensor,
991 state: &mut S,
992 forward: &mut Self::ForwardContext,
993 parallel: Option<&B::ParallelContext>,
994 context: &<B::Tensor as eredu_nn::Tensor>::Context,
995 ) -> Result<B::Tensor, Self::Error> {
996 match parallel {
997 Some(parallel) => self.complete_execution_group_parallel(
998 group, hidden, state, forward, parallel, context,
999 ),
1000 None => self.complete_execution_group(group, hidden, state, forward, context),
1001 }
1002 }
1003
1004 #[allow(clippy::too_many_arguments)]
1006 fn finish_partition(
1007 &mut self,
1008 hidden: &B::Tensor,
1009 state: &mut S,
1010 forward: &Self::ForwardContext,
1011 owns_output: bool,
1012 parallel: Option<&B::ParallelContext>,
1013 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1014 ) -> Result<
1015 LayeredPartitionOutput<
1016 B::Tensor,
1017 <Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
1018 >,
1019 Self::Error,
1020 >;
1021}
1022
1023pub trait RoutedLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
1028where
1029 B: eredu_nn::GroupedNeuralBackend,
1030 S: RuntimeState<B>,
1031{
1032 fn expert_pass_for_unit(
1037 &self,
1038 _group: usize,
1039 _index: usize,
1040 hidden: &B::Tensor,
1041 _forward: &Self::ForwardContext,
1042 ) -> ExpertPass {
1043 let sequence = hidden
1044 .shape()
1045 .get(hidden.shape().len().saturating_sub(2))
1046 .copied()
1047 .unwrap_or(1);
1048 if sequence > 1 {
1049 ExpertPass::Prefill
1050 } else {
1051 ExpertPass::Decode
1052 }
1053 }
1054
1055 fn routed_observation_point(
1061 &self,
1062 _group: usize,
1063 _index: usize,
1064 ) -> Result<Option<RoutedObservationPoint>, Self::Error> {
1065 Ok(None)
1066 }
1067
1068 #[allow(clippy::too_many_arguments)]
1070 fn forward_unit_with_provider<P>(
1071 &mut self,
1072 group: usize,
1073 index: usize,
1074 unit: &mut Self::Unit,
1075 hidden: &B::Tensor,
1076 state: &mut S,
1077 forward: &mut Self::ForwardContext,
1078 pass: ExpertPass,
1079 provider: &mut P,
1080 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1081 ) -> Result<B::Tensor, Self::Error>
1082 where
1083 P: RoutedExpertProvider<B>,
1084 P::Error: std::fmt::Display;
1085
1086 #[allow(clippy::too_many_arguments)]
1088 fn forward_unit_with_inferred_provider<P>(
1089 &mut self,
1090 group: usize,
1091 index: usize,
1092 unit: &mut Self::Unit,
1093 hidden: &B::Tensor,
1094 state: &mut S,
1095 forward: &mut Self::ForwardContext,
1096 provider: &mut P,
1097 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1098 ) -> Result<B::Tensor, Self::Error>
1099 where
1100 P: RoutedExpertProvider<B>,
1101 P::Error: std::fmt::Display,
1102 {
1103 let pass = self.expert_pass_for_unit(group, index, hidden, forward);
1104 self.forward_unit_with_provider(
1105 group, index, unit, hidden, state, forward, pass, provider, context,
1106 )
1107 }
1108
1109 #[allow(clippy::too_many_arguments)]
1112 fn forward_unit_observed_with_provider<P, O>(
1113 &mut self,
1114 group: usize,
1115 index: usize,
1116 unit: &mut Self::Unit,
1117 hidden: &B::Tensor,
1118 state: &mut S,
1119 forward: &mut Self::ForwardContext,
1120 pass: ExpertPass,
1121 provider: &mut P,
1122 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1123 observer: &mut O,
1124 ) -> Result<B::Tensor, Self::Error>
1125 where
1126 P: RoutedExpertProvider<B>,
1127 P::Error: std::fmt::Display,
1128 O: ActivationObserver<B::Tensor, Self::Error> + ?Sized,
1129 Self::Error: std::fmt::Display,
1130 {
1131 match self.routed_observation_point(group, index)? {
1132 Some(point) => {
1133 let mut observed = ObservedExpertProvider::new(provider, observer, point);
1134 self.forward_unit_with_provider(
1135 group,
1136 index,
1137 unit,
1138 hidden,
1139 state,
1140 forward,
1141 pass,
1142 &mut observed,
1143 context,
1144 )
1145 }
1146 None => self.forward_unit_with_provider(
1147 group, index, unit, hidden, state, forward, pass, provider, context,
1148 ),
1149 }
1150 }
1151
1152 #[allow(clippy::too_many_arguments)]
1155 fn forward_unit_observed_with_inferred_provider<P, O>(
1156 &mut self,
1157 group: usize,
1158 index: usize,
1159 unit: &mut Self::Unit,
1160 hidden: &B::Tensor,
1161 state: &mut S,
1162 forward: &mut Self::ForwardContext,
1163 provider: &mut P,
1164 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1165 observer: &mut O,
1166 ) -> Result<B::Tensor, Self::Error>
1167 where
1168 P: RoutedExpertProvider<B>,
1169 P::Error: std::fmt::Display,
1170 O: ActivationObserver<B::Tensor, Self::Error> + ?Sized,
1171 Self::Error: std::fmt::Display,
1172 {
1173 let pass = self.expert_pass_for_unit(group, index, hidden, forward);
1174 self.forward_unit_observed_with_provider(
1175 group, index, unit, hidden, state, forward, pass, provider, context, observer,
1176 )
1177 }
1178}
1179
1180pub trait ParallelRoutedLayeredArchitecture<B, S>:
1182 RoutedLayeredArchitecture<B, S> + ParallelLayeredArchitecture<B, S>
1183where
1184 B: eredu_nn::GroupedNeuralBackend,
1185 S: RuntimeState<B>,
1186{
1187 #[allow(clippy::too_many_arguments)]
1189 fn forward_unit_parallel_with_provider<P>(
1190 &mut self,
1191 group: usize,
1192 index: usize,
1193 unit: &mut Self::Unit,
1194 hidden: &B::Tensor,
1195 state: &mut S,
1196 forward: &mut Self::ForwardContext,
1197 pass: ExpertPass,
1198 provider: &mut P,
1199 parallel: &B::ParallelContext,
1200 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1201 ) -> Result<B::Tensor, Self::Error>
1202 where
1203 P: crate::TensorParallelRoutedExpertProvider<B>,
1204 P::Error: std::fmt::Display;
1205}
1206
1207pub struct ResidentRuntime<A, B, S>
1209where
1210 B: NeuralBackend,
1211 S: RuntimeState<B>,
1212 A: LayeredArchitecture<B, S>,
1213{
1214 architecture: A,
1215 graph: ExecutionGraph,
1216 units: Vec<Vec<A::Unit>>,
1217 backend: std::marker::PhantomData<fn() -> (B, S)>,
1218}
1219
1220impl<A, B, S> ResidentRuntime<A, B, S>
1221where
1222 B: NeuralBackend,
1223 S: RuntimeState<B>,
1224 A: LayeredArchitecture<B, S>,
1225{
1226 pub fn new(
1228 architecture: A,
1229 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1230 ) -> Result<Self, A::Error> {
1231 let graph = architecture.execution_graph()?;
1232 let mut units = Vec::with_capacity(graph.groups().len());
1233 for group in 0..graph.groups().len() {
1234 let count = architecture.group_unit_count(group)?;
1235 units.push(
1236 (0..count)
1237 .map(|index| architecture.build_unit(group, index, context))
1238 .collect::<Result<Vec<_>, _>>()?,
1239 );
1240 }
1241 Ok(Self {
1242 architecture,
1243 graph,
1244 units,
1245 backend: std::marker::PhantomData,
1246 })
1247 }
1248
1249 pub fn forward<'a>(
1251 &mut self,
1252 input: A::Input<'a>,
1253 state: &mut S,
1254 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1255 ) -> Result<B::Tensor, A::Error> {
1256 self.forward_with_context(input, state, context)
1257 .map(|(output, _)| output)
1258 }
1259
1260 pub fn forward_with_context<'a>(
1266 &mut self,
1267 input: A::Input<'a>,
1268 state: &mut S,
1269 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1270 ) -> Result<(B::Tensor, A::ForwardContext), A::Error> {
1271 self.forward_with_traversal_hook(input, state, context, &mut NoopLayeredTraversalHook)
1272 }
1273
1274 pub fn forward_with_traversal_hook<'a, H>(
1276 &mut self,
1277 input: A::Input<'a>,
1278 state: &mut S,
1279 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1280 hook: &mut H,
1281 ) -> Result<(B::Tensor, A::ForwardContext), A::Error>
1282 where
1283 H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
1284 {
1285 let forward = self.architecture.begin_forward(input, state, context)?;
1286 let initial = forward.hidden;
1287 let mut forward_context = forward.context;
1288 let mut schedule = ExecutionGroupSchedule::new(&self.graph);
1289 let mut outputs: Vec<Option<B::Tensor>> = vec![None; self.graph.groups().len()];
1290 for &group in self.graph.execution_order() {
1291 let dependencies = schedule
1292 .dependencies(group)
1293 .expect("validated execution order contains a known group")
1294 .iter()
1295 .map(|&dependency| {
1296 outputs[dependency]
1297 .as_ref()
1298 .expect("topological dependency has completed")
1299 .clone()
1300 })
1301 .collect::<Vec<_>>();
1302 let dependency_refs = dependencies.iter().collect::<Vec<_>>();
1303 let mut hidden = self.architecture.begin_execution_group(
1304 group,
1305 &initial,
1306 &dependency_refs,
1307 state,
1308 &mut forward_context,
1309 context,
1310 )?;
1311 hook.after_group_begin(group, &mut hidden, &mut forward_context, context)?;
1312 for dependency in schedule
1313 .started(group)
1314 .expect("topological execution starts only ready groups")
1315 {
1316 outputs[dependency] = None;
1317 }
1318 if self
1319 .architecture
1320 .should_execute_group(group, &forward_context)
1321 {
1322 let unit_count = self.units[group].len();
1323 for (index, unit) in self.units[group].iter_mut().enumerate() {
1324 if hook.before_unit(
1325 group,
1326 index,
1327 unit_count - index,
1328 &mut hidden,
1329 &mut forward_context,
1330 context,
1331 )? == LayeredUnitAction::SkipRemainingGroup
1332 {
1333 break;
1334 }
1335 hidden = self.architecture.forward_unit(
1336 group,
1337 index,
1338 unit,
1339 &hidden,
1340 state,
1341 &mut forward_context,
1342 context,
1343 )?;
1344 hook.after_unit(group, index, &mut hidden, &mut forward_context, context)?;
1345 }
1346 }
1347 hidden = self.architecture.complete_execution_group(
1348 group,
1349 &hidden,
1350 state,
1351 &mut forward_context,
1352 context,
1353 )?;
1354 hook.after_group(group, &mut hidden, &mut forward_context, context)?;
1355 outputs[group] = Some(hidden);
1356 schedule
1357 .ordered(group)
1358 .expect("started group can be ordered exactly once");
1359 }
1360 let hidden = outputs[self.graph.output()]
1361 .take()
1362 .expect("validated graph output completed");
1363 let output = self
1364 .architecture
1365 .finish_forward(&hidden, state, &forward_context, context)?;
1366 Ok((output, forward_context))
1367 }
1368
1369 pub const fn architecture(&self) -> &A {
1371 &self.architecture
1372 }
1373
1374 pub fn architecture_mut(&mut self) -> &mut A {
1376 &mut self.architecture
1377 }
1378
1379 pub fn units(&self) -> &[Vec<A::Unit>] {
1381 &self.units
1382 }
1383
1384 pub fn units_mut(&mut self) -> &mut [Vec<A::Unit>] {
1386 &mut self.units
1387 }
1388
1389 pub fn into_parts(self) -> (A, Vec<A::Unit>) {
1391 (
1392 self.architecture,
1393 self.units.into_iter().flatten().collect(),
1394 )
1395 }
1396}
1397
1398pub trait LayerwisePolicy<B, U>
1400where
1401 B: NeuralBackend,
1402{
1403 type Lease: std::ops::DerefMut<Target = U>;
1405 type Error;
1407
1408 fn begin(
1410 &mut self,
1411 initial: &B::Tensor,
1412 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1413 ) -> Result<(), Self::Error>;
1414
1415 fn abort(
1421 &mut self,
1422 active: Option<(usize, crate::ExecutionUnitAddress, Self::Lease)>,
1423 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
1424 ) {
1425 drop(active);
1426 }
1427
1428 fn acquire<E, F>(
1433 &mut self,
1434 ordinal: usize,
1435 address: crate::ExecutionUnitAddress,
1436 build: F,
1437 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1438 ) -> Result<Self::Lease, LayerwiseAcquireError<E, Self::Error>>
1439 where
1440 F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>;
1441
1442 fn complete<'a, StateValues, ContextValues>(
1444 &mut self,
1445 ordinal: usize,
1446 address: crate::ExecutionUnitAddress,
1447 lease: Self::Lease,
1448 output: &'a B::Tensor,
1449 state_values: StateValues,
1450 context_values: ContextValues,
1451 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1452 ) -> Result<(), Self::Error>
1453 where
1454 B::Tensor: 'a,
1455 StateValues: Iterator<Item = &'a B::Tensor>,
1456 ContextValues: Iterator<Item = &'a B::Tensor>;
1457
1458 fn finish(
1460 &mut self,
1461 output: &B::Tensor,
1462 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1463 ) -> Result<(), Self::Error>;
1464}
1465
1466pub struct LayerwisePolicyForward<'a, B, U, P>
1468where
1469 B: NeuralBackend,
1470 P: LayerwisePolicy<B, U>,
1471{
1472 policy: &'a mut P,
1473 context: &'a <B::Tensor as eredu_nn::Tensor>::Context,
1474 active: Option<(usize, crate::ExecutionUnitAddress, P::Lease)>,
1475 finished: bool,
1476 unit: std::marker::PhantomData<fn() -> U>,
1477}
1478
1479impl<'a, B, U, P> LayerwisePolicyForward<'a, B, U, P>
1480where
1481 B: NeuralBackend,
1482 P: LayerwisePolicy<B, U>,
1483{
1484 pub fn begin(
1486 policy: &'a mut P,
1487 initial: &B::Tensor,
1488 context: &'a <B::Tensor as eredu_nn::Tensor>::Context,
1489 ) -> Result<Self, P::Error> {
1490 if let Err(error) = policy.begin(initial, context) {
1491 policy.abort(None, context);
1492 return Err(error);
1493 }
1494 Ok(Self {
1495 policy,
1496 context,
1497 active: None,
1498 finished: false,
1499 unit: std::marker::PhantomData,
1500 })
1501 }
1502
1503 pub fn acquire<E, F>(
1505 &mut self,
1506 ordinal: usize,
1507 address: crate::ExecutionUnitAddress,
1508 build: F,
1509 ) -> Result<&mut P::Lease, LayerwiseAcquireError<E, P::Error>>
1510 where
1511 F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>,
1512 {
1513 debug_assert!(self.active.is_none());
1514 let lease = self.policy.acquire(ordinal, address, build, self.context)?;
1515 self.active = Some((ordinal, address, lease));
1516 Ok(&mut self
1517 .active
1518 .as_mut()
1519 .expect("acquired policy lease is active")
1520 .2)
1521 }
1522
1523 pub fn complete<'value, StateValues, ContextValues>(
1525 &mut self,
1526 output: &'value B::Tensor,
1527 state_values: StateValues,
1528 context_values: ContextValues,
1529 ) -> Result<(), P::Error>
1530 where
1531 B::Tensor: 'value,
1532 StateValues: Iterator<Item = &'value B::Tensor>,
1533 ContextValues: Iterator<Item = &'value B::Tensor>,
1534 {
1535 let (ordinal, address, lease) = self
1536 .active
1537 .take()
1538 .expect("policy completion follows one acquisition");
1539 self.policy.complete(
1540 ordinal,
1541 address,
1542 lease,
1543 output,
1544 state_values,
1545 context_values,
1546 self.context,
1547 )
1548 }
1549
1550 pub fn finish(&mut self, output: &B::Tensor) -> Result<(), P::Error> {
1552 self.policy.finish(output, self.context)?;
1553 self.finished = true;
1554 Ok(())
1555 }
1556}
1557
1558impl<B, U, P> Drop for LayerwisePolicyForward<'_, B, U, P>
1559where
1560 B: NeuralBackend,
1561 P: LayerwisePolicy<B, U>,
1562{
1563 fn drop(&mut self) {
1564 if !self.finished {
1565 self.policy.abort(self.active.take(), self.context);
1566 }
1567 }
1568}
1569
1570#[derive(Debug)]
1572pub enum LayerwiseAcquireError<A, P> {
1573 Architecture(A),
1575 Policy(P),
1577}
1578
1579#[derive(Debug, thiserror::Error)]
1581pub enum LayerwiseRuntimeError<A, P>
1582where
1583 A: std::fmt::Display,
1584 P: std::fmt::Display,
1585{
1586 #[error("layered architecture failed: {0}")]
1588 Architecture(A),
1589 #[error(transparent)]
1591 State(#[from] crate::StateError),
1592 #[error(transparent)]
1594 Layout(#[from] crate::ExecutionUnitLayoutError),
1595 #[error("layerwise execution policy failed: {0}")]
1597 Policy(P),
1598 #[error("layerwise backend submission failed: {0}")]
1600 Submission(String),
1601}
1602
1603pub struct LayerwiseRuntime<A, B, S, P>
1605where
1606 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as eredu_nn::Tensor>::Context>,
1607 S: RuntimeState<B>,
1608 A: LayeredArchitecture<B, S>,
1609 P: LayerwisePolicy<B, A::Unit>,
1610{
1611 architecture: A,
1612 policy: P,
1613 executors: Option<Vec<B::OwnedExecutor>>,
1614 backend: std::marker::PhantomData<fn() -> (B, S)>,
1615}
1616
1617impl<A, B, S, P> LayerwiseRuntime<A, B, S, P>
1618where
1619 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as eredu_nn::Tensor>::Context>,
1620 S: RuntimeState<B>,
1621 A: LayeredArchitecture<B, S>,
1622 P: LayerwisePolicy<B, A::Unit>,
1623 A::Error: std::fmt::Display,
1624 P::Error: std::fmt::Display,
1625{
1626 pub const fn new(architecture: A, policy: P) -> Self {
1628 Self {
1629 architecture,
1630 policy,
1631 executors: None,
1632 backend: std::marker::PhantomData,
1633 }
1634 }
1635
1636 pub const fn new_policy_first(policy: P, architecture: A) -> Self {
1640 Self::new(architecture, policy)
1641 }
1642
1643 pub const fn architecture(&self) -> &A {
1645 &self.architecture
1646 }
1647
1648 pub fn architecture_mut(&mut self) -> &mut A {
1650 &mut self.architecture
1651 }
1652
1653 pub const fn policy(&self) -> &P {
1655 &self.policy
1656 }
1657
1658 pub fn policy_mut(&mut self) -> &mut P {
1660 &mut self.policy
1661 }
1662
1663 pub fn forward<'a>(
1665 &mut self,
1666 input: A::Input<'a>,
1667 state: &mut S,
1668 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1669 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>> {
1670 self.forward_with_context_hook(input, state, context, |_, _, _| Ok(()))
1671 .map(|(output, _)| output)
1672 }
1673
1674 pub fn forward_with_context_hook<'a, H>(
1676 &mut self,
1677 input: A::Input<'a>,
1678 state: &mut S,
1679 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1680 hook: H,
1681 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1682 where
1683 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
1684 {
1685 self.forward_with_unit_executor_and_context_hook(
1686 input,
1687 state,
1688 context,
1689 |architecture, group, index, unit, hidden, state, forward, context| {
1690 architecture.forward_unit(group, index, unit, hidden, state, forward, context)
1691 },
1692 hook,
1693 )
1694 }
1695
1696 pub fn forward_with_unit_executor<'a, E>(
1702 &mut self,
1703 input: A::Input<'a>,
1704 state: &mut S,
1705 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1706 execute: E,
1707 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1708 where
1709 E: FnMut(
1710 &mut A,
1711 usize,
1712 usize,
1713 &mut A::Unit,
1714 &B::Tensor,
1715 &mut S,
1716 &mut A::ForwardContext,
1717 &<B::Tensor as eredu_nn::Tensor>::Context,
1718 ) -> Result<B::Tensor, A::Error>,
1719 {
1720 self.forward_with_unit_executor_and_context_hook(
1721 input,
1722 state,
1723 context,
1724 execute,
1725 |_, _, _| Ok(()),
1726 )
1727 .map(|(output, _)| output)
1728 }
1729
1730 pub fn forward_with_observer<'a, Observer>(
1732 &mut self,
1733 input: A::Input<'a>,
1734 state: &mut S,
1735 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1736 observer: &mut Observer,
1737 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1738 where
1739 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1740 {
1741 self.forward_with_observer_and_context(input, state, context, observer)
1742 .map(|(output, _)| output)
1743 }
1744
1745 pub fn forward_with_observer_and_context<'a, Observer>(
1748 &mut self,
1749 input: A::Input<'a>,
1750 state: &mut S,
1751 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1752 observer: &mut Observer,
1753 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1754 where
1755 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1756 {
1757 self.forward_with_unit_executor_and_observer_and_context(
1758 input,
1759 state,
1760 context,
1761 |architecture, group, index, unit, hidden, state, forward, context| {
1762 architecture.forward_unit(group, index, unit, hidden, state, forward, context)
1763 },
1764 observer,
1765 )
1766 }
1767
1768 pub fn forward_with_unit_executor_and_observer<'a, E, Observer>(
1770 &mut self,
1771 input: A::Input<'a>,
1772 state: &mut S,
1773 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1774 execute: E,
1775 observer: &mut Observer,
1776 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1777 where
1778 E: FnMut(
1779 &mut A,
1780 usize,
1781 usize,
1782 &mut A::Unit,
1783 &B::Tensor,
1784 &mut S,
1785 &mut A::ForwardContext,
1786 &<B::Tensor as eredu_nn::Tensor>::Context,
1787 ) -> Result<B::Tensor, A::Error>,
1788 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1789 {
1790 self.forward_with_unit_executor_and_observer_and_context(
1791 input, state, context, execute, observer,
1792 )
1793 .map(|(output, _)| output)
1794 }
1795
1796 pub fn forward_with_unit_executor_and_observer_and_context<'a, E, Observer>(
1798 &mut self,
1799 input: A::Input<'a>,
1800 state: &mut S,
1801 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1802 execute: E,
1803 observer: &mut Observer,
1804 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1805 where
1806 E: FnMut(
1807 &mut A,
1808 usize,
1809 usize,
1810 &mut A::Unit,
1811 &B::Tensor,
1812 &mut S,
1813 &mut A::ForwardContext,
1814 &<B::Tensor as eredu_nn::Tensor>::Context,
1815 ) -> Result<B::Tensor, A::Error>,
1816 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1817 {
1818 let graph = self
1819 .architecture
1820 .execution_graph()
1821 .map_err(LayerwiseRuntimeError::Architecture)?;
1822 let mut units = Vec::with_capacity(graph.groups().len());
1823 let mut group_inputs = Vec::with_capacity(graph.groups().len());
1824 let mut group_outputs = Vec::with_capacity(graph.groups().len());
1825 for group in 0..graph.groups().len() {
1826 let count = self
1827 .architecture
1828 .group_unit_count(group)
1829 .map_err(LayerwiseRuntimeError::Architecture)?;
1830 units.push(
1831 (0..count)
1832 .map(|index| self.architecture.unit_path(group, index))
1833 .collect::<Result<Vec<_>, _>>()
1834 .map_err(LayerwiseRuntimeError::Architecture)?,
1835 );
1836 group_inputs.push(
1837 self.architecture
1838 .group_input_observation_path(group)
1839 .map_err(LayerwiseRuntimeError::Architecture)?,
1840 );
1841 group_outputs.push(
1842 self.architecture
1843 .group_output_observation_path(group)
1844 .map_err(LayerwiseRuntimeError::Architecture)?,
1845 );
1846 }
1847 let observer = std::rc::Rc::new(std::cell::RefCell::new(observer));
1848 let mut hook = ActivationObserverTraversalHook {
1849 observer,
1850 units,
1851 group_inputs,
1852 group_outputs,
1853 };
1854 self.forward_with_unit_executor_and_traversal_hook(
1855 input, state, context, execute, &mut hook,
1856 )
1857 }
1858
1859 #[allow(clippy::too_many_arguments)]
1867 pub fn forward_with_provider_and_observer<'a, Provider, Observer>(
1868 &mut self,
1869 input: A::Input<'a>,
1870 state: &mut S,
1871 pass: ExpertPass,
1872 provider: &mut Provider,
1873 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1874 observer: &mut Observer,
1875 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1876 where
1877 B: eredu_nn::GroupedNeuralBackend,
1878 A: RoutedLayeredArchitecture<B, S>,
1879 A::Error: std::fmt::Display,
1880 Provider: RoutedExpertProvider<B>,
1881 Provider::Error: std::fmt::Display,
1882 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1883 {
1884 self.forward_with_provider_and_observer_and_context(
1885 input, state, pass, provider, context, observer,
1886 )
1887 .map(|(output, _)| output)
1888 }
1889
1890 #[allow(clippy::too_many_arguments)]
1892 pub fn forward_with_provider_and_observer_and_context<'a, Provider, Observer>(
1893 &mut self,
1894 input: A::Input<'a>,
1895 state: &mut S,
1896 pass: ExpertPass,
1897 provider: &mut Provider,
1898 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1899 observer: &mut Observer,
1900 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1901 where
1902 B: eredu_nn::GroupedNeuralBackend,
1903 A: RoutedLayeredArchitecture<B, S>,
1904 A::Error: std::fmt::Display,
1905 Provider: RoutedExpertProvider<B>,
1906 Provider::Error: std::fmt::Display,
1907 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1908 {
1909 let graph = self
1910 .architecture
1911 .execution_graph()
1912 .map_err(LayerwiseRuntimeError::Architecture)?;
1913 let mut units = Vec::with_capacity(graph.groups().len());
1914 let mut group_inputs = Vec::with_capacity(graph.groups().len());
1915 let mut group_outputs = Vec::with_capacity(graph.groups().len());
1916 for group in 0..graph.groups().len() {
1917 let count = self
1918 .architecture
1919 .group_unit_count(group)
1920 .map_err(LayerwiseRuntimeError::Architecture)?;
1921 units.push(
1922 (0..count)
1923 .map(|index| self.architecture.unit_path(group, index))
1924 .collect::<Result<Vec<_>, _>>()
1925 .map_err(LayerwiseRuntimeError::Architecture)?,
1926 );
1927 group_inputs.push(
1928 self.architecture
1929 .group_input_observation_path(group)
1930 .map_err(LayerwiseRuntimeError::Architecture)?,
1931 );
1932 group_outputs.push(
1933 self.architecture
1934 .group_output_observation_path(group)
1935 .map_err(LayerwiseRuntimeError::Architecture)?,
1936 );
1937 }
1938 let observer = std::rc::Rc::new(std::cell::RefCell::new(observer));
1939 let routed_observer = observer.clone();
1940 let mut hook = ActivationObserverTraversalHook {
1941 observer,
1942 units,
1943 group_inputs,
1944 group_outputs,
1945 };
1946 self.forward_with_unit_executor_and_traversal_hook(
1947 input,
1948 state,
1949 context,
1950 |architecture, group, index, unit, hidden, state, forward, context| {
1951 architecture.forward_unit_observed_with_provider(
1952 group,
1953 index,
1954 unit,
1955 hidden,
1956 state,
1957 forward,
1958 pass,
1959 provider,
1960 context,
1961 &mut **routed_observer.borrow_mut(),
1962 )
1963 },
1964 &mut hook,
1965 )
1966 }
1967
1968 #[allow(clippy::too_many_arguments)]
1971 pub fn forward_with_inferred_provider_and_observer<'a, Provider, Observer>(
1972 &mut self,
1973 input: A::Input<'a>,
1974 state: &mut S,
1975 provider: &mut Provider,
1976 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1977 observer: &mut Observer,
1978 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1979 where
1980 B: eredu_nn::GroupedNeuralBackend,
1981 A: RoutedLayeredArchitecture<B, S>,
1982 A::Error: std::fmt::Display,
1983 Provider: RoutedExpertProvider<B>,
1984 Provider::Error: std::fmt::Display,
1985 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1986 {
1987 self.forward_with_inferred_provider_and_observer_and_context(
1988 input, state, provider, context, observer,
1989 )
1990 .map(|(output, _)| output)
1991 }
1992
1993 #[allow(clippy::too_many_arguments)]
1996 pub fn forward_with_inferred_provider_and_observer_and_context<'a, Provider, Observer>(
1997 &mut self,
1998 input: A::Input<'a>,
1999 state: &mut S,
2000 provider: &mut Provider,
2001 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2002 observer: &mut Observer,
2003 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2004 where
2005 B: eredu_nn::GroupedNeuralBackend,
2006 A: RoutedLayeredArchitecture<B, S>,
2007 A::Error: std::fmt::Display,
2008 Provider: RoutedExpertProvider<B>,
2009 Provider::Error: std::fmt::Display,
2010 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2011 {
2012 let graph = self
2013 .architecture
2014 .execution_graph()
2015 .map_err(LayerwiseRuntimeError::Architecture)?;
2016 let mut units = Vec::with_capacity(graph.groups().len());
2017 let mut group_inputs = Vec::with_capacity(graph.groups().len());
2018 let mut group_outputs = Vec::with_capacity(graph.groups().len());
2019 for group in 0..graph.groups().len() {
2020 let count = self
2021 .architecture
2022 .group_unit_count(group)
2023 .map_err(LayerwiseRuntimeError::Architecture)?;
2024 units.push(
2025 (0..count)
2026 .map(|index| self.architecture.unit_path(group, index))
2027 .collect::<Result<Vec<_>, _>>()
2028 .map_err(LayerwiseRuntimeError::Architecture)?,
2029 );
2030 group_inputs.push(
2031 self.architecture
2032 .group_input_observation_path(group)
2033 .map_err(LayerwiseRuntimeError::Architecture)?,
2034 );
2035 group_outputs.push(
2036 self.architecture
2037 .group_output_observation_path(group)
2038 .map_err(LayerwiseRuntimeError::Architecture)?,
2039 );
2040 }
2041 let observer = std::rc::Rc::new(std::cell::RefCell::new(observer));
2042 let routed_observer = observer.clone();
2043 let mut hook = ActivationObserverTraversalHook {
2044 observer,
2045 units,
2046 group_inputs,
2047 group_outputs,
2048 };
2049 self.forward_with_unit_executor_and_traversal_hook(
2050 input,
2051 state,
2052 context,
2053 |architecture, group, index, unit, hidden, state, forward, context| {
2054 architecture.forward_unit_observed_with_inferred_provider(
2055 group,
2056 index,
2057 unit,
2058 hidden,
2059 state,
2060 forward,
2061 provider,
2062 context,
2063 &mut **routed_observer.borrow_mut(),
2064 )
2065 },
2066 &mut hook,
2067 )
2068 }
2069
2070 pub fn forward_with_unit_executor_and_context_hook<'a, E, H>(
2072 &mut self,
2073 input: A::Input<'a>,
2074 state: &mut S,
2075 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2076 execute: E,
2077 mut hook: H,
2078 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2079 where
2080 E: FnMut(
2081 &mut A,
2082 usize,
2083 usize,
2084 &mut A::Unit,
2085 &B::Tensor,
2086 &mut S,
2087 &mut A::ForwardContext,
2088 &<B::Tensor as eredu_nn::Tensor>::Context,
2089 ) -> Result<B::Tensor, A::Error>,
2090 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
2091 {
2092 self.forward_with_unit_executor_and_activation_hook(
2093 input,
2094 state,
2095 context,
2096 execute,
2097 |group, index, _hidden, forward| hook(group, index, forward),
2098 )
2099 }
2100
2101 pub fn forward_with_unit_executor_and_activation_hook<'a, E, H>(
2108 &mut self,
2109 input: A::Input<'a>,
2110 state: &mut S,
2111 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2112 execute: E,
2113 hook: H,
2114 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2115 where
2116 E: FnMut(
2117 &mut A,
2118 usize,
2119 usize,
2120 &mut A::Unit,
2121 &B::Tensor,
2122 &mut S,
2123 &mut A::ForwardContext,
2124 &<B::Tensor as eredu_nn::Tensor>::Context,
2125 ) -> Result<B::Tensor, A::Error>,
2126 H: FnMut(usize, usize, &B::Tensor, &mut A::ForwardContext) -> Result<(), A::Error>,
2127 {
2128 self.forward_with_unit_executor_and_traversal_hook(
2129 input,
2130 state,
2131 context,
2132 execute,
2133 &mut AfterUnitTraversalHook { after_unit: hook },
2134 )
2135 }
2136
2137 pub fn forward_with_traversal_hook<'a, H>(
2139 &mut self,
2140 input: A::Input<'a>,
2141 state: &mut S,
2142 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2143 hook: &mut H,
2144 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2145 where
2146 H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
2147 {
2148 self.forward_with_unit_executor_and_traversal_hook(
2149 input,
2150 state,
2151 context,
2152 |architecture, group, index, unit, hidden, state, forward, context| {
2153 architecture.forward_unit(group, index, unit, hidden, state, forward, context)
2154 },
2155 hook,
2156 )
2157 }
2158
2159 pub fn forward_with_unit_executor_and_traversal_hook<'a, E, H>(
2161 &mut self,
2162 input: A::Input<'a>,
2163 state: &mut S,
2164 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2165 mut execute: E,
2166 hook: &mut H,
2167 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2168 where
2169 E: FnMut(
2170 &mut A,
2171 usize,
2172 usize,
2173 &mut A::Unit,
2174 &B::Tensor,
2175 &mut S,
2176 &mut A::ForwardContext,
2177 &<B::Tensor as eredu_nn::Tensor>::Context,
2178 ) -> Result<B::Tensor, A::Error>,
2179 H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
2180 {
2181 let graph = self
2182 .architecture
2183 .execution_graph()
2184 .map_err(LayerwiseRuntimeError::Architecture)?;
2185 let counts = (0..graph.groups().len())
2186 .map(|group| {
2187 self.architecture
2188 .group_unit_count(group)
2189 .map_err(LayerwiseRuntimeError::Architecture)
2190 })
2191 .collect::<Result<Vec<_>, _>>()?;
2192 let layout = ExecutionUnitLayout::new(&graph, counts)?;
2193 if self.executors.as_ref().map(Vec::len) != Some(graph.groups().len()) {
2194 self.executors = Some(
2195 B::fork_executors(context, graph.groups().len())
2196 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
2197 );
2198 }
2199 let executors = self
2200 .executors
2201 .as_ref()
2202 .expect("layered runtime initialized its executor cache");
2203 let forward = self
2204 .architecture
2205 .begin_forward(input, state, context)
2206 .map_err(LayerwiseRuntimeError::Architecture)?;
2207 let initial_completion = (graph.groups().len() > 1)
2208 .then(|| B::submit(context, [&forward.hidden]))
2209 .transpose()
2210 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2211 let mut policy = LayerwisePolicyForward::begin(&mut self.policy, &forward.hidden, context)
2212 .map_err(LayerwiseRuntimeError::Policy)?;
2213 let initial = forward.hidden;
2214 let mut forward_context = forward.context;
2215 let mut schedule = ExecutionGroupSchedule::new(&graph);
2216 let mut outputs: Vec<Option<B::Tensor>> = vec![None; graph.groups().len()];
2217 let mut completions: Vec<Option<B::Completion>> =
2218 (0..graph.groups().len()).map(|_| None).collect();
2219 for &group in graph.execution_order() {
2220 let executor = std::borrow::Borrow::borrow(&executors[group]);
2221 let group_dependencies = schedule
2222 .dependencies(group)
2223 .expect("validated execution order contains a known group");
2224 if group_dependencies.is_empty() {
2225 if let Some(completion) = &initial_completion {
2226 B::order_after(completion, executor)
2227 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2228 }
2229 }
2230 for &dependency in group_dependencies {
2231 B::order_after(
2232 completions[dependency]
2233 .as_ref()
2234 .expect("topological dependency has a completion"),
2235 executor,
2236 )
2237 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2238 }
2239 let dependencies = schedule
2240 .dependencies(group)
2241 .expect("validated execution order contains a known group")
2242 .iter()
2243 .map(|&dependency| {
2244 outputs[dependency]
2245 .as_ref()
2246 .expect("topological dependency has completed")
2247 .clone()
2248 })
2249 .collect::<Vec<_>>();
2250 let dependency_refs = dependencies.iter().collect::<Vec<_>>();
2251 let mut hidden = self
2252 .architecture
2253 .begin_execution_group(
2254 group,
2255 &initial,
2256 &dependency_refs,
2257 state,
2258 &mut forward_context,
2259 executor,
2260 )
2261 .map_err(LayerwiseRuntimeError::Architecture)?;
2262 hook.after_group_begin(group, &mut hidden, &mut forward_context, executor)
2263 .map_err(LayerwiseRuntimeError::Architecture)?;
2264 for dependency in schedule
2265 .started(group)
2266 .expect("topological execution starts only ready groups")
2267 {
2268 outputs[dependency] = None;
2269 }
2270 if self
2271 .architecture
2272 .should_execute_group(group, &forward_context)
2273 {
2274 let unit_count = layout
2275 .group_range(group)
2276 .expect("layout covers every graph group")
2277 .len();
2278 for index in 0..unit_count {
2279 if hook
2280 .before_unit(
2281 group,
2282 index,
2283 unit_count - index,
2284 &mut hidden,
2285 &mut forward_context,
2286 executor,
2287 )
2288 .map_err(LayerwiseRuntimeError::Architecture)?
2289 == LayeredUnitAction::SkipRemainingGroup
2290 {
2291 break;
2292 }
2293 let ordinal = layout
2294 .ordinal(group, index)
2295 .expect("group-local unit belongs to the layout");
2296 let address = layout
2297 .address(ordinal)
2298 .expect("group-local unit has a stable policy address");
2299 let lease = policy
2300 .acquire(ordinal, address, |executor| {
2301 self.architecture.build_unit(group, index, executor)
2302 })
2303 .map_err(|error| match error {
2304 LayerwiseAcquireError::Architecture(error) => {
2305 LayerwiseRuntimeError::Architecture(error)
2306 }
2307 LayerwiseAcquireError::Policy(error) => {
2308 LayerwiseRuntimeError::Policy(error)
2309 }
2310 })?;
2311 hidden = execute(
2312 &mut self.architecture,
2313 group,
2314 index,
2315 lease,
2316 &hidden,
2317 state,
2318 &mut forward_context,
2319 executor,
2320 )
2321 .map_err(LayerwiseRuntimeError::Architecture)?;
2322 hook.after_unit(group, index, &mut hidden, &mut forward_context, executor)
2323 .map_err(LayerwiseRuntimeError::Architecture)?;
2324 let mut state_values = Vec::new();
2325 for state_ordinal in self
2326 .architecture
2327 .retained_state_ordinals(group, index, ordinal)
2328 {
2329 state_values.extend(
2330 state
2331 .retained_values(state_ordinal, address.with_index(state_ordinal))
2332 .map_err(LayerwiseRuntimeError::State)?,
2333 );
2334 }
2335 let context_values =
2336 self.architecture
2337 .retained_context_values(&forward_context, group, index);
2338 policy
2339 .complete(&hidden, state_values.into_iter(), context_values)
2340 .map_err(LayerwiseRuntimeError::Policy)?;
2341 }
2342 }
2343 hidden = self
2344 .architecture
2345 .complete_execution_group(group, &hidden, state, &mut forward_context, executor)
2346 .map_err(LayerwiseRuntimeError::Architecture)?;
2347 hook.after_group(group, &mut hidden, &mut forward_context, executor)
2348 .map_err(LayerwiseRuntimeError::Architecture)?;
2349 outputs[group] = Some(hidden);
2350 if graph.groups().len() > 1 {
2351 completions[group] = Some(
2352 B::submit(
2353 executor,
2354 [outputs[group]
2355 .as_ref()
2356 .expect("group output was stored before submission")],
2357 )
2358 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
2359 );
2360 }
2361 schedule
2362 .ordered(group)
2363 .expect("started group can be ordered exactly once");
2364 }
2365 let hidden = outputs[graph.output()]
2366 .take()
2367 .expect("validated graph output completed");
2368 if let Some(completion) = &completions[graph.output()] {
2369 B::order_after(completion, context)
2370 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2371 }
2372 let output = self
2373 .architecture
2374 .finish_forward(&hidden, state, &forward_context, context)
2375 .map_err(LayerwiseRuntimeError::Architecture)?;
2376 policy
2377 .finish(&output)
2378 .map_err(LayerwiseRuntimeError::Policy)?;
2379 Ok((output, forward_context))
2380 }
2381
2382 pub fn forward_parallel<'a>(
2384 &mut self,
2385 input: A::Input<'a>,
2386 state: &mut S,
2387 parallel: &B::ParallelContext,
2388 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2389 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2390 where
2391 A: ParallelLayeredArchitecture<B, S>,
2392 {
2393 self.forward_parallel_with_context_hook(input, state, parallel, context, |_, _, _| Ok(()))
2394 .map(|(output, _)| output)
2395 }
2396
2397 pub fn forward_parallel_with_context_hook<'a, H>(
2399 &mut self,
2400 input: A::Input<'a>,
2401 state: &mut S,
2402 parallel: &B::ParallelContext,
2403 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2404 hook: H,
2405 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2406 where
2407 A: ParallelLayeredArchitecture<B, S>,
2408 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
2409 {
2410 self.forward_parallel_with_unit_executor_and_traversal_hook(
2411 input,
2412 state,
2413 parallel,
2414 context,
2415 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2416 architecture.forward_unit_parallel(
2417 group, index, unit, hidden, state, forward, parallel, context,
2418 )
2419 },
2420 &mut AfterUnitContextTraversalHook { after_unit: hook },
2421 )
2422 }
2423
2424 pub fn forward_parallel_with_unit_executor<'a, E>(
2426 &mut self,
2427 input: A::Input<'a>,
2428 state: &mut S,
2429 parallel: &B::ParallelContext,
2430 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2431 execute: E,
2432 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2433 where
2434 A: ParallelLayeredArchitecture<B, S>,
2435 E: FnMut(
2436 &mut A,
2437 usize,
2438 usize,
2439 &mut A::Unit,
2440 &B::Tensor,
2441 &mut S,
2442 &mut A::ForwardContext,
2443 &B::ParallelContext,
2444 &<B::Tensor as eredu_nn::Tensor>::Context,
2445 ) -> Result<B::Tensor, A::Error>,
2446 {
2447 self.forward_parallel_with_unit_executor_and_context_hook(
2448 input,
2449 state,
2450 parallel,
2451 context,
2452 execute,
2453 |_, _, _| Ok(()),
2454 )
2455 .map(|(output, _)| output)
2456 }
2457
2458 pub fn forward_parallel_with_observer<'a, Observer>(
2460 &mut self,
2461 input: A::Input<'a>,
2462 state: &mut S,
2463 parallel: &B::ParallelContext,
2464 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2465 observer: &mut Observer,
2466 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2467 where
2468 A: ParallelLayeredArchitecture<B, S>,
2469 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2470 {
2471 self.forward_parallel_with_unit_executor_and_observer(
2472 input,
2473 state,
2474 parallel,
2475 context,
2476 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2477 architecture.forward_unit_parallel(
2478 group, index, unit, hidden, state, forward, parallel, context,
2479 )
2480 },
2481 observer,
2482 )
2483 }
2484
2485 pub fn forward_parallel_with_unit_executor_and_observer<'a, E, Observer>(
2487 &mut self,
2488 input: A::Input<'a>,
2489 state: &mut S,
2490 parallel: &B::ParallelContext,
2491 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2492 mut execute: E,
2493 observer: &mut Observer,
2494 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2495 where
2496 A: ParallelLayeredArchitecture<B, S>,
2497 E: FnMut(
2498 &mut A,
2499 usize,
2500 usize,
2501 &mut A::Unit,
2502 &B::Tensor,
2503 &mut S,
2504 &mut A::ForwardContext,
2505 &B::ParallelContext,
2506 &<B::Tensor as eredu_nn::Tensor>::Context,
2507 ) -> Result<B::Tensor, A::Error>,
2508 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2509 {
2510 self.forward_parallel_with_unit_executor(
2511 input,
2512 state,
2513 parallel,
2514 context,
2515 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2516 let path = architecture.unit_path(group, index)?;
2517 let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
2518 let output = execute(
2519 architecture,
2520 group,
2521 index,
2522 unit,
2523 &input,
2524 state,
2525 forward,
2526 parallel,
2527 context,
2528 )?;
2529 observe_and_intervene(observer, &format!("{path}.output"), &output)
2530 },
2531 )
2532 }
2533
2534 #[allow(clippy::too_many_arguments)]
2536 pub fn forward_parallel_with_provider_and_observer<'a, Provider, Observer>(
2537 &mut self,
2538 input: A::Input<'a>,
2539 state: &mut S,
2540 pass: ExpertPass,
2541 provider: &mut Provider,
2542 parallel: &B::ParallelContext,
2543 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2544 observer: &mut Observer,
2545 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2546 where
2547 B: eredu_nn::GroupedNeuralBackend,
2548 A: ParallelRoutedLayeredArchitecture<B, S>,
2549 Provider: crate::TensorParallelRoutedExpertProvider<B>,
2550 Provider::Error: std::fmt::Display,
2551 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2552 {
2553 self.forward_parallel_with_unit_executor(
2554 input,
2555 state,
2556 parallel,
2557 context,
2558 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2559 let path = architecture.unit_path(group, index)?;
2560 let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
2561 let output = match architecture.routed_observation_point(group, index)? {
2562 Some(point) => {
2563 let mut observed = ObservedExpertProvider::new(provider, observer, point);
2564 architecture.forward_unit_parallel_with_provider(
2565 group,
2566 index,
2567 unit,
2568 &input,
2569 state,
2570 forward,
2571 pass,
2572 &mut observed,
2573 parallel,
2574 context,
2575 )
2576 }
2577 None => architecture.forward_unit_parallel_with_provider(
2578 group, index, unit, &input, state, forward, pass, provider, parallel,
2579 context,
2580 ),
2581 }?;
2582 observe_and_intervene(observer, &format!("{path}.output"), &output)
2583 },
2584 )
2585 }
2586
2587 pub fn forward_parallel_with_unit_executor_and_context_hook<'a, E, H>(
2589 &mut self,
2590 input: A::Input<'a>,
2591 state: &mut S,
2592 parallel: &B::ParallelContext,
2593 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2594 execute: E,
2595 hook: H,
2596 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2597 where
2598 A: ParallelLayeredArchitecture<B, S>,
2599 E: FnMut(
2600 &mut A,
2601 usize,
2602 usize,
2603 &mut A::Unit,
2604 &B::Tensor,
2605 &mut S,
2606 &mut A::ForwardContext,
2607 &B::ParallelContext,
2608 &<B::Tensor as eredu_nn::Tensor>::Context,
2609 ) -> Result<B::Tensor, A::Error>,
2610 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
2611 {
2612 self.forward_parallel_with_unit_executor_and_traversal_hook(
2613 input,
2614 state,
2615 parallel,
2616 context,
2617 execute,
2618 &mut AfterUnitContextTraversalHook { after_unit: hook },
2619 )
2620 }
2621
2622 pub fn forward_parallel_with_traversal_hook<'a, H>(
2624 &mut self,
2625 input: A::Input<'a>,
2626 state: &mut S,
2627 parallel: &B::ParallelContext,
2628 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2629 hook: &mut H,
2630 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2631 where
2632 A: ParallelLayeredArchitecture<B, S>,
2633 H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
2634 {
2635 self.forward_parallel_with_unit_executor_and_traversal_hook(
2636 input,
2637 state,
2638 parallel,
2639 context,
2640 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2641 architecture.forward_unit_parallel(
2642 group, index, unit, hidden, state, forward, parallel, context,
2643 )
2644 },
2645 hook,
2646 )
2647 }
2648
2649 pub fn forward_parallel_with_unit_executor_and_traversal_hook<'a, E, H>(
2651 &mut self,
2652 input: A::Input<'a>,
2653 state: &mut S,
2654 parallel: &B::ParallelContext,
2655 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2656 mut execute: E,
2657 hook: &mut H,
2658 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2659 where
2660 A: ParallelLayeredArchitecture<B, S>,
2661 E: FnMut(
2662 &mut A,
2663 usize,
2664 usize,
2665 &mut A::Unit,
2666 &B::Tensor,
2667 &mut S,
2668 &mut A::ForwardContext,
2669 &B::ParallelContext,
2670 &<B::Tensor as eredu_nn::Tensor>::Context,
2671 ) -> Result<B::Tensor, A::Error>,
2672 H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
2673 {
2674 let graph = self
2675 .architecture
2676 .execution_graph()
2677 .map_err(LayerwiseRuntimeError::Architecture)?;
2678 let counts = (0..graph.groups().len())
2679 .map(|group| {
2680 self.architecture
2681 .group_unit_count(group)
2682 .map_err(LayerwiseRuntimeError::Architecture)
2683 })
2684 .collect::<Result<Vec<_>, _>>()?;
2685 let layout = ExecutionUnitLayout::new(&graph, counts)?;
2686 if self.executors.as_ref().map(Vec::len) != Some(graph.groups().len()) {
2687 self.executors = Some(
2688 B::fork_executors(context, graph.groups().len())
2689 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
2690 );
2691 }
2692 let executors = self
2693 .executors
2694 .as_ref()
2695 .expect("layered runtime initialized its executor cache");
2696 let forward = self
2697 .architecture
2698 .begin_forward_parallel(input, state, parallel, context)
2699 .map_err(LayerwiseRuntimeError::Architecture)?;
2700 let initial_completion = (graph.groups().len() > 1)
2701 .then(|| B::submit(context, [&forward.hidden]))
2702 .transpose()
2703 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2704 let mut policy = LayerwisePolicyForward::begin(&mut self.policy, &forward.hidden, context)
2705 .map_err(LayerwiseRuntimeError::Policy)?;
2706 let initial = forward.hidden;
2707 let mut forward_context = forward.context;
2708 let mut schedule = ExecutionGroupSchedule::new(&graph);
2709 let mut outputs: Vec<Option<B::Tensor>> = vec![None; graph.groups().len()];
2710 let mut completions: Vec<Option<B::Completion>> =
2711 (0..graph.groups().len()).map(|_| None).collect();
2712 for &group in graph.execution_order() {
2713 let executor = std::borrow::Borrow::borrow(&executors[group]);
2714 let group_dependencies = schedule
2715 .dependencies(group)
2716 .expect("validated execution order contains a known group");
2717 if group_dependencies.is_empty() {
2718 if let Some(completion) = &initial_completion {
2719 B::order_after(completion, executor)
2720 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2721 }
2722 }
2723 for &dependency in group_dependencies {
2724 B::order_after(
2725 completions[dependency]
2726 .as_ref()
2727 .expect("topological dependency has a completion"),
2728 executor,
2729 )
2730 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2731 }
2732 let dependencies = schedule
2733 .dependencies(group)
2734 .expect("validated execution order contains a known group")
2735 .iter()
2736 .map(|&dependency| {
2737 outputs[dependency]
2738 .as_ref()
2739 .expect("topological dependency has completed")
2740 .clone()
2741 })
2742 .collect::<Vec<_>>();
2743 let dependency_refs = dependencies.iter().collect::<Vec<_>>();
2744 let mut hidden = self
2745 .architecture
2746 .begin_execution_group_parallel(
2747 group,
2748 &initial,
2749 &dependency_refs,
2750 state,
2751 &mut forward_context,
2752 parallel,
2753 executor,
2754 )
2755 .map_err(LayerwiseRuntimeError::Architecture)?;
2756 hook.after_group_begin(group, &mut hidden, &mut forward_context, executor)
2757 .map_err(LayerwiseRuntimeError::Architecture)?;
2758 for dependency in schedule
2759 .started(group)
2760 .expect("topological execution starts only ready groups")
2761 {
2762 outputs[dependency] = None;
2763 }
2764 if self
2765 .architecture
2766 .should_execute_group(group, &forward_context)
2767 {
2768 let unit_count = layout
2769 .group_range(group)
2770 .expect("layout covers every graph group")
2771 .len();
2772 for index in 0..unit_count {
2773 if hook
2774 .before_unit(
2775 group,
2776 index,
2777 unit_count - index,
2778 &mut hidden,
2779 &mut forward_context,
2780 executor,
2781 )
2782 .map_err(LayerwiseRuntimeError::Architecture)?
2783 == LayeredUnitAction::SkipRemainingGroup
2784 {
2785 break;
2786 }
2787 let ordinal = layout
2788 .ordinal(group, index)
2789 .expect("group-local unit belongs to the layout");
2790 let address = layout
2791 .address(ordinal)
2792 .expect("group-local unit has a stable policy address");
2793 let lease = policy
2794 .acquire(ordinal, address, |executor| {
2795 self.architecture.build_unit(group, index, executor)
2796 })
2797 .map_err(|error| match error {
2798 LayerwiseAcquireError::Architecture(error) => {
2799 LayerwiseRuntimeError::Architecture(error)
2800 }
2801 LayerwiseAcquireError::Policy(error) => {
2802 LayerwiseRuntimeError::Policy(error)
2803 }
2804 })?;
2805 hidden = execute(
2806 &mut self.architecture,
2807 group,
2808 index,
2809 lease,
2810 &hidden,
2811 state,
2812 &mut forward_context,
2813 parallel,
2814 executor,
2815 )
2816 .map_err(LayerwiseRuntimeError::Architecture)?;
2817 hook.after_unit(group, index, &mut hidden, &mut forward_context, executor)
2818 .map_err(LayerwiseRuntimeError::Architecture)?;
2819 let mut state_values = Vec::new();
2820 for state_ordinal in self
2821 .architecture
2822 .retained_state_ordinals(group, index, ordinal)
2823 {
2824 state_values.extend(
2825 state
2826 .retained_values(state_ordinal, address.with_index(state_ordinal))
2827 .map_err(LayerwiseRuntimeError::State)?,
2828 );
2829 }
2830 let context_values =
2831 self.architecture
2832 .retained_context_values(&forward_context, group, index);
2833 policy
2834 .complete(&hidden, state_values.into_iter(), context_values)
2835 .map_err(LayerwiseRuntimeError::Policy)?;
2836 }
2837 }
2838 hidden = self
2839 .architecture
2840 .complete_execution_group_parallel(
2841 group,
2842 &hidden,
2843 state,
2844 &mut forward_context,
2845 parallel,
2846 executor,
2847 )
2848 .map_err(LayerwiseRuntimeError::Architecture)?;
2849 hook.after_group(group, &mut hidden, &mut forward_context, executor)
2850 .map_err(LayerwiseRuntimeError::Architecture)?;
2851 outputs[group] = Some(hidden);
2852 if graph.groups().len() > 1 {
2853 completions[group] = Some(
2854 B::submit(
2855 executor,
2856 [outputs[group]
2857 .as_ref()
2858 .expect("group output was stored before submission")],
2859 )
2860 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
2861 );
2862 }
2863 schedule
2864 .ordered(group)
2865 .expect("started group can be ordered exactly once");
2866 }
2867 let hidden = outputs[graph.output()]
2868 .take()
2869 .expect("validated graph output completed");
2870 if let Some(completion) = &completions[graph.output()] {
2871 B::order_after(completion, context)
2872 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2873 }
2874 let output = self
2875 .architecture
2876 .finish_forward_parallel(&hidden, state, &forward_context, parallel, context)
2877 .map_err(LayerwiseRuntimeError::Architecture)?;
2878 policy
2879 .finish(&output)
2880 .map_err(LayerwiseRuntimeError::Policy)?;
2881 Ok((output, forward_context))
2882 }
2883}
2884
2885pub struct ResidentUnitLease<U> {
2887 index: usize,
2888 unit: U,
2889}
2890
2891impl<U> std::ops::Deref for ResidentUnitLease<U> {
2892 type Target = U;
2893
2894 fn deref(&self) -> &Self::Target {
2895 &self.unit
2896 }
2897}
2898
2899impl<U> std::ops::DerefMut for ResidentUnitLease<U> {
2900 fn deref_mut(&mut self) -> &mut Self::Target {
2901 &mut self.unit
2902 }
2903}
2904
2905pub struct ResidentUnitWindow<U> {
2907 units: Vec<Option<U>>,
2908}
2909
2910impl<U> ResidentUnitWindow<U> {
2911 pub fn new(units: Vec<U>) -> Self {
2913 Self {
2914 units: units.into_iter().map(Some).collect(),
2915 }
2916 }
2917}
2918
2919impl<B, U> LayerwisePolicy<B, U> for ResidentUnitWindow<U>
2920where
2921 B: NeuralBackend,
2922{
2923 type Lease = ResidentUnitLease<U>;
2924 type Error = ResidentUnitWindowError;
2925
2926 fn begin(
2927 &mut self,
2928 _initial: &B::Tensor,
2929 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2930 ) -> Result<(), Self::Error> {
2931 Ok(())
2932 }
2933
2934 fn abort(
2935 &mut self,
2936 active: Option<(usize, crate::ExecutionUnitAddress, Self::Lease)>,
2937 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2938 ) {
2939 let Some((ordinal, _, lease)) = active else {
2940 return;
2941 };
2942 debug_assert_eq!(lease.index, ordinal);
2943 if let Some(slot) = self.units.get_mut(lease.index) {
2944 debug_assert!(slot.is_none());
2945 if slot.is_none() {
2946 *slot = Some(lease.unit);
2947 }
2948 }
2949 }
2950
2951 fn acquire<E, F>(
2952 &mut self,
2953 index: usize,
2954 _address: crate::ExecutionUnitAddress,
2955 _build: F,
2956 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2957 ) -> Result<Self::Lease, LayerwiseAcquireError<E, Self::Error>>
2958 where
2959 F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>,
2960 {
2961 let count = self.units.len();
2962 let unit = self
2963 .units
2964 .get_mut(index)
2965 .ok_or(ResidentUnitWindowError::UnknownUnit { index, count })
2966 .map_err(LayerwiseAcquireError::Policy)?
2967 .take()
2968 .ok_or(ResidentUnitWindowError::AlreadyAcquired { index })
2969 .map_err(LayerwiseAcquireError::Policy)?;
2970 Ok(ResidentUnitLease { index, unit })
2971 }
2972
2973 fn complete<'a, StateValues, ContextValues>(
2974 &mut self,
2975 index: usize,
2976 _address: crate::ExecutionUnitAddress,
2977 lease: Self::Lease,
2978 _output: &'a B::Tensor,
2979 _state_values: StateValues,
2980 _context_values: ContextValues,
2981 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2982 ) -> Result<(), Self::Error>
2983 where
2984 B::Tensor: 'a,
2985 StateValues: Iterator<Item = &'a B::Tensor>,
2986 ContextValues: Iterator<Item = &'a B::Tensor>,
2987 {
2988 if lease.index != index {
2989 return Err(ResidentUnitWindowError::MismatchedUnit {
2990 expected: index,
2991 actual: lease.index,
2992 });
2993 }
2994 let slot = self
2995 .units
2996 .get_mut(index)
2997 .expect("acquired unit index remains in the window");
2998 if slot.replace(lease.unit).is_some() {
2999 return Err(ResidentUnitWindowError::AlreadyResident { index });
3000 }
3001 Ok(())
3002 }
3003
3004 fn finish(
3005 &mut self,
3006 _output: &B::Tensor,
3007 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
3008 ) -> Result<(), Self::Error> {
3009 Ok(())
3010 }
3011}
3012
3013#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
3015pub enum ResidentUnitWindowError {
3016 #[error("unit {index} is outside the {count}-unit window")]
3018 UnknownUnit {
3019 index: usize,
3021 count: usize,
3023 },
3024 #[error("unit {index} is already acquired")]
3026 AlreadyAcquired {
3027 index: usize,
3029 },
3030 #[error("unit completion expected {expected}, received {actual}")]
3032 MismatchedUnit {
3033 expected: usize,
3035 actual: usize,
3037 },
3038 #[error("unit {index} is already resident")]
3040 AlreadyResident {
3041 index: usize,
3043 },
3044}
3045
3046#[cfg(test)]
3047mod tests {
3048 use super::{ArchitectureGroupKind, LayeredPipelineSchedule, LayeredPipelineScheduleError};
3049 use crate::{ExecutionGraph, ExecutionGroupSpec, ExecutionScheduleError};
3050
3051 fn pipeline_graph() -> ExecutionGraph {
3052 ExecutionGraph::new(
3053 vec![
3054 ExecutionGroupSpec::root("vision"),
3055 ExecutionGroupSpec::root("audio"),
3056 ExecutionGroupSpec::with_dependencies("projector", ["vision"]),
3057 ExecutionGroupSpec::with_dependencies("merge", ["projector", "audio"]),
3058 ExecutionGroupSpec::with_dependencies("decoder", ["merge"]),
3059 ExecutionGroupSpec::with_dependencies("prediction", ["decoder"]),
3060 ],
3061 "prediction",
3062 )
3063 .unwrap()
3064 }
3065
3066 #[test]
3067 fn pipeline_schedule_owns_activity_propagation_and_ready_batches() {
3068 let graph = pipeline_graph();
3069 let contracts = [
3070 (ArchitectureGroupKind::VisionEncoder, true),
3071 (ArchitectureGroupKind::AudioEncoder, true),
3072 (ArchitectureGroupKind::Projector, false),
3073 (ArchitectureGroupKind::Merger, false),
3074 (ArchitectureGroupKind::Decoder, false),
3075 (ArchitectureGroupKind::Prediction, false),
3076 ];
3077 let mut queried = Vec::new();
3078 let mut schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |group| {
3079 queried.push(group);
3080 Ok::<_, LayeredPipelineScheduleError>(group == 0)
3081 })
3082 .unwrap();
3083
3084 assert_eq!(queried, [0, 1]);
3085 assert_eq!(schedule.activity(), [true, false, true, true, true, false]);
3086 assert_eq!(schedule.compatible_batch(|_, _| true), [0, 1]);
3087 schedule.started(0).unwrap();
3088 schedule.started(1).unwrap();
3089 schedule.ordered(0).unwrap();
3090 schedule.ordered(1).unwrap();
3091 assert_eq!(schedule.ready_groups().collect::<Vec<_>>(), [2]);
3092 for group in [2, 3, 4, 5] {
3093 schedule.started(group).unwrap();
3094 schedule.ordered(group).unwrap();
3095 }
3096 assert!(schedule.is_complete());
3097 }
3098
3099 #[test]
3100 fn pipeline_schedule_rejects_kind_and_transition_drift() {
3101 let graph = pipeline_graph();
3102 let error = LayeredPipelineSchedule::try_new(
3103 &graph,
3104 [(ArchitectureGroupKind::Decoder, false)],
3105 |_| Ok::<_, LayeredPipelineScheduleError>(true),
3106 )
3107 .unwrap_err();
3108 assert_eq!(
3109 error,
3110 LayeredPipelineScheduleError::GroupContractCount {
3111 graph: 6,
3112 declared: 1,
3113 }
3114 );
3115
3116 let contracts = [
3117 (ArchitectureGroupKind::VisionEncoder, true),
3118 (ArchitectureGroupKind::AudioEncoder, true),
3119 (ArchitectureGroupKind::Projector, false),
3120 (ArchitectureGroupKind::Merger, false),
3121 (ArchitectureGroupKind::Decoder, false),
3122 (ArchitectureGroupKind::Prediction, false),
3123 ];
3124 let mut schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |_| {
3125 Ok::<_, LayeredPipelineScheduleError>(true)
3126 })
3127 .unwrap();
3128 assert_eq!(
3129 schedule.started(2),
3130 Err(LayeredPipelineScheduleError::Transition(
3131 ExecutionScheduleError::DependenciesPending { group: 2 }
3132 ))
3133 );
3134 }
3135
3136 #[test]
3137 fn pipeline_schedule_consumes_declared_request_optionality() {
3138 let graph = ExecutionGraph::new(
3139 vec![
3140 ExecutionGroupSpec::root("mandatory_vision"),
3141 ExecutionGroupSpec::root("optional_audio"),
3142 ExecutionGroupSpec::with_dependencies(
3143 "decoder",
3144 ["mandatory_vision", "optional_audio"],
3145 ),
3146 ],
3147 "decoder",
3148 )
3149 .unwrap();
3150 let contracts = [
3151 (ArchitectureGroupKind::VisionEncoder, false),
3152 (ArchitectureGroupKind::AudioEncoder, true),
3153 (ArchitectureGroupKind::Decoder, false),
3154 ];
3155 let mut queried = Vec::new();
3156 let schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |group| {
3157 queried.push(group);
3158 Ok::<_, LayeredPipelineScheduleError>(false)
3159 })
3160 .unwrap();
3161
3162 assert_eq!(queried, [1]);
3163 assert_eq!(schedule.activity(), [true, false, true]);
3164
3165 let invalid = [
3166 (ArchitectureGroupKind::VisionEncoder, false),
3167 (ArchitectureGroupKind::AudioEncoder, false),
3168 (ArchitectureGroupKind::Decoder, true),
3169 ];
3170 assert_eq!(
3171 LayeredPipelineSchedule::try_new(&graph, invalid, |_| {
3172 Ok::<_, LayeredPipelineScheduleError>(true)
3173 })
3174 .unwrap_err(),
3175 LayeredPipelineScheduleError::InvalidRequestOptionalGroup {
3176 group: 2,
3177 kind: ArchitectureGroupKind::Decoder,
3178 }
3179 );
3180 }
3181}