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};
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: &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_unit(
371 &mut self,
372 _group: usize,
373 _index: usize,
374 _value: &B::Tensor,
375 _forward: &mut C,
376 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
377 ) -> Result<(), E> {
378 Ok(())
379 }
380
381 fn after_group(
383 &mut self,
384 _group: usize,
385 _value: &B::Tensor,
386 _forward: &mut C,
387 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
388 ) -> Result<(), E> {
389 Ok(())
390 }
391}
392
393pub struct CompositeLayeredTraversalHook<L, R> {
399 left: L,
400 right: R,
401}
402
403impl<L, R> CompositeLayeredTraversalHook<L, R> {
404 pub const fn new(left: L, right: R) -> Self {
406 Self { left, right }
407 }
408
409 pub fn into_parts(self) -> (L, R) {
411 (self.left, self.right)
412 }
413}
414
415impl<B, C, E, L, R> LayeredTraversalHook<B, C, E> for CompositeLayeredTraversalHook<L, R>
416where
417 B: NeuralBackend,
418 L: LayeredTraversalHook<B, C, E>,
419 R: LayeredTraversalHook<B, C, E>,
420{
421 fn before_unit(
422 &mut self,
423 group: usize,
424 index: usize,
425 remaining_units: usize,
426 value: &B::Tensor,
427 forward: &mut C,
428 context: &<B::Tensor as eredu_nn::Tensor>::Context,
429 ) -> Result<LayeredUnitAction, E> {
430 let left = self
431 .left
432 .before_unit(group, index, remaining_units, value, forward, context)?;
433 let right =
434 self.right
435 .before_unit(group, index, remaining_units, value, forward, context)?;
436 Ok(
437 if left == LayeredUnitAction::SkipRemainingGroup
438 || right == LayeredUnitAction::SkipRemainingGroup
439 {
440 LayeredUnitAction::SkipRemainingGroup
441 } else {
442 LayeredUnitAction::Execute
443 },
444 )
445 }
446
447 fn after_unit(
448 &mut self,
449 group: usize,
450 index: usize,
451 value: &B::Tensor,
452 forward: &mut C,
453 context: &<B::Tensor as eredu_nn::Tensor>::Context,
454 ) -> Result<(), E> {
455 self.left
456 .after_unit(group, index, value, forward, context)?;
457 self.right.after_unit(group, index, value, forward, context)
458 }
459
460 fn after_group(
461 &mut self,
462 group: usize,
463 value: &B::Tensor,
464 forward: &mut C,
465 context: &<B::Tensor as eredu_nn::Tensor>::Context,
466 ) -> Result<(), E> {
467 self.left.after_group(group, value, forward, context)?;
468 self.right.after_group(group, value, forward, context)
469 }
470}
471
472struct NoopLayeredTraversalHook;
473
474impl<B, C, E> LayeredTraversalHook<B, C, E> for NoopLayeredTraversalHook where B: NeuralBackend {}
475
476struct AfterUnitTraversalHook<F> {
477 after_unit: F,
478}
479
480struct AfterUnitContextTraversalHook<F> {
481 after_unit: F,
482}
483
484impl<B, C, E, F> LayeredTraversalHook<B, C, E> for AfterUnitTraversalHook<F>
485where
486 B: NeuralBackend,
487 F: FnMut(usize, usize, &B::Tensor, &mut C) -> Result<(), E>,
488{
489 fn after_unit(
490 &mut self,
491 group: usize,
492 index: usize,
493 value: &B::Tensor,
494 forward: &mut C,
495 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
496 ) -> Result<(), E> {
497 (self.after_unit)(group, index, value, forward)
498 }
499}
500
501impl<B, C, E, F> LayeredTraversalHook<B, C, E> for AfterUnitContextTraversalHook<F>
502where
503 B: NeuralBackend,
504 F: FnMut(usize, usize, &mut C) -> Result<(), E>,
505{
506 fn after_unit(
507 &mut self,
508 group: usize,
509 index: usize,
510 _value: &B::Tensor,
511 forward: &mut C,
512 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
513 ) -> Result<(), E> {
514 (self.after_unit)(group, index, forward)
515 }
516}
517
518pub trait LayeredArchitecture<B, S>:
524 ArchitectureParameters<B, DefinitionError = Self::Error>
525where
526 B: NeuralBackend,
527 S: RuntimeState<B>,
528{
529 type Input<'a>
531 where
532 Self: 'a;
533 type StaticModules: Parameterized<B::Tensor>;
535 type Unit: Parameterized<B::Tensor>;
537 type ForwardContext;
539 type RetainedContextValues<'a>: Iterator<Item = &'a B::Tensor>
541 where
542 Self: 'a,
543 B::Tensor: 'a;
544 type Error;
546
547 fn group_transport(&self, group: usize) -> ArchitectureGroupTransport;
549
550 fn primary_execution_group(&self) -> &str;
557
558 fn prediction_execution_groups(&self) -> Vec<String> {
563 Vec::new()
564 }
565
566 fn state_partition_plan(&self, layout: &StateLayout) -> crate::ArchitectureStatePartitionPlan;
568
569 fn execution_graph(&self) -> Result<ExecutionGraph, Self::Error>;
571
572 fn group_unit_count(&self, group: usize) -> Result<usize, Self::Error>;
574
575 fn unit_path(&self, group: usize, index: usize) -> Result<String, Self::Error>;
577
578 fn static_modules(&self) -> &Self::StaticModules;
580
581 fn static_modules_mut(&mut self) -> &mut Self::StaticModules;
583
584 fn build_unit(
586 &self,
587 group: usize,
588 index: usize,
589 context: &<B::Tensor as eredu_nn::Tensor>::Context,
590 ) -> Result<Self::Unit, Self::Error>;
591
592 fn begin_forward<'a>(
594 &mut self,
595 input: Self::Input<'a>,
596 state: &mut S,
597 context: &<B::Tensor as eredu_nn::Tensor>::Context,
598 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
599
600 fn begin_execution_group(
602 &mut self,
603 group: usize,
604 initial: &B::Tensor,
605 dependencies: &[&B::Tensor],
606 state: &mut S,
607 forward: &mut Self::ForwardContext,
608 context: &<B::Tensor as eredu_nn::Tensor>::Context,
609 ) -> Result<B::Tensor, Self::Error>;
610
611 fn should_execute_group(&self, _group: usize, _forward: &Self::ForwardContext) -> bool {
613 true
614 }
615
616 fn state_ordinal(&self, _group: usize, _index: usize, ordinal: usize) -> usize {
622 ordinal
623 }
624
625 fn retained_state_ordinals(
631 &self,
632 group: usize,
633 index: usize,
634 ordinal: usize,
635 ) -> std::ops::Range<usize> {
636 let state = self.state_ordinal(group, index, ordinal);
637 state..state + 1
638 }
639
640 fn forward_unit(
642 &mut self,
643 group: usize,
644 index: usize,
645 unit: &mut Self::Unit,
646 hidden: &B::Tensor,
647 state: &mut S,
648 forward: &mut Self::ForwardContext,
649 context: &<B::Tensor as eredu_nn::Tensor>::Context,
650 ) -> Result<B::Tensor, Self::Error>;
651
652 fn complete_execution_group(
654 &mut self,
655 _group: usize,
656 hidden: &B::Tensor,
657 _state: &mut S,
658 _forward: &mut Self::ForwardContext,
659 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
660 ) -> Result<B::Tensor, Self::Error> {
661 Ok(hidden.clone())
662 }
663
664 fn finish_forward(
666 &mut self,
667 hidden: &B::Tensor,
668 state: &mut S,
669 forward: &Self::ForwardContext,
670 context: &<B::Tensor as eredu_nn::Tensor>::Context,
671 ) -> Result<B::Tensor, Self::Error>;
672
673 fn retained_context_values<'a>(
675 &'a self,
676 forward: &'a Self::ForwardContext,
677 group: usize,
678 index: usize,
679 ) -> Self::RetainedContextValues<'a>;
680}
681
682pub trait ParallelLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
688where
689 B: NeuralBackend,
690 S: RuntimeState<B>,
691{
692 fn begin_forward_parallel<'a>(
694 &mut self,
695 input: Self::Input<'a>,
696 state: &mut S,
697 parallel: &B::ParallelContext,
698 context: &<B::Tensor as eredu_nn::Tensor>::Context,
699 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
700
701 fn forward_unit_parallel(
703 &mut self,
704 group_index: usize,
705 index: usize,
706 unit: &mut Self::Unit,
707 hidden: &B::Tensor,
708 state: &mut S,
709 forward: &mut Self::ForwardContext,
710 parallel: &B::ParallelContext,
711 context: &<B::Tensor as eredu_nn::Tensor>::Context,
712 ) -> Result<B::Tensor, Self::Error>;
713
714 fn begin_execution_group_parallel(
716 &mut self,
717 group_index: usize,
718 initial: &B::Tensor,
719 dependencies: &[&B::Tensor],
720 state: &mut S,
721 forward: &mut Self::ForwardContext,
722 _parallel: &B::ParallelContext,
723 context: &<B::Tensor as eredu_nn::Tensor>::Context,
724 ) -> Result<B::Tensor, Self::Error> {
725 self.begin_execution_group(group_index, initial, dependencies, state, forward, context)
726 }
727
728 fn complete_execution_group_parallel(
730 &mut self,
731 group_index: usize,
732 hidden: &B::Tensor,
733 state: &mut S,
734 forward: &mut Self::ForwardContext,
735 _parallel: &B::ParallelContext,
736 context: &<B::Tensor as eredu_nn::Tensor>::Context,
737 ) -> Result<B::Tensor, Self::Error> {
738 self.complete_execution_group(group_index, hidden, state, forward, context)
739 }
740
741 fn finish_forward_parallel(
743 &mut self,
744 hidden: &B::Tensor,
745 state: &mut S,
746 forward: &Self::ForwardContext,
747 parallel: &B::ParallelContext,
748 context: &<B::Tensor as eredu_nn::Tensor>::Context,
749 ) -> Result<B::Tensor, Self::Error>;
750}
751
752#[derive(Debug)]
757pub enum LayeredPartitionInput<'a, T, A = NoAuxiliaryBoundary> {
758 Tokens(&'a T),
760 Hidden {
762 hidden: T,
764 auxiliary: A,
766 },
767}
768
769pub enum LayeredPartitionOutput<T, A = NoAuxiliaryBoundary> {
775 Final {
777 output: T,
779 retained: Option<T>,
781 },
782 Boundary {
784 hidden: T,
786 auxiliary: A,
788 },
789}
790
791pub trait PartitionedLayeredArchitecture<B, S>: ParallelLayeredArchitecture<B, S>
798where
799 B: NeuralBackend,
800 S: RuntimeState<B>,
801{
802 type Boundary: crate::ArchitectureBoundary;
804
805 fn boundary_schema(&self) -> Result<Self::Boundary, Self::Error>;
807
808 fn begin_partition<'a>(
810 &mut self,
811 input: LayeredPartitionInput<
812 'a,
813 B::Tensor,
814 <Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
815 >,
816 mask: Option<&B::Tensor>,
817 state: &mut S,
818 expected: &crate::StateLayout,
819 first_state_ordinal: usize,
820 context: &<B::Tensor as eredu_nn::Tensor>::Context,
821 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
822
823 #[allow(clippy::too_many_arguments)]
825 fn begin_partition_parallel<'a>(
826 &mut self,
827 input: LayeredPartitionInput<
828 'a,
829 B::Tensor,
830 <Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
831 >,
832 mask: Option<&B::Tensor>,
833 state: &mut S,
834 expected: &crate::StateLayout,
835 first_state_ordinal: usize,
836 parallel: &B::ParallelContext,
837 context: &<B::Tensor as eredu_nn::Tensor>::Context,
838 ) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
839
840 #[allow(clippy::too_many_arguments)]
845 fn enter_partition_group(
846 &mut self,
847 group: usize,
848 initial: &B::Tensor,
849 state: &mut S,
850 forward: &mut Self::ForwardContext,
851 parallel: Option<&B::ParallelContext>,
852 context: &<B::Tensor as eredu_nn::Tensor>::Context,
853 ) -> Result<B::Tensor, Self::Error> {
854 match parallel {
855 Some(parallel) => self.begin_execution_group_parallel(
856 group,
857 initial,
858 &[],
859 state,
860 forward,
861 parallel,
862 context,
863 ),
864 None => self.begin_execution_group(group, initial, &[], state, forward, context),
865 }
866 }
867
868 #[allow(clippy::too_many_arguments)]
871 fn leave_partition_group(
872 &mut self,
873 group: usize,
874 hidden: &B::Tensor,
875 state: &mut S,
876 forward: &mut Self::ForwardContext,
877 parallel: Option<&B::ParallelContext>,
878 context: &<B::Tensor as eredu_nn::Tensor>::Context,
879 ) -> Result<B::Tensor, Self::Error> {
880 match parallel {
881 Some(parallel) => self.complete_execution_group_parallel(
882 group, hidden, state, forward, parallel, context,
883 ),
884 None => self.complete_execution_group(group, hidden, state, forward, context),
885 }
886 }
887
888 #[allow(clippy::too_many_arguments)]
890 fn finish_partition(
891 &mut self,
892 hidden: &B::Tensor,
893 state: &mut S,
894 forward: &Self::ForwardContext,
895 owns_output: bool,
896 parallel: Option<&B::ParallelContext>,
897 context: &<B::Tensor as eredu_nn::Tensor>::Context,
898 ) -> Result<
899 LayeredPartitionOutput<
900 B::Tensor,
901 <Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
902 >,
903 Self::Error,
904 >;
905}
906
907pub trait RoutedLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
912where
913 B: eredu_nn::GroupedNeuralBackend,
914 S: RuntimeState<B>,
915{
916 fn routed_observation_point(
922 &self,
923 _group: usize,
924 _index: usize,
925 ) -> Result<Option<RoutedObservationPoint>, Self::Error> {
926 Ok(None)
927 }
928
929 #[allow(clippy::too_many_arguments)]
931 fn forward_unit_with_provider<P>(
932 &mut self,
933 group: usize,
934 index: usize,
935 unit: &mut Self::Unit,
936 hidden: &B::Tensor,
937 state: &mut S,
938 forward: &mut Self::ForwardContext,
939 pass: ExpertPass,
940 provider: &mut P,
941 context: &<B::Tensor as eredu_nn::Tensor>::Context,
942 ) -> Result<B::Tensor, Self::Error>
943 where
944 P: RoutedExpertProvider<B>,
945 P::Error: std::fmt::Display;
946}
947
948pub trait ParallelRoutedLayeredArchitecture<B, S>:
950 RoutedLayeredArchitecture<B, S> + ParallelLayeredArchitecture<B, S>
951where
952 B: eredu_nn::GroupedNeuralBackend,
953 S: RuntimeState<B>,
954{
955 #[allow(clippy::too_many_arguments)]
957 fn forward_unit_parallel_with_provider<P>(
958 &mut self,
959 group: usize,
960 index: usize,
961 unit: &mut Self::Unit,
962 hidden: &B::Tensor,
963 state: &mut S,
964 forward: &mut Self::ForwardContext,
965 pass: ExpertPass,
966 provider: &mut P,
967 parallel: &B::ParallelContext,
968 context: &<B::Tensor as eredu_nn::Tensor>::Context,
969 ) -> Result<B::Tensor, Self::Error>
970 where
971 P: crate::TensorParallelRoutedExpertProvider<B>,
972 P::Error: std::fmt::Display;
973}
974
975pub struct ResidentRuntime<A, B, S>
977where
978 B: NeuralBackend,
979 S: RuntimeState<B>,
980 A: LayeredArchitecture<B, S>,
981{
982 architecture: A,
983 graph: ExecutionGraph,
984 units: Vec<Vec<A::Unit>>,
985 backend: std::marker::PhantomData<fn() -> (B, S)>,
986}
987
988impl<A, B, S> ResidentRuntime<A, B, S>
989where
990 B: NeuralBackend,
991 S: RuntimeState<B>,
992 A: LayeredArchitecture<B, S>,
993{
994 pub fn new(
996 architecture: A,
997 context: &<B::Tensor as eredu_nn::Tensor>::Context,
998 ) -> Result<Self, A::Error> {
999 let graph = architecture.execution_graph()?;
1000 let mut units = Vec::with_capacity(graph.groups().len());
1001 for group in 0..graph.groups().len() {
1002 let count = architecture.group_unit_count(group)?;
1003 units.push(
1004 (0..count)
1005 .map(|index| architecture.build_unit(group, index, context))
1006 .collect::<Result<Vec<_>, _>>()?,
1007 );
1008 }
1009 Ok(Self {
1010 architecture,
1011 graph,
1012 units,
1013 backend: std::marker::PhantomData,
1014 })
1015 }
1016
1017 pub fn forward<'a>(
1019 &mut self,
1020 input: A::Input<'a>,
1021 state: &mut S,
1022 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1023 ) -> Result<B::Tensor, A::Error> {
1024 self.forward_with_context(input, state, context)
1025 .map(|(output, _)| output)
1026 }
1027
1028 pub fn forward_with_context<'a>(
1034 &mut self,
1035 input: A::Input<'a>,
1036 state: &mut S,
1037 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1038 ) -> Result<(B::Tensor, A::ForwardContext), A::Error> {
1039 self.forward_with_traversal_hook(input, state, context, &mut NoopLayeredTraversalHook)
1040 }
1041
1042 pub fn forward_with_traversal_hook<'a, H>(
1044 &mut self,
1045 input: A::Input<'a>,
1046 state: &mut S,
1047 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1048 hook: &mut H,
1049 ) -> Result<(B::Tensor, A::ForwardContext), A::Error>
1050 where
1051 H: LayeredTraversalHook<B, A::ForwardContext, A::Error>,
1052 {
1053 let forward = self.architecture.begin_forward(input, state, context)?;
1054 let initial = forward.hidden;
1055 let mut forward_context = forward.context;
1056 let mut schedule = ExecutionGroupSchedule::new(&self.graph);
1057 let mut outputs: Vec<Option<B::Tensor>> = vec![None; self.graph.groups().len()];
1058 for &group in self.graph.execution_order() {
1059 let dependencies = schedule
1060 .dependencies(group)
1061 .expect("validated execution order contains a known group")
1062 .iter()
1063 .map(|&dependency| {
1064 outputs[dependency]
1065 .as_ref()
1066 .expect("topological dependency has completed")
1067 .clone()
1068 })
1069 .collect::<Vec<_>>();
1070 let dependency_refs = dependencies.iter().collect::<Vec<_>>();
1071 let mut hidden = self.architecture.begin_execution_group(
1072 group,
1073 &initial,
1074 &dependency_refs,
1075 state,
1076 &mut forward_context,
1077 context,
1078 )?;
1079 for dependency in schedule
1080 .started(group)
1081 .expect("topological execution starts only ready groups")
1082 {
1083 outputs[dependency] = None;
1084 }
1085 if self
1086 .architecture
1087 .should_execute_group(group, &forward_context)
1088 {
1089 let unit_count = self.units[group].len();
1090 for (index, unit) in self.units[group].iter_mut().enumerate() {
1091 if hook.before_unit(
1092 group,
1093 index,
1094 unit_count - index,
1095 &hidden,
1096 &mut forward_context,
1097 context,
1098 )? == LayeredUnitAction::SkipRemainingGroup
1099 {
1100 break;
1101 }
1102 hidden = self.architecture.forward_unit(
1103 group,
1104 index,
1105 unit,
1106 &hidden,
1107 state,
1108 &mut forward_context,
1109 context,
1110 )?;
1111 hook.after_unit(group, index, &hidden, &mut forward_context, context)?;
1112 }
1113 }
1114 hidden = self.architecture.complete_execution_group(
1115 group,
1116 &hidden,
1117 state,
1118 &mut forward_context,
1119 context,
1120 )?;
1121 hook.after_group(group, &hidden, &mut forward_context, context)?;
1122 outputs[group] = Some(hidden);
1123 schedule
1124 .ordered(group)
1125 .expect("started group can be ordered exactly once");
1126 }
1127 let hidden = outputs[self.graph.output()]
1128 .take()
1129 .expect("validated graph output completed");
1130 let output = self
1131 .architecture
1132 .finish_forward(&hidden, state, &forward_context, context)?;
1133 Ok((output, forward_context))
1134 }
1135
1136 pub const fn architecture(&self) -> &A {
1138 &self.architecture
1139 }
1140
1141 pub fn architecture_mut(&mut self) -> &mut A {
1143 &mut self.architecture
1144 }
1145
1146 pub fn units(&self) -> &[Vec<A::Unit>] {
1148 &self.units
1149 }
1150
1151 pub fn units_mut(&mut self) -> &mut [Vec<A::Unit>] {
1153 &mut self.units
1154 }
1155
1156 pub fn into_parts(self) -> (A, Vec<A::Unit>) {
1158 (
1159 self.architecture,
1160 self.units.into_iter().flatten().collect(),
1161 )
1162 }
1163}
1164
1165pub trait LayerwisePolicy<B, U>
1167where
1168 B: NeuralBackend,
1169{
1170 type Lease: std::ops::DerefMut<Target = U>;
1172 type Error;
1174
1175 fn begin(
1177 &mut self,
1178 initial: &B::Tensor,
1179 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1180 ) -> Result<(), Self::Error>;
1181
1182 fn abort(
1188 &mut self,
1189 active: Option<(usize, crate::ExecutionUnitAddress, Self::Lease)>,
1190 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
1191 ) {
1192 drop(active);
1193 }
1194
1195 fn acquire<E, F>(
1200 &mut self,
1201 ordinal: usize,
1202 address: crate::ExecutionUnitAddress,
1203 build: F,
1204 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1205 ) -> Result<Self::Lease, LayerwiseAcquireError<E, Self::Error>>
1206 where
1207 F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>;
1208
1209 fn complete<'a, StateValues, ContextValues>(
1211 &mut self,
1212 ordinal: usize,
1213 address: crate::ExecutionUnitAddress,
1214 lease: Self::Lease,
1215 output: &'a B::Tensor,
1216 state_values: StateValues,
1217 context_values: ContextValues,
1218 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1219 ) -> Result<(), Self::Error>
1220 where
1221 B::Tensor: 'a,
1222 StateValues: Iterator<Item = &'a B::Tensor>,
1223 ContextValues: Iterator<Item = &'a B::Tensor>;
1224
1225 fn finish(
1227 &mut self,
1228 output: &B::Tensor,
1229 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1230 ) -> Result<(), Self::Error>;
1231}
1232
1233struct LayerwisePolicyForward<'a, B, U, P>
1235where
1236 B: NeuralBackend,
1237 P: LayerwisePolicy<B, U>,
1238{
1239 policy: &'a mut P,
1240 context: &'a <B::Tensor as eredu_nn::Tensor>::Context,
1241 active: Option<(usize, crate::ExecutionUnitAddress, P::Lease)>,
1242 finished: bool,
1243 unit: std::marker::PhantomData<fn() -> U>,
1244}
1245
1246impl<'a, B, U, P> LayerwisePolicyForward<'a, B, U, P>
1247where
1248 B: NeuralBackend,
1249 P: LayerwisePolicy<B, U>,
1250{
1251 fn begin(
1252 policy: &'a mut P,
1253 initial: &B::Tensor,
1254 context: &'a <B::Tensor as eredu_nn::Tensor>::Context,
1255 ) -> Result<Self, P::Error> {
1256 if let Err(error) = policy.begin(initial, context) {
1257 policy.abort(None, context);
1258 return Err(error);
1259 }
1260 Ok(Self {
1261 policy,
1262 context,
1263 active: None,
1264 finished: false,
1265 unit: std::marker::PhantomData,
1266 })
1267 }
1268
1269 fn acquire<E, F>(
1270 &mut self,
1271 ordinal: usize,
1272 address: crate::ExecutionUnitAddress,
1273 build: F,
1274 ) -> Result<&mut P::Lease, LayerwiseAcquireError<E, P::Error>>
1275 where
1276 F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>,
1277 {
1278 debug_assert!(self.active.is_none());
1279 let lease = self.policy.acquire(ordinal, address, build, self.context)?;
1280 self.active = Some((ordinal, address, lease));
1281 Ok(&mut self
1282 .active
1283 .as_mut()
1284 .expect("acquired policy lease is active")
1285 .2)
1286 }
1287
1288 fn complete<'value, StateValues, ContextValues>(
1289 &mut self,
1290 output: &'value B::Tensor,
1291 state_values: StateValues,
1292 context_values: ContextValues,
1293 ) -> Result<(), P::Error>
1294 where
1295 B::Tensor: 'value,
1296 StateValues: Iterator<Item = &'value B::Tensor>,
1297 ContextValues: Iterator<Item = &'value B::Tensor>,
1298 {
1299 let (ordinal, address, lease) = self
1300 .active
1301 .take()
1302 .expect("policy completion follows one acquisition");
1303 self.policy.complete(
1304 ordinal,
1305 address,
1306 lease,
1307 output,
1308 state_values,
1309 context_values,
1310 self.context,
1311 )
1312 }
1313
1314 fn finish(&mut self, output: &B::Tensor) -> Result<(), P::Error> {
1315 self.policy.finish(output, self.context)?;
1316 self.finished = true;
1317 Ok(())
1318 }
1319}
1320
1321impl<B, U, P> Drop for LayerwisePolicyForward<'_, B, U, P>
1322where
1323 B: NeuralBackend,
1324 P: LayerwisePolicy<B, U>,
1325{
1326 fn drop(&mut self) {
1327 if !self.finished {
1328 self.policy.abort(self.active.take(), self.context);
1329 }
1330 }
1331}
1332
1333#[derive(Debug)]
1335pub enum LayerwiseAcquireError<A, P> {
1336 Architecture(A),
1338 Policy(P),
1340}
1341
1342#[derive(Debug, thiserror::Error)]
1344pub enum LayerwiseRuntimeError<A, P>
1345where
1346 A: std::fmt::Display,
1347 P: std::fmt::Display,
1348{
1349 #[error("layered architecture failed: {0}")]
1351 Architecture(A),
1352 #[error(transparent)]
1354 State(#[from] crate::StateError),
1355 #[error(transparent)]
1357 Layout(#[from] crate::ExecutionUnitLayoutError),
1358 #[error("layerwise execution policy failed: {0}")]
1360 Policy(P),
1361 #[error("layerwise backend submission failed: {0}")]
1363 Submission(String),
1364}
1365
1366pub struct LayerwiseRuntime<A, B, S, P>
1368where
1369 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as eredu_nn::Tensor>::Context>,
1370 S: RuntimeState<B>,
1371 A: LayeredArchitecture<B, S>,
1372 P: LayerwisePolicy<B, A::Unit>,
1373{
1374 architecture: A,
1375 policy: P,
1376 executors: Option<Vec<B::OwnedExecutor>>,
1377 backend: std::marker::PhantomData<fn() -> (B, S)>,
1378}
1379
1380impl<A, B, S, P> LayerwiseRuntime<A, B, S, P>
1381where
1382 B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as eredu_nn::Tensor>::Context>,
1383 S: RuntimeState<B>,
1384 A: LayeredArchitecture<B, S>,
1385 P: LayerwisePolicy<B, A::Unit>,
1386 A::Error: std::fmt::Display,
1387 P::Error: std::fmt::Display,
1388{
1389 pub const fn new(architecture: A, policy: P) -> Self {
1391 Self {
1392 architecture,
1393 policy,
1394 executors: None,
1395 backend: std::marker::PhantomData,
1396 }
1397 }
1398
1399 pub const fn new_policy_first(policy: P, architecture: A) -> Self {
1403 Self::new(architecture, policy)
1404 }
1405
1406 pub const fn architecture(&self) -> &A {
1408 &self.architecture
1409 }
1410
1411 pub fn architecture_mut(&mut self) -> &mut A {
1413 &mut self.architecture
1414 }
1415
1416 pub const fn policy(&self) -> &P {
1418 &self.policy
1419 }
1420
1421 pub fn policy_mut(&mut self) -> &mut P {
1423 &mut self.policy
1424 }
1425
1426 pub fn forward<'a>(
1428 &mut self,
1429 input: A::Input<'a>,
1430 state: &mut S,
1431 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1432 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>> {
1433 self.forward_with_context_hook(input, state, context, |_, _, _| Ok(()))
1434 .map(|(output, _)| output)
1435 }
1436
1437 pub fn forward_with_context_hook<'a, H>(
1439 &mut self,
1440 input: A::Input<'a>,
1441 state: &mut S,
1442 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1443 hook: H,
1444 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1445 where
1446 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
1447 {
1448 self.forward_with_unit_executor_and_context_hook(
1449 input,
1450 state,
1451 context,
1452 |architecture, group, index, unit, hidden, state, forward, context| {
1453 architecture.forward_unit(group, index, unit, hidden, state, forward, context)
1454 },
1455 hook,
1456 )
1457 }
1458
1459 pub fn forward_with_unit_executor<'a, E>(
1465 &mut self,
1466 input: A::Input<'a>,
1467 state: &mut S,
1468 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1469 execute: E,
1470 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1471 where
1472 E: FnMut(
1473 &mut A,
1474 usize,
1475 usize,
1476 &mut A::Unit,
1477 &B::Tensor,
1478 &mut S,
1479 &mut A::ForwardContext,
1480 &<B::Tensor as eredu_nn::Tensor>::Context,
1481 ) -> Result<B::Tensor, A::Error>,
1482 {
1483 self.forward_with_unit_executor_and_context_hook(
1484 input,
1485 state,
1486 context,
1487 execute,
1488 |_, _, _| Ok(()),
1489 )
1490 .map(|(output, _)| output)
1491 }
1492
1493 pub fn forward_with_observer<'a, Observer>(
1495 &mut self,
1496 input: A::Input<'a>,
1497 state: &mut S,
1498 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1499 observer: &mut Observer,
1500 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1501 where
1502 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1503 {
1504 self.forward_with_unit_executor_and_observer(
1505 input,
1506 state,
1507 context,
1508 |architecture, group, index, unit, hidden, state, forward, context| {
1509 architecture.forward_unit(group, index, unit, hidden, state, forward, context)
1510 },
1511 observer,
1512 )
1513 }
1514
1515 pub fn forward_with_unit_executor_and_observer<'a, E, Observer>(
1517 &mut self,
1518 input: A::Input<'a>,
1519 state: &mut S,
1520 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1521 mut execute: E,
1522 observer: &mut Observer,
1523 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1524 where
1525 E: FnMut(
1526 &mut A,
1527 usize,
1528 usize,
1529 &mut A::Unit,
1530 &B::Tensor,
1531 &mut S,
1532 &mut A::ForwardContext,
1533 &<B::Tensor as eredu_nn::Tensor>::Context,
1534 ) -> Result<B::Tensor, A::Error>,
1535 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1536 {
1537 self.forward_with_unit_executor(
1538 input,
1539 state,
1540 context,
1541 |architecture, group, index, unit, hidden, state, forward, context| {
1542 let path = architecture.unit_path(group, index)?;
1543 let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
1544 let output = execute(
1545 architecture,
1546 group,
1547 index,
1548 unit,
1549 &input,
1550 state,
1551 forward,
1552 context,
1553 )?;
1554 observe_and_intervene(observer, &format!("{path}.output"), &output)
1555 },
1556 )
1557 }
1558
1559 #[allow(clippy::too_many_arguments)]
1567 pub fn forward_with_provider_and_observer<'a, Provider, Observer>(
1568 &mut self,
1569 input: A::Input<'a>,
1570 state: &mut S,
1571 pass: ExpertPass,
1572 provider: &mut Provider,
1573 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1574 observer: &mut Observer,
1575 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1576 where
1577 B: eredu_nn::GroupedNeuralBackend,
1578 A: RoutedLayeredArchitecture<B, S>,
1579 A::Error: std::fmt::Display,
1580 Provider: RoutedExpertProvider<B>,
1581 Provider::Error: std::fmt::Display,
1582 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
1583 {
1584 self.forward_with_unit_executor(
1585 input,
1586 state,
1587 context,
1588 |architecture, group, index, unit, hidden, state, forward, context| {
1589 let path = architecture.unit_path(group, index)?;
1590 let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
1591 let output = match architecture.routed_observation_point(group, index)? {
1592 Some(point) => {
1593 let mut observed = ObservedExpertProvider::new(provider, observer, point);
1594 architecture.forward_unit_with_provider(
1595 group,
1596 index,
1597 unit,
1598 &input,
1599 state,
1600 forward,
1601 pass,
1602 &mut observed,
1603 context,
1604 )?
1605 }
1606 None => architecture.forward_unit_with_provider(
1607 group, index, unit, &input, state, forward, pass, provider, context,
1608 )?,
1609 };
1610 observe_and_intervene(observer, &format!("{path}.output"), &output)
1611 },
1612 )
1613 }
1614
1615 pub fn forward_with_unit_executor_and_context_hook<'a, E, H>(
1617 &mut self,
1618 input: A::Input<'a>,
1619 state: &mut S,
1620 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1621 execute: E,
1622 mut hook: H,
1623 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1624 where
1625 E: FnMut(
1626 &mut A,
1627 usize,
1628 usize,
1629 &mut A::Unit,
1630 &B::Tensor,
1631 &mut S,
1632 &mut A::ForwardContext,
1633 &<B::Tensor as eredu_nn::Tensor>::Context,
1634 ) -> Result<B::Tensor, A::Error>,
1635 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
1636 {
1637 self.forward_with_unit_executor_and_activation_hook(
1638 input,
1639 state,
1640 context,
1641 execute,
1642 |group, index, _hidden, forward| hook(group, index, forward),
1643 )
1644 }
1645
1646 pub fn forward_with_unit_executor_and_activation_hook<'a, E, H>(
1653 &mut self,
1654 input: A::Input<'a>,
1655 state: &mut S,
1656 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1657 execute: E,
1658 hook: H,
1659 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1660 where
1661 E: FnMut(
1662 &mut A,
1663 usize,
1664 usize,
1665 &mut A::Unit,
1666 &B::Tensor,
1667 &mut S,
1668 &mut A::ForwardContext,
1669 &<B::Tensor as eredu_nn::Tensor>::Context,
1670 ) -> Result<B::Tensor, A::Error>,
1671 H: FnMut(usize, usize, &B::Tensor, &mut A::ForwardContext) -> Result<(), A::Error>,
1672 {
1673 self.forward_with_unit_executor_and_traversal_hook(
1674 input,
1675 state,
1676 context,
1677 execute,
1678 &mut AfterUnitTraversalHook { after_unit: hook },
1679 )
1680 }
1681
1682 pub fn forward_with_traversal_hook<'a, H>(
1684 &mut self,
1685 input: A::Input<'a>,
1686 state: &mut S,
1687 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1688 hook: &mut H,
1689 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1690 where
1691 H: LayeredTraversalHook<B, A::ForwardContext, A::Error>,
1692 {
1693 self.forward_with_unit_executor_and_traversal_hook(
1694 input,
1695 state,
1696 context,
1697 |architecture, group, index, unit, hidden, state, forward, context| {
1698 architecture.forward_unit(group, index, unit, hidden, state, forward, context)
1699 },
1700 hook,
1701 )
1702 }
1703
1704 pub fn forward_with_unit_executor_and_traversal_hook<'a, E, H>(
1706 &mut self,
1707 input: A::Input<'a>,
1708 state: &mut S,
1709 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1710 mut execute: E,
1711 hook: &mut H,
1712 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1713 where
1714 E: FnMut(
1715 &mut A,
1716 usize,
1717 usize,
1718 &mut A::Unit,
1719 &B::Tensor,
1720 &mut S,
1721 &mut A::ForwardContext,
1722 &<B::Tensor as eredu_nn::Tensor>::Context,
1723 ) -> Result<B::Tensor, A::Error>,
1724 H: LayeredTraversalHook<B, A::ForwardContext, A::Error>,
1725 {
1726 let graph = self
1727 .architecture
1728 .execution_graph()
1729 .map_err(LayerwiseRuntimeError::Architecture)?;
1730 let counts = (0..graph.groups().len())
1731 .map(|group| {
1732 self.architecture
1733 .group_unit_count(group)
1734 .map_err(LayerwiseRuntimeError::Architecture)
1735 })
1736 .collect::<Result<Vec<_>, _>>()?;
1737 let layout = ExecutionUnitLayout::new(&graph, counts)?;
1738 if self.executors.as_ref().map(Vec::len) != Some(graph.groups().len()) {
1739 self.executors = Some(
1740 B::fork_executors(context, graph.groups().len())
1741 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
1742 );
1743 }
1744 let executors = self
1745 .executors
1746 .as_ref()
1747 .expect("layered runtime initialized its executor cache");
1748 let forward = self
1749 .architecture
1750 .begin_forward(input, state, context)
1751 .map_err(LayerwiseRuntimeError::Architecture)?;
1752 let initial_completion = (graph.groups().len() > 1)
1753 .then(|| B::submit(context, [&forward.hidden]))
1754 .transpose()
1755 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
1756 let mut policy = LayerwisePolicyForward::begin(&mut self.policy, &forward.hidden, context)
1757 .map_err(LayerwiseRuntimeError::Policy)?;
1758 let initial = forward.hidden;
1759 let mut forward_context = forward.context;
1760 let mut schedule = ExecutionGroupSchedule::new(&graph);
1761 let mut outputs: Vec<Option<B::Tensor>> = vec![None; graph.groups().len()];
1762 let mut completions: Vec<Option<B::Completion>> =
1763 (0..graph.groups().len()).map(|_| None).collect();
1764 for &group in graph.execution_order() {
1765 let executor = std::borrow::Borrow::borrow(&executors[group]);
1766 let group_dependencies = schedule
1767 .dependencies(group)
1768 .expect("validated execution order contains a known group");
1769 if group_dependencies.is_empty() {
1770 if let Some(completion) = &initial_completion {
1771 B::order_after(completion, executor)
1772 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
1773 }
1774 }
1775 for &dependency in group_dependencies {
1776 B::order_after(
1777 completions[dependency]
1778 .as_ref()
1779 .expect("topological dependency has a completion"),
1780 executor,
1781 )
1782 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
1783 }
1784 let dependencies = schedule
1785 .dependencies(group)
1786 .expect("validated execution order contains a known group")
1787 .iter()
1788 .map(|&dependency| {
1789 outputs[dependency]
1790 .as_ref()
1791 .expect("topological dependency has completed")
1792 .clone()
1793 })
1794 .collect::<Vec<_>>();
1795 let dependency_refs = dependencies.iter().collect::<Vec<_>>();
1796 let mut hidden = self
1797 .architecture
1798 .begin_execution_group(
1799 group,
1800 &initial,
1801 &dependency_refs,
1802 state,
1803 &mut forward_context,
1804 executor,
1805 )
1806 .map_err(LayerwiseRuntimeError::Architecture)?;
1807 for dependency in schedule
1808 .started(group)
1809 .expect("topological execution starts only ready groups")
1810 {
1811 outputs[dependency] = None;
1812 }
1813 if self
1814 .architecture
1815 .should_execute_group(group, &forward_context)
1816 {
1817 let unit_count = layout
1818 .group_range(group)
1819 .expect("layout covers every graph group")
1820 .len();
1821 for index in 0..unit_count {
1822 if hook
1823 .before_unit(
1824 group,
1825 index,
1826 unit_count - index,
1827 &hidden,
1828 &mut forward_context,
1829 executor,
1830 )
1831 .map_err(LayerwiseRuntimeError::Architecture)?
1832 == LayeredUnitAction::SkipRemainingGroup
1833 {
1834 break;
1835 }
1836 let ordinal = layout
1837 .ordinal(group, index)
1838 .expect("group-local unit belongs to the layout");
1839 let address = layout
1840 .address(ordinal)
1841 .expect("group-local unit has a stable policy address");
1842 let lease = policy
1843 .acquire(ordinal, address, |executor| {
1844 self.architecture.build_unit(group, index, executor)
1845 })
1846 .map_err(|error| match error {
1847 LayerwiseAcquireError::Architecture(error) => {
1848 LayerwiseRuntimeError::Architecture(error)
1849 }
1850 LayerwiseAcquireError::Policy(error) => {
1851 LayerwiseRuntimeError::Policy(error)
1852 }
1853 })?;
1854 hidden = execute(
1855 &mut self.architecture,
1856 group,
1857 index,
1858 lease,
1859 &hidden,
1860 state,
1861 &mut forward_context,
1862 executor,
1863 )
1864 .map_err(LayerwiseRuntimeError::Architecture)?;
1865 hook.after_unit(group, index, &hidden, &mut forward_context, executor)
1866 .map_err(LayerwiseRuntimeError::Architecture)?;
1867 let mut state_values = Vec::new();
1868 for state_ordinal in self
1869 .architecture
1870 .retained_state_ordinals(group, index, ordinal)
1871 {
1872 state_values.extend(
1873 state
1874 .retained_values(state_ordinal, address.with_index(state_ordinal))
1875 .map_err(LayerwiseRuntimeError::State)?,
1876 );
1877 }
1878 let context_values =
1879 self.architecture
1880 .retained_context_values(&forward_context, group, index);
1881 policy
1882 .complete(&hidden, state_values.into_iter(), context_values)
1883 .map_err(LayerwiseRuntimeError::Policy)?;
1884 }
1885 }
1886 hidden = self
1887 .architecture
1888 .complete_execution_group(group, &hidden, state, &mut forward_context, executor)
1889 .map_err(LayerwiseRuntimeError::Architecture)?;
1890 hook.after_group(group, &hidden, &mut forward_context, executor)
1891 .map_err(LayerwiseRuntimeError::Architecture)?;
1892 outputs[group] = Some(hidden);
1893 if graph.groups().len() > 1 {
1894 completions[group] = Some(
1895 B::submit(
1896 executor,
1897 [outputs[group]
1898 .as_ref()
1899 .expect("group output was stored before submission")],
1900 )
1901 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
1902 );
1903 }
1904 schedule
1905 .ordered(group)
1906 .expect("started group can be ordered exactly once");
1907 }
1908 let hidden = outputs[graph.output()]
1909 .take()
1910 .expect("validated graph output completed");
1911 if let Some(completion) = &completions[graph.output()] {
1912 B::order_after(completion, context)
1913 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
1914 }
1915 let output = self
1916 .architecture
1917 .finish_forward(&hidden, state, &forward_context, context)
1918 .map_err(LayerwiseRuntimeError::Architecture)?;
1919 policy
1920 .finish(&output)
1921 .map_err(LayerwiseRuntimeError::Policy)?;
1922 Ok((output, forward_context))
1923 }
1924
1925 pub fn forward_parallel<'a>(
1927 &mut self,
1928 input: A::Input<'a>,
1929 state: &mut S,
1930 parallel: &B::ParallelContext,
1931 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1932 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1933 where
1934 A: ParallelLayeredArchitecture<B, S>,
1935 {
1936 self.forward_parallel_with_context_hook(input, state, parallel, context, |_, _, _| Ok(()))
1937 .map(|(output, _)| output)
1938 }
1939
1940 pub fn forward_parallel_with_context_hook<'a, H>(
1942 &mut self,
1943 input: A::Input<'a>,
1944 state: &mut S,
1945 parallel: &B::ParallelContext,
1946 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1947 hook: H,
1948 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
1949 where
1950 A: ParallelLayeredArchitecture<B, S>,
1951 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
1952 {
1953 self.forward_parallel_with_unit_executor_and_traversal_hook(
1954 input,
1955 state,
1956 parallel,
1957 context,
1958 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
1959 architecture.forward_unit_parallel(
1960 group, index, unit, hidden, state, forward, parallel, context,
1961 )
1962 },
1963 &mut AfterUnitContextTraversalHook { after_unit: hook },
1964 )
1965 }
1966
1967 pub fn forward_parallel_with_unit_executor<'a, E>(
1969 &mut self,
1970 input: A::Input<'a>,
1971 state: &mut S,
1972 parallel: &B::ParallelContext,
1973 context: &<B::Tensor as eredu_nn::Tensor>::Context,
1974 execute: E,
1975 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
1976 where
1977 A: ParallelLayeredArchitecture<B, S>,
1978 E: FnMut(
1979 &mut A,
1980 usize,
1981 usize,
1982 &mut A::Unit,
1983 &B::Tensor,
1984 &mut S,
1985 &mut A::ForwardContext,
1986 &B::ParallelContext,
1987 &<B::Tensor as eredu_nn::Tensor>::Context,
1988 ) -> Result<B::Tensor, A::Error>,
1989 {
1990 self.forward_parallel_with_unit_executor_and_context_hook(
1991 input,
1992 state,
1993 parallel,
1994 context,
1995 execute,
1996 |_, _, _| Ok(()),
1997 )
1998 .map(|(output, _)| output)
1999 }
2000
2001 pub fn forward_parallel_with_observer<'a, Observer>(
2003 &mut self,
2004 input: A::Input<'a>,
2005 state: &mut S,
2006 parallel: &B::ParallelContext,
2007 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2008 observer: &mut Observer,
2009 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2010 where
2011 A: ParallelLayeredArchitecture<B, S>,
2012 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2013 {
2014 self.forward_parallel_with_unit_executor_and_observer(
2015 input,
2016 state,
2017 parallel,
2018 context,
2019 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2020 architecture.forward_unit_parallel(
2021 group, index, unit, hidden, state, forward, parallel, context,
2022 )
2023 },
2024 observer,
2025 )
2026 }
2027
2028 pub fn forward_parallel_with_unit_executor_and_observer<'a, E, Observer>(
2030 &mut self,
2031 input: A::Input<'a>,
2032 state: &mut S,
2033 parallel: &B::ParallelContext,
2034 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2035 mut execute: E,
2036 observer: &mut Observer,
2037 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2038 where
2039 A: ParallelLayeredArchitecture<B, S>,
2040 E: FnMut(
2041 &mut A,
2042 usize,
2043 usize,
2044 &mut A::Unit,
2045 &B::Tensor,
2046 &mut S,
2047 &mut A::ForwardContext,
2048 &B::ParallelContext,
2049 &<B::Tensor as eredu_nn::Tensor>::Context,
2050 ) -> Result<B::Tensor, A::Error>,
2051 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2052 {
2053 self.forward_parallel_with_unit_executor(
2054 input,
2055 state,
2056 parallel,
2057 context,
2058 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2059 let path = architecture.unit_path(group, index)?;
2060 let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
2061 let output = execute(
2062 architecture,
2063 group,
2064 index,
2065 unit,
2066 &input,
2067 state,
2068 forward,
2069 parallel,
2070 context,
2071 )?;
2072 observe_and_intervene(observer, &format!("{path}.output"), &output)
2073 },
2074 )
2075 }
2076
2077 #[allow(clippy::too_many_arguments)]
2079 pub fn forward_parallel_with_provider_and_observer<'a, Provider, Observer>(
2080 &mut self,
2081 input: A::Input<'a>,
2082 state: &mut S,
2083 pass: ExpertPass,
2084 provider: &mut Provider,
2085 parallel: &B::ParallelContext,
2086 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2087 observer: &mut Observer,
2088 ) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
2089 where
2090 B: eredu_nn::GroupedNeuralBackend,
2091 A: ParallelRoutedLayeredArchitecture<B, S>,
2092 Provider: crate::TensorParallelRoutedExpertProvider<B>,
2093 Provider::Error: std::fmt::Display,
2094 Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
2095 {
2096 self.forward_parallel_with_unit_executor(
2097 input,
2098 state,
2099 parallel,
2100 context,
2101 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2102 let path = architecture.unit_path(group, index)?;
2103 let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
2104 let output = match architecture.routed_observation_point(group, index)? {
2105 Some(point) => {
2106 let mut observed = ObservedExpertProvider::new(provider, observer, point);
2107 architecture.forward_unit_parallel_with_provider(
2108 group,
2109 index,
2110 unit,
2111 &input,
2112 state,
2113 forward,
2114 pass,
2115 &mut observed,
2116 parallel,
2117 context,
2118 )
2119 }
2120 None => architecture.forward_unit_parallel_with_provider(
2121 group, index, unit, &input, state, forward, pass, provider, parallel,
2122 context,
2123 ),
2124 }?;
2125 observe_and_intervene(observer, &format!("{path}.output"), &output)
2126 },
2127 )
2128 }
2129
2130 pub fn forward_parallel_with_unit_executor_and_context_hook<'a, E, H>(
2132 &mut self,
2133 input: A::Input<'a>,
2134 state: &mut S,
2135 parallel: &B::ParallelContext,
2136 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2137 execute: E,
2138 hook: H,
2139 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2140 where
2141 A: ParallelLayeredArchitecture<B, S>,
2142 E: FnMut(
2143 &mut A,
2144 usize,
2145 usize,
2146 &mut A::Unit,
2147 &B::Tensor,
2148 &mut S,
2149 &mut A::ForwardContext,
2150 &B::ParallelContext,
2151 &<B::Tensor as eredu_nn::Tensor>::Context,
2152 ) -> Result<B::Tensor, A::Error>,
2153 H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
2154 {
2155 self.forward_parallel_with_unit_executor_and_traversal_hook(
2156 input,
2157 state,
2158 parallel,
2159 context,
2160 execute,
2161 &mut AfterUnitContextTraversalHook { after_unit: hook },
2162 )
2163 }
2164
2165 pub fn forward_parallel_with_traversal_hook<'a, H>(
2167 &mut self,
2168 input: A::Input<'a>,
2169 state: &mut S,
2170 parallel: &B::ParallelContext,
2171 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2172 hook: &mut H,
2173 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2174 where
2175 A: ParallelLayeredArchitecture<B, S>,
2176 H: LayeredTraversalHook<B, A::ForwardContext, A::Error>,
2177 {
2178 self.forward_parallel_with_unit_executor_and_traversal_hook(
2179 input,
2180 state,
2181 parallel,
2182 context,
2183 |architecture, group, index, unit, hidden, state, forward, parallel, context| {
2184 architecture.forward_unit_parallel(
2185 group, index, unit, hidden, state, forward, parallel, context,
2186 )
2187 },
2188 hook,
2189 )
2190 }
2191
2192 pub fn forward_parallel_with_unit_executor_and_traversal_hook<'a, E, H>(
2194 &mut self,
2195 input: A::Input<'a>,
2196 state: &mut S,
2197 parallel: &B::ParallelContext,
2198 context: &<B::Tensor as eredu_nn::Tensor>::Context,
2199 mut execute: E,
2200 hook: &mut H,
2201 ) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
2202 where
2203 A: ParallelLayeredArchitecture<B, S>,
2204 E: FnMut(
2205 &mut A,
2206 usize,
2207 usize,
2208 &mut A::Unit,
2209 &B::Tensor,
2210 &mut S,
2211 &mut A::ForwardContext,
2212 &B::ParallelContext,
2213 &<B::Tensor as eredu_nn::Tensor>::Context,
2214 ) -> Result<B::Tensor, A::Error>,
2215 H: LayeredTraversalHook<B, A::ForwardContext, A::Error>,
2216 {
2217 let graph = self
2218 .architecture
2219 .execution_graph()
2220 .map_err(LayerwiseRuntimeError::Architecture)?;
2221 let counts = (0..graph.groups().len())
2222 .map(|group| {
2223 self.architecture
2224 .group_unit_count(group)
2225 .map_err(LayerwiseRuntimeError::Architecture)
2226 })
2227 .collect::<Result<Vec<_>, _>>()?;
2228 let layout = ExecutionUnitLayout::new(&graph, counts)?;
2229 if self.executors.as_ref().map(Vec::len) != Some(graph.groups().len()) {
2230 self.executors = Some(
2231 B::fork_executors(context, graph.groups().len())
2232 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
2233 );
2234 }
2235 let executors = self
2236 .executors
2237 .as_ref()
2238 .expect("layered runtime initialized its executor cache");
2239 let forward = self
2240 .architecture
2241 .begin_forward_parallel(input, state, parallel, context)
2242 .map_err(LayerwiseRuntimeError::Architecture)?;
2243 let initial_completion = (graph.groups().len() > 1)
2244 .then(|| B::submit(context, [&forward.hidden]))
2245 .transpose()
2246 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2247 let mut policy = LayerwisePolicyForward::begin(&mut self.policy, &forward.hidden, context)
2248 .map_err(LayerwiseRuntimeError::Policy)?;
2249 let initial = forward.hidden;
2250 let mut forward_context = forward.context;
2251 let mut schedule = ExecutionGroupSchedule::new(&graph);
2252 let mut outputs: Vec<Option<B::Tensor>> = vec![None; graph.groups().len()];
2253 let mut completions: Vec<Option<B::Completion>> =
2254 (0..graph.groups().len()).map(|_| None).collect();
2255 for &group in graph.execution_order() {
2256 let executor = std::borrow::Borrow::borrow(&executors[group]);
2257 let group_dependencies = schedule
2258 .dependencies(group)
2259 .expect("validated execution order contains a known group");
2260 if group_dependencies.is_empty() {
2261 if let Some(completion) = &initial_completion {
2262 B::order_after(completion, executor)
2263 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2264 }
2265 }
2266 for &dependency in group_dependencies {
2267 B::order_after(
2268 completions[dependency]
2269 .as_ref()
2270 .expect("topological dependency has a completion"),
2271 executor,
2272 )
2273 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2274 }
2275 let dependencies = schedule
2276 .dependencies(group)
2277 .expect("validated execution order contains a known group")
2278 .iter()
2279 .map(|&dependency| {
2280 outputs[dependency]
2281 .as_ref()
2282 .expect("topological dependency has completed")
2283 .clone()
2284 })
2285 .collect::<Vec<_>>();
2286 let dependency_refs = dependencies.iter().collect::<Vec<_>>();
2287 let mut hidden = self
2288 .architecture
2289 .begin_execution_group_parallel(
2290 group,
2291 &initial,
2292 &dependency_refs,
2293 state,
2294 &mut forward_context,
2295 parallel,
2296 executor,
2297 )
2298 .map_err(LayerwiseRuntimeError::Architecture)?;
2299 for dependency in schedule
2300 .started(group)
2301 .expect("topological execution starts only ready groups")
2302 {
2303 outputs[dependency] = None;
2304 }
2305 if self
2306 .architecture
2307 .should_execute_group(group, &forward_context)
2308 {
2309 let unit_count = layout
2310 .group_range(group)
2311 .expect("layout covers every graph group")
2312 .len();
2313 for index in 0..unit_count {
2314 if hook
2315 .before_unit(
2316 group,
2317 index,
2318 unit_count - index,
2319 &hidden,
2320 &mut forward_context,
2321 executor,
2322 )
2323 .map_err(LayerwiseRuntimeError::Architecture)?
2324 == LayeredUnitAction::SkipRemainingGroup
2325 {
2326 break;
2327 }
2328 let ordinal = layout
2329 .ordinal(group, index)
2330 .expect("group-local unit belongs to the layout");
2331 let address = layout
2332 .address(ordinal)
2333 .expect("group-local unit has a stable policy address");
2334 let lease = policy
2335 .acquire(ordinal, address, |executor| {
2336 self.architecture.build_unit(group, index, executor)
2337 })
2338 .map_err(|error| match error {
2339 LayerwiseAcquireError::Architecture(error) => {
2340 LayerwiseRuntimeError::Architecture(error)
2341 }
2342 LayerwiseAcquireError::Policy(error) => {
2343 LayerwiseRuntimeError::Policy(error)
2344 }
2345 })?;
2346 hidden = execute(
2347 &mut self.architecture,
2348 group,
2349 index,
2350 lease,
2351 &hidden,
2352 state,
2353 &mut forward_context,
2354 parallel,
2355 executor,
2356 )
2357 .map_err(LayerwiseRuntimeError::Architecture)?;
2358 hook.after_unit(group, index, &hidden, &mut forward_context, executor)
2359 .map_err(LayerwiseRuntimeError::Architecture)?;
2360 let mut state_values = Vec::new();
2361 for state_ordinal in self
2362 .architecture
2363 .retained_state_ordinals(group, index, ordinal)
2364 {
2365 state_values.extend(
2366 state
2367 .retained_values(state_ordinal, address.with_index(state_ordinal))
2368 .map_err(LayerwiseRuntimeError::State)?,
2369 );
2370 }
2371 let context_values =
2372 self.architecture
2373 .retained_context_values(&forward_context, group, index);
2374 policy
2375 .complete(&hidden, state_values.into_iter(), context_values)
2376 .map_err(LayerwiseRuntimeError::Policy)?;
2377 }
2378 }
2379 hidden = self
2380 .architecture
2381 .complete_execution_group_parallel(
2382 group,
2383 &hidden,
2384 state,
2385 &mut forward_context,
2386 parallel,
2387 executor,
2388 )
2389 .map_err(LayerwiseRuntimeError::Architecture)?;
2390 hook.after_group(group, &hidden, &mut forward_context, executor)
2391 .map_err(LayerwiseRuntimeError::Architecture)?;
2392 outputs[group] = Some(hidden);
2393 if graph.groups().len() > 1 {
2394 completions[group] = Some(
2395 B::submit(
2396 executor,
2397 [outputs[group]
2398 .as_ref()
2399 .expect("group output was stored before submission")],
2400 )
2401 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
2402 );
2403 }
2404 schedule
2405 .ordered(group)
2406 .expect("started group can be ordered exactly once");
2407 }
2408 let hidden = outputs[graph.output()]
2409 .take()
2410 .expect("validated graph output completed");
2411 if let Some(completion) = &completions[graph.output()] {
2412 B::order_after(completion, context)
2413 .map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
2414 }
2415 let output = self
2416 .architecture
2417 .finish_forward_parallel(&hidden, state, &forward_context, parallel, context)
2418 .map_err(LayerwiseRuntimeError::Architecture)?;
2419 policy
2420 .finish(&output)
2421 .map_err(LayerwiseRuntimeError::Policy)?;
2422 Ok((output, forward_context))
2423 }
2424}
2425
2426pub struct ResidentUnitLease<U> {
2428 index: usize,
2429 unit: U,
2430}
2431
2432impl<U> std::ops::Deref for ResidentUnitLease<U> {
2433 type Target = U;
2434
2435 fn deref(&self) -> &Self::Target {
2436 &self.unit
2437 }
2438}
2439
2440impl<U> std::ops::DerefMut for ResidentUnitLease<U> {
2441 fn deref_mut(&mut self) -> &mut Self::Target {
2442 &mut self.unit
2443 }
2444}
2445
2446pub struct ResidentUnitWindow<U> {
2448 units: Vec<Option<U>>,
2449}
2450
2451impl<U> ResidentUnitWindow<U> {
2452 pub fn new(units: Vec<U>) -> Self {
2454 Self {
2455 units: units.into_iter().map(Some).collect(),
2456 }
2457 }
2458}
2459
2460impl<B, U> LayerwisePolicy<B, U> for ResidentUnitWindow<U>
2461where
2462 B: NeuralBackend,
2463{
2464 type Lease = ResidentUnitLease<U>;
2465 type Error = ResidentUnitWindowError;
2466
2467 fn begin(
2468 &mut self,
2469 _initial: &B::Tensor,
2470 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2471 ) -> Result<(), Self::Error> {
2472 Ok(())
2473 }
2474
2475 fn abort(
2476 &mut self,
2477 active: Option<(usize, crate::ExecutionUnitAddress, Self::Lease)>,
2478 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2479 ) {
2480 let Some((ordinal, _, lease)) = active else {
2481 return;
2482 };
2483 debug_assert_eq!(lease.index, ordinal);
2484 if let Some(slot) = self.units.get_mut(lease.index) {
2485 debug_assert!(slot.is_none());
2486 if slot.is_none() {
2487 *slot = Some(lease.unit);
2488 }
2489 }
2490 }
2491
2492 fn acquire<E, F>(
2493 &mut self,
2494 index: usize,
2495 _address: crate::ExecutionUnitAddress,
2496 _build: F,
2497 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2498 ) -> Result<Self::Lease, LayerwiseAcquireError<E, Self::Error>>
2499 where
2500 F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>,
2501 {
2502 let count = self.units.len();
2503 let unit = self
2504 .units
2505 .get_mut(index)
2506 .ok_or(ResidentUnitWindowError::UnknownUnit { index, count })
2507 .map_err(LayerwiseAcquireError::Policy)?
2508 .take()
2509 .ok_or(ResidentUnitWindowError::AlreadyAcquired { index })
2510 .map_err(LayerwiseAcquireError::Policy)?;
2511 Ok(ResidentUnitLease { index, unit })
2512 }
2513
2514 fn complete<'a, StateValues, ContextValues>(
2515 &mut self,
2516 index: usize,
2517 _address: crate::ExecutionUnitAddress,
2518 lease: Self::Lease,
2519 _output: &'a B::Tensor,
2520 _state_values: StateValues,
2521 _context_values: ContextValues,
2522 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2523 ) -> Result<(), Self::Error>
2524 where
2525 B::Tensor: 'a,
2526 StateValues: Iterator<Item = &'a B::Tensor>,
2527 ContextValues: Iterator<Item = &'a B::Tensor>,
2528 {
2529 if lease.index != index {
2530 return Err(ResidentUnitWindowError::MismatchedUnit {
2531 expected: index,
2532 actual: lease.index,
2533 });
2534 }
2535 let slot = self
2536 .units
2537 .get_mut(index)
2538 .expect("acquired unit index remains in the window");
2539 if slot.replace(lease.unit).is_some() {
2540 return Err(ResidentUnitWindowError::AlreadyResident { index });
2541 }
2542 Ok(())
2543 }
2544
2545 fn finish(
2546 &mut self,
2547 _output: &B::Tensor,
2548 _context: &<B::Tensor as eredu_nn::Tensor>::Context,
2549 ) -> Result<(), Self::Error> {
2550 Ok(())
2551 }
2552}
2553
2554#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
2556pub enum ResidentUnitWindowError {
2557 #[error("unit {index} is outside the {count}-unit window")]
2559 UnknownUnit {
2560 index: usize,
2562 count: usize,
2564 },
2565 #[error("unit {index} is already acquired")]
2567 AlreadyAcquired {
2568 index: usize,
2570 },
2571 #[error("unit completion expected {expected}, received {actual}")]
2573 MismatchedUnit {
2574 expected: usize,
2576 actual: usize,
2578 },
2579 #[error("unit {index} is already resident")]
2581 AlreadyResident {
2582 index: usize,
2584 },
2585}
2586
2587#[cfg(test)]
2588mod tests {
2589 use super::{ArchitectureGroupKind, LayeredPipelineSchedule, LayeredPipelineScheduleError};
2590 use crate::{ExecutionGraph, ExecutionGroupSpec, ExecutionScheduleError};
2591
2592 fn pipeline_graph() -> ExecutionGraph {
2593 ExecutionGraph::new(
2594 vec![
2595 ExecutionGroupSpec::root("vision"),
2596 ExecutionGroupSpec::root("audio"),
2597 ExecutionGroupSpec::with_dependencies("projector", ["vision"]),
2598 ExecutionGroupSpec::with_dependencies("merge", ["projector", "audio"]),
2599 ExecutionGroupSpec::with_dependencies("decoder", ["merge"]),
2600 ExecutionGroupSpec::with_dependencies("prediction", ["decoder"]),
2601 ],
2602 "prediction",
2603 )
2604 .unwrap()
2605 }
2606
2607 #[test]
2608 fn pipeline_schedule_owns_activity_propagation_and_ready_batches() {
2609 let graph = pipeline_graph();
2610 let contracts = [
2611 (ArchitectureGroupKind::VisionEncoder, true),
2612 (ArchitectureGroupKind::AudioEncoder, true),
2613 (ArchitectureGroupKind::Projector, false),
2614 (ArchitectureGroupKind::Merger, false),
2615 (ArchitectureGroupKind::Decoder, false),
2616 (ArchitectureGroupKind::Prediction, false),
2617 ];
2618 let mut queried = Vec::new();
2619 let mut schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |group| {
2620 queried.push(group);
2621 Ok::<_, LayeredPipelineScheduleError>(group == 0)
2622 })
2623 .unwrap();
2624
2625 assert_eq!(queried, [0, 1]);
2626 assert_eq!(schedule.activity(), [true, false, true, true, true, false]);
2627 assert_eq!(schedule.compatible_batch(|_, _| true), [0, 1]);
2628 schedule.started(0).unwrap();
2629 schedule.started(1).unwrap();
2630 schedule.ordered(0).unwrap();
2631 schedule.ordered(1).unwrap();
2632 assert_eq!(schedule.ready_groups().collect::<Vec<_>>(), [2]);
2633 for group in [2, 3, 4, 5] {
2634 schedule.started(group).unwrap();
2635 schedule.ordered(group).unwrap();
2636 }
2637 assert!(schedule.is_complete());
2638 }
2639
2640 #[test]
2641 fn pipeline_schedule_rejects_kind_and_transition_drift() {
2642 let graph = pipeline_graph();
2643 let error = LayeredPipelineSchedule::try_new(
2644 &graph,
2645 [(ArchitectureGroupKind::Decoder, false)],
2646 |_| Ok::<_, LayeredPipelineScheduleError>(true),
2647 )
2648 .unwrap_err();
2649 assert_eq!(
2650 error,
2651 LayeredPipelineScheduleError::GroupContractCount {
2652 graph: 6,
2653 declared: 1,
2654 }
2655 );
2656
2657 let contracts = [
2658 (ArchitectureGroupKind::VisionEncoder, true),
2659 (ArchitectureGroupKind::AudioEncoder, true),
2660 (ArchitectureGroupKind::Projector, false),
2661 (ArchitectureGroupKind::Merger, false),
2662 (ArchitectureGroupKind::Decoder, false),
2663 (ArchitectureGroupKind::Prediction, false),
2664 ];
2665 let mut schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |_| {
2666 Ok::<_, LayeredPipelineScheduleError>(true)
2667 })
2668 .unwrap();
2669 assert_eq!(
2670 schedule.started(2),
2671 Err(LayeredPipelineScheduleError::Transition(
2672 ExecutionScheduleError::DependenciesPending { group: 2 }
2673 ))
2674 );
2675 }
2676
2677 #[test]
2678 fn pipeline_schedule_consumes_declared_request_optionality() {
2679 let graph = ExecutionGraph::new(
2680 vec![
2681 ExecutionGroupSpec::root("mandatory_vision"),
2682 ExecutionGroupSpec::root("optional_audio"),
2683 ExecutionGroupSpec::with_dependencies(
2684 "decoder",
2685 ["mandatory_vision", "optional_audio"],
2686 ),
2687 ],
2688 "decoder",
2689 )
2690 .unwrap();
2691 let contracts = [
2692 (ArchitectureGroupKind::VisionEncoder, false),
2693 (ArchitectureGroupKind::AudioEncoder, true),
2694 (ArchitectureGroupKind::Decoder, false),
2695 ];
2696 let mut queried = Vec::new();
2697 let schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |group| {
2698 queried.push(group);
2699 Ok::<_, LayeredPipelineScheduleError>(false)
2700 })
2701 .unwrap();
2702
2703 assert_eq!(queried, [1]);
2704 assert_eq!(schedule.activity(), [true, false, true]);
2705
2706 let invalid = [
2707 (ArchitectureGroupKind::VisionEncoder, false),
2708 (ArchitectureGroupKind::AudioEncoder, false),
2709 (ArchitectureGroupKind::Decoder, true),
2710 ];
2711 assert_eq!(
2712 LayeredPipelineSchedule::try_new(&graph, invalid, |_| {
2713 Ok::<_, LayeredPipelineScheduleError>(true)
2714 })
2715 .unwrap_err(),
2716 LayeredPipelineScheduleError::InvalidRequestOptionalGroup {
2717 group: 2,
2718 kind: ArchitectureGroupKind::Decoder,
2719 }
2720 );
2721 }
2722}