Skip to main content

eredu_runtime/
layered.rs

1//! Statically dispatched layered-architecture lifecycle and resident execution.
2
3#![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
17/// Statically dispatched visitor over one immutable pinned parameter module.
18pub trait StaticParameterVisitor<B: NeuralBackend> {
19    /// Failure returned by the consumer.
20    type Error;
21
22    /// Visits the module bound to one architecture-declared static role.
23    fn visit<M>(&mut self, role: &str, module: &M) -> Result<(), Self::Error>
24    where
25        M: Parameterized<B::Tensor>;
26}
27
28/// Statically dispatched visitor over one mutable pinned parameter module.
29pub trait StaticParameterVisitorMut<B: NeuralBackend> {
30    /// Failure returned by the consumer.
31    type Error;
32
33    /// Visits the mutable module bound to one architecture-declared static role.
34    fn visit_mut<M>(&mut self, role: &str, module: &mut M) -> Result<(), Self::Error>
35    where
36        M: Parameterized<B::Tensor>;
37}
38
39/// Architecture-owned enumeration and binding of pinned parameter modules.
40///
41/// Parameter descriptions select the roles owned by a partition. This
42/// contract resolves those roles to concrete neutral modules without making a
43/// backend know family fields or checkpoint roots.
44pub trait ArchitectureParameters<B: NeuralBackend> {
45    /// Architecture-owned failure while deriving geometry or topology.
46    type DefinitionError;
47
48    /// Returns the authoritative mutable-state geometry for this realization.
49    fn state_layout(&self) -> Result<StateLayout, Self::DefinitionError>;
50
51    /// Declares cache-relevant architecture identity for one realized state partition.
52    ///
53    /// The partition supplies the exact rank-local layout and architecture-global
54    /// offset. Concrete backends supply only their lowered parallel topology.
55    fn state_identity(
56        &self,
57        state: &crate::PartitionState,
58        topology: eredu_core::cache::PromptCacheTopology,
59    ) -> Result<crate::ModelStateIdentity, Self::DefinitionError>;
60
61    /// Describes every parameter with its canonical graph owner and placement.
62    fn parameter_description(
63        &self,
64        context: &<B::Tensor as eredu_nn::Tensor>::Context,
65    ) -> Result<crate::ArchitectureParameterDescription, Self::DefinitionError>;
66
67    /// Returns architecture-owned checkpoint rewrites for pinned parameters.
68    fn static_parameter_recipes(
69        &self,
70        _source: &dyn CheckpointSource,
71    ) -> Result<BTreeMap<String, DerivedWeightRecipe>, String> {
72        Ok(BTreeMap::new())
73    }
74
75    /// Visits every available pinned parameter module exactly once.
76    fn visit_static_parameters<V>(&self, visitor: &mut V) -> Result<(), V::Error>
77    where
78        V: StaticParameterVisitor<B>;
79
80    /// Mutably visits every available pinned parameter module exactly once.
81    fn visit_static_parameters_mut<V>(&mut self, visitor: &mut V) -> Result<(), V::Error>
82    where
83        V: StaticParameterVisitorMut<B>;
84}
85
86/// Backend-native activation and architecture-owned forward context.
87pub struct LayeredForwardState<T, C> {
88    /// Initial activation supplied to the first execution unit.
89    pub hidden: T,
90    /// Masks, positions, or other architecture-owned forward values.
91    pub context: C,
92}
93
94/// Architecture-authored semantic kind for one transport-visible execution group.
95#[derive(Debug, Clone, Copy, Eq, PartialEq)]
96pub enum ArchitectureGroupKind {
97    /// Primary text decoding.
98    Decoder,
99    /// Embedded prediction after the primary decoder output.
100    Prediction,
101    /// Visual encoding.
102    VisionEncoder,
103    /// Audio encoding.
104    AudioEncoder,
105    /// Learned modality projection.
106    Projector,
107    /// Learned or structural modality merge.
108    Merger,
109    /// Final multimodal assembly.
110    ModalityFinalization,
111}
112
113/// Backend-neutral lifecycle for the pipeline ingress phase of a layered graph.
114///
115/// Architectures declare semantic group kinds and request optionality while
116/// concrete backends report only whether optional encoder roots have work. The
117/// runtime derives all downstream activity, admits dependency-ready compatible
118/// batches, and owns completion transitions. Pipeline backends therefore share
119/// the same graph lifecycle instead of reconstructing it around native streams
120/// and routes.
121#[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    /// Creates the pipeline ingress lifecycle from canonical architecture contracts.
131    ///
132    /// Each contract pairs a group's semantic kind with its declared request
133    /// optionality. `request_group_active` is called only for optional encoder
134    /// roots. Mandatory encoders, decoder ingress, and finalization always run;
135    /// structural merge activity is derived from dependency activity; and
136    /// prediction is a later phase.
137    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    /// Returns whether a group participates in this pipeline ingress pass.
192    pub fn is_active(&self, group: usize) -> Option<bool> {
193        self.active.get(group).copied()
194    }
195
196    /// Returns all group activity in canonical architecture order.
197    pub fn activity(&self) -> &[bool] {
198        &self.active
199    }
200
201    /// Returns dependency-ready groups in stable architecture order.
202    pub fn ready_groups(&self) -> impl Iterator<Item = usize> + '_ {
203        self.schedule.startable_groups()
204    }
205
206    /// Selects a deterministic maximal compatible subset of ready groups.
207    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    /// Returns dependency slots in architecture declaration order.
222    pub fn dependencies(&self, group: usize) -> Option<&[usize]> {
223        self.graph.dependencies(group)
224    }
225
226    /// Commits architecture setup for a dependency-ready group.
227    ///
228    /// The returned producer slots no longer need to retain their outputs for
229    /// another consumer after this group has captured its dependencies.
230    pub fn started(&mut self, group: usize) -> Result<Vec<usize>, LayeredPipelineScheduleError> {
231        self.schedule.started(group).map_err(Into::into)
232    }
233
234    /// Commits one successfully submitted group and unlocks its dependents.
235    pub fn ordered(&mut self, group: usize) -> Result<(), LayeredPipelineScheduleError> {
236        self.schedule.ordered(group)?;
237        self.completed += 1;
238        Ok(())
239    }
240
241    /// Returns whether every architecture group has completed this phase.
242    pub fn is_complete(&self) -> bool {
243        self.completed == self.active.len()
244    }
245}
246
247/// Invalid backend-neutral pipeline lifecycle declaration or transition.
248#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
249pub enum LayeredPipelineScheduleError {
250    /// The physical realization did not preserve one contract per canonical group.
251    #[error(
252        "execution graph contains {graph} groups but the pipeline declared {declared} group contracts"
253    )]
254    GroupContractCount {
255        /// Number of canonical graph groups.
256        graph: usize,
257        /// Number of supplied lifecycle contracts.
258        declared: usize,
259    },
260    /// Request optionality was attached to a group which cannot consume request media directly.
261    #[error("execution group {group} of kind {kind:?} cannot be request-optional")]
262    InvalidRequestOptionalGroup {
263        /// Canonical architecture group slot.
264        group: usize,
265        /// Declared semantic group kind.
266        kind: ArchitectureGroupKind,
267    },
268    /// An ordinary execution-group lifecycle invariant failed.
269    #[error(transparent)]
270    Transition(#[from] ExecutionScheduleError),
271}
272
273/// Pipeline ownership policy for one architecture execution group.
274#[derive(Debug, Clone, Copy, Eq, PartialEq)]
275pub enum ArchitectureGroupPlacement {
276    /// Balance the group across every pipeline owner.
277    Pipeline,
278    /// Place the complete group on the architecture output owner.
279    OutputOwner,
280}
281
282/// Architecture-level merge destination resolved by a concrete pipeline topology.
283#[derive(Debug, Clone, Copy, Eq, PartialEq)]
284pub enum ArchitectureMergeDestination {
285    /// Use the group's terminal owner.
286    LastOwner,
287    /// Return the result to the first pipeline owner for dependency assembly.
288    FirstPipelineOwner,
289    /// Deliver the result to the architecture output owner.
290    OutputOwner,
291}
292
293/// Cartesian subgroup semantics required while a group executes.
294#[derive(Debug, Clone, Copy, Eq, PartialEq)]
295pub enum ArchitectureParallelSubgroup {
296    /// Tensor sharding without routed expert exchange.
297    TensorSharded,
298    /// Decoder tensor and routed-expert parallelism.
299    Decoder,
300}
301
302/// Backend-neutral transport and placement semantics for one execution group.
303#[derive(Debug, Clone, Eq, PartialEq)]
304pub struct ArchitectureGroupTransport {
305    /// Physical pipeline ownership policy.
306    pub placement: ArchitectureGroupPlacement,
307    /// Semantic compute kind.
308    pub kind: ArchitectureGroupKind,
309    /// Static roles owned by the group's first physical owner.
310    pub first_owner_static_roles: Vec<String>,
311    /// Static roles owned by the group's terminal physical owner.
312    pub last_owner_static_roles: Vec<String>,
313    /// Dependency merge destination.
314    pub merge_destination: ArchitectureMergeDestination,
315    /// Optional active Cartesian subgroup contract.
316    pub parallel_subgroup: Option<ArchitectureParallelSubgroup>,
317    /// Whether request media may omit this root encoder group entirely.
318    pub request_optional: bool,
319}
320
321/// Stable layered traversal boundary exposed to generic runtime drivers.
322#[derive(Debug, Clone, Copy, Eq, PartialEq)]
323pub enum LayeredTraversalPoint {
324    /// Output of one execution unit before the next unit starts.
325    Unit {
326        /// Execution-group index.
327        group: usize,
328        /// Group-local unit index.
329        index: usize,
330    },
331    /// Output of one completed execution group.
332    Group {
333        /// Execution-group index.
334        group: usize,
335    },
336}
337
338/// Decision returned immediately before one execution unit is acquired.
339#[derive(Debug, Clone, Copy, Eq, PartialEq)]
340pub enum LayeredUnitAction {
341    /// Execute this unit normally.
342    Execute,
343    /// Omit this unit and every remaining unit in the current group.
344    SkipRemainingGroup,
345}
346
347/// Statically dispatched hook shared by resident and bounded layered traversal.
348///
349/// The hook can observe unit/group outputs and can omit only a complete group
350/// tail. Drivers are responsible for proving that an omission preserves their
351/// semantics before returning [`LayeredUnitAction::SkipRemainingGroup`].
352pub trait LayeredTraversalHook<B, C, E>
353where
354    B: NeuralBackend,
355{
356    /// Chooses whether to execute the next unit.
357    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    /// Observes and may replace the activation selected for one ready group.
370    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    /// Observes one executed unit output.
381    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    /// Observes one completed execution-group output.
393    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
404/// Statically combines two traversal hooks over one production forward pass.
405///
406/// Both hooks observe every reached boundary in left-to-right order. A unit is
407/// skipped when either hook proves that the remaining group tail can be
408/// omitted; errors stop delegation before any later callback is invoked.
409pub struct CompositeLayeredTraversalHook<L, R> {
410    left: L,
411    right: R,
412}
413
414impl<L, R> CompositeLayeredTraversalHook<L, R> {
415    /// Creates one ordered pair of traversal hooks.
416    pub const fn new(left: L, right: R) -> Self {
417        Self { left, right }
418    }
419
420    /// Returns both hooks after traversal.
421    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 = format!("{}.input", 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 = format!("{}.output", 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
611/// Backend-neutral lifecycle implemented once by a layered architecture.
612///
613/// All hot values remain concrete associated types. Resident and bounded
614/// runtime policies call these same methods without erasing tensors, units, or
615/// mutable layer state.
616pub trait LayeredArchitecture<B, S>:
617    ArchitectureParameters<B, DefinitionError = Self::Error>
618where
619    B: NeuralBackend,
620    S: RuntimeState<B>,
621{
622    /// Borrowed prepared model input.
623    type Input<'a>
624    where
625        Self: 'a;
626    /// Pinned model modules such as embeddings, final normalization, and head.
627    type StaticModules: Parameterized<B::Tensor>;
628    /// One ordered execution unit.
629    type Unit: Parameterized<B::Tensor>;
630    /// Architecture-owned state retained for one complete forward pass.
631    type ForwardContext;
632    /// Allocation-free iterator over transient tensors retained by a unit submission.
633    type RetainedContextValues<'a>: Iterator<Item = &'a B::Tensor>
634    where
635        Self: 'a,
636        B::Tensor: 'a;
637    /// Concrete architecture or backend failure.
638    type Error;
639
640    /// Declares transport and physical placement semantics for one canonical group slot.
641    fn group_transport(&self, group: usize) -> ArchitectureGroupTransport;
642
643    /// Returns the stable identifier of the primary pipeline execution group.
644    ///
645    /// Pipeline composition resolves this identifier against [`Self::execution_graph`]
646    /// instead of guessing the primary group from its semantic kind. Architectures may
647    /// therefore declare multiple decoder-shaped groups without making composition
648    /// dependent on declaration order.
649    fn primary_execution_group(&self) -> &str;
650
651    /// Returns stable identifiers for ordered embedded-prediction groups.
652    ///
653    /// The order is the architecture's prediction-depth order. Semantic group kinds
654    /// remain lifecycle metadata and are not used as group addresses.
655    fn prediction_execution_groups(&self) -> Vec<String> {
656        Vec::new()
657    }
658
659    /// Borrows the exact ordinary-target value consumed by an additive prediction extension.
660    fn prediction_target_capture(_context: &Self::ForwardContext) -> Option<&B::Tensor> {
661        None
662    }
663
664    /// Declares the exact capture placeholder submitted by a non-output pipeline rank.
665    fn prediction_target_placeholder_shape(
666        &self,
667        _forward: &Self::ForwardContext,
668    ) -> Result<Option<Vec<i32>>, Self::Error> {
669        Ok(None)
670    }
671
672    /// Declares how the complete mutable-state layout is divided among realized partitions.
673    fn state_partition_plan(&self, layout: &StateLayout) -> crate::ArchitectureStatePartitionPlan;
674
675    /// Declares the dependency graph between ordered execution groups.
676    fn execution_graph(&self) -> Result<ExecutionGraph, Self::Error>;
677
678    /// Returns the number of ordered execution units in one graph group.
679    fn group_unit_count(&self, group: usize) -> Result<usize, Self::Error>;
680
681    /// Returns the stable architecture-owned path of one group-local execution unit.
682    fn unit_path(&self, group: usize, index: usize) -> Result<String, Self::Error>;
683
684    /// Architecture-owned name for the activation selected at group ingress.
685    fn group_input_observation_path(&self, _group: usize) -> Result<Option<String>, Self::Error> {
686        Ok(None)
687    }
688
689    /// Architecture-owned name for the activation emitted after group completion.
690    fn group_output_observation_path(&self, _group: usize) -> Result<Option<String>, Self::Error> {
691        Ok(None)
692    }
693
694    /// Borrows pinned modules for parameter discovery and binding.
695    fn static_modules(&self) -> &Self::StaticModules;
696
697    /// Mutably borrows pinned modules for parameter binding.
698    fn static_modules_mut(&mut self) -> &mut Self::StaticModules;
699
700    /// Builds one unloaded execution unit using backend-native operators.
701    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    /// Embeds input and prepares architecture-owned forward values.
709    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    /// Selects or merges the activation consumed by one ready execution group.
717    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    /// Returns whether one ready group is needed for this forward pass.
728    fn should_execute_group(&self, _group: usize, _forward: &Self::ForwardContext) -> bool {
729        true
730    }
731
732    /// Maps one execution unit to its architecture-global mutable-state slot.
733    ///
734    /// The default matches a single flattened decoder schedule. Composite
735    /// architectures can keep parameter-only groups outside their state layout
736    /// and remap later groups onto their semantic decoder or predictor layers.
737    fn state_ordinal(&self, _group: usize, _index: usize, ordinal: usize) -> usize {
738        ordinal
739    }
740
741    /// Returns every architecture-global state slot retained by one unit.
742    ///
743    /// The default retains the single slot returned by [`Self::state_ordinal`].
744    /// Composite units can return a contiguous range when one residency unit
745    /// internally executes several stateful layers.
746    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    /// Executes one ordered unit against its concrete mutable layer state.
757    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    /// Converts a completed group's output into its dependency-facing value.
769    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    /// Applies final normalization and output projection.
781    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    /// Borrows transient forward tensors required by one unit's submission.
790    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
798/// Optional statically dispatched parallel lifecycle for a layered architecture.
799///
800/// The runtime owns traversal and exact unit completion while the architecture
801/// owns parallel embedding, block, and output semantics. Backend-native
802/// collective contexts cross this boundary unchanged.
803pub trait ParallelLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
804where
805    B: NeuralBackend,
806    S: RuntimeState<B>,
807{
808    /// Embeds input and prepares forward values for rank-local execution.
809    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    /// Executes one rank-local unit and its required collectives.
818    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    /// Selects or merges a ready group's activation under a parallel context.
831    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    /// Converts a completed group's output under a parallel context.
845    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    /// Applies the rank-local output projection and returns complete logits.
858    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/// Input accepted at a rank-local layered partition boundary.
869///
870/// A partition either embeds borrowed token ids or consumes an owned hidden
871/// tensor prepared by architecture ingress or a preceding pipeline owner.
872#[derive(Debug)]
873pub enum LayeredPartitionInput<'a, T, A = NoAuxiliaryBoundary> {
874    /// Token ids supplied to the architecture input owner.
875    Tokens(&'a T),
876    /// Architecture-prepared or upstream hidden state.
877    Hidden {
878        /// Evolving activation received from the preceding owner.
879        hidden: T,
880        /// Architecture-typed context carried across the partition boundary.
881        auxiliary: A,
882    },
883}
884
885/// Architecture-owned result of completing one layered partition.
886///
887/// The runtime and concrete transports only distinguish a final output from a
888/// transport boundary. Families retain ownership of auxiliary boundary values
889/// and of any hidden activation required by an embedded predictor.
890pub enum LayeredPartitionOutput<T, A = NoAuxiliaryBoundary> {
891    /// Complete architecture output produced by the output owner.
892    Final {
893        /// Projected architecture output, normally vocabulary logits.
894        output: T,
895        /// Optional pre-projection value consumed by an embedded predictor.
896        retained: Option<T>,
897    },
898    /// Values transported to the next pipeline owner.
899    Boundary {
900        /// Evolving activation.
901        hidden: T,
902        /// Architecture-typed auxiliary context.
903        auxiliary: A,
904    },
905}
906
907/// Architecture-owned preparation for rank-local partition execution.
908///
909/// The neutral partition driver owns validation, group sequencing, and output
910/// ownership. Architectures own the semantic conversion of partition inputs,
911/// entry into and completion of their selected execution group, and typed
912/// partition output.
913pub trait PartitionedLayeredArchitecture<B, S>: ParallelLayeredArchitecture<B, S>
914where
915    B: NeuralBackend,
916    S: RuntimeState<B>,
917{
918    /// Architecture-owned schema for primary and auxiliary partition transport.
919    type Boundary: crate::ArchitectureBoundary;
920
921    /// Derives the complete transport schema from the normalized architecture.
922    fn boundary_schema(&self) -> Result<Self::Boundary, Self::Error>;
923
924    /// Prepares a replicated partition from tokens or upstream hidden state.
925    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    /// Prepares the tensor-parallel form of the same partition.
940    #[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    /// Enters the selected execution group after the partition input has been
957    /// prepared. The default is the ordinary layered group entry with no graph
958    /// dependencies; graph architectures may override this when their
959    /// partition input is already assembled.
960    #[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    /// Completes the selected execution group before the architecture emits
985    /// its final value or typed pipeline boundary.
986    #[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    /// Emits an architecture partition after its execution group has closed.
1005    #[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
1023/// Provider-aware unit execution for architectures with routed feed-forward work.
1024///
1025/// Partition drivers retain ownership of expert residency while the neutral
1026/// architecture retains attention, residual, routing, and unit semantics.
1027pub trait RoutedLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
1028where
1029    B: eredu_nn::GroupedNeuralBackend,
1030    S: RuntimeState<B>,
1031{
1032    /// Classifies routed storage access from architecture-visible unit input.
1033    ///
1034    /// Architectures with a nonstandard pass contract may override this
1035    /// method; concrete backends must not derive the semantic pass.
1036    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    /// Returns the architecture-owned routing observation point for one unit.
1056    ///
1057    /// Architectures without observable routed work in the selected unit return
1058    /// `None`. Concrete backends must not reconstruct semantic paths or expert
1059    /// cardinality.
1060    fn routed_observation_point(
1061        &self,
1062        _group: usize,
1063        _index: usize,
1064    ) -> Result<Option<RoutedObservationPoint>, Self::Error> {
1065        Ok(None)
1066    }
1067
1068    /// Executes one unit through a runtime-supplied routed-expert provider.
1069    #[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    /// Executes one unit with architecture-owned pass classification.
1087    #[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    /// Executes one provider-backed unit while exposing its architecture-owned
1110    /// routed/shared/combined observation stages.
1111    #[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    /// Executes one observed provider-backed unit with architecture-owned pass
1153    /// classification.
1154    #[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
1180/// Tensor-parallel provider-aware unit execution.
1181pub trait ParallelRoutedLayeredArchitecture<B, S>:
1182    RoutedLayeredArchitecture<B, S> + ParallelLayeredArchitecture<B, S>
1183where
1184    B: eredu_nn::GroupedNeuralBackend,
1185    S: RuntimeState<B>,
1186{
1187    /// Executes one tensor-parallel unit through a runtime-supplied provider.
1188    #[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
1207/// Fully resident runtime using the same lifecycle as bounded execution.
1208pub 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    /// Builds every execution unit once and keeps it resident.
1227    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    /// Runs one complete prefill or decode pass without dynamic dispatch.
1250    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    /// Runs one complete pass and returns its architecture-owned context.
1261    ///
1262    /// This is the resident counterpart of the bounded runtime's context
1263    /// result and lets callers retain target captures without storing
1264    /// request-local tensors on the model object.
1265    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    /// Runs one resident pass through a statically dispatched traversal hook.
1275    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    /// Borrows the architecture and its pinned parameter topology.
1370    pub const fn architecture(&self) -> &A {
1371        &self.architecture
1372    }
1373
1374    /// Mutably borrows the architecture.
1375    pub fn architecture_mut(&mut self) -> &mut A {
1376        &mut self.architecture
1377    }
1378
1379    /// Borrows resident execution units for loading or inspection.
1380    pub fn units(&self) -> &[Vec<A::Unit>] {
1381        &self.units
1382    }
1383
1384    /// Mutably borrows resident execution units for parameter binding.
1385    pub fn units_mut(&mut self) -> &mut [Vec<A::Unit>] {
1386        &mut self.units
1387    }
1388
1389    /// Decomposes the runtime without cloning backend-native values.
1390    pub fn into_parts(self) -> (A, Vec<A::Unit>) {
1391        (
1392            self.architecture,
1393            self.units.into_iter().flatten().collect(),
1394        )
1395    }
1396}
1397
1398/// Policy controlling acquisition and exact release of one execution unit.
1399pub trait LayerwisePolicy<B, U>
1400where
1401    B: NeuralBackend,
1402{
1403    /// Concrete lease owning one populated unit and all residency guards.
1404    type Lease: std::ops::DerefMut<Target = U>;
1405    /// Concrete acquisition or completion failure.
1406    type Error;
1407
1408    /// Starts one forward after architecture input preparation.
1409    fn begin(
1410        &mut self,
1411        initial: &B::Tensor,
1412        context: &<B::Tensor as eredu_nn::Tensor>::Context,
1413    ) -> Result<(), Self::Error>;
1414
1415    /// Aborts an incomplete forward and releases all policy-owned state.
1416    ///
1417    /// `active` contains the unit lease when execution stopped after
1418    /// acquisition but before exact completion. Implementations with no
1419    /// forward-scoped state may rely on the default, which simply drops it.
1420    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    /// Acquires one populated unit for exclusive execution.
1429    ///
1430    /// The flat ordinal addresses storage while `address` preserves the
1431    /// architecture execution group and group-local unit index for scheduling.
1432    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    /// Retains the unit and dependent native values through exact completion.
1443    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    /// Completes the final output and releases any remaining unit guards.
1459    fn finish(
1460        &mut self,
1461        output: &B::Tensor,
1462        context: &<B::Tensor as eredu_nn::Tensor>::Context,
1463    ) -> Result<(), Self::Error>;
1464}
1465
1466/// Failure-safe ownership of one policy forward and its current unit lease.
1467pub 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    /// Begins one failure-safe policy transaction.
1485    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    /// Acquires one unit and retains its lease until completion or abort.
1504    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    /// Completes and returns the currently active unit lease.
1524    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    /// Completes the transaction; dropping before this call aborts it.
1551    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/// Failure while a layerwise policy acquires or populates one architecture unit.
1571#[derive(Debug)]
1572pub enum LayerwiseAcquireError<A, P> {
1573    /// The neutral architecture could not construct its unloaded unit.
1574    Architecture(A),
1575    /// The execution policy could not acquire residency or populate the unit.
1576    Policy(P),
1577}
1578
1579/// Failure from architecture execution or layerwise residency policy.
1580#[derive(Debug, thiserror::Error)]
1581pub enum LayerwiseRuntimeError<A, P>
1582where
1583    A: std::fmt::Display,
1584    P: std::fmt::Display,
1585{
1586    /// Architecture construction or forward failure.
1587    #[error("layered architecture failed: {0}")]
1588    Architecture(A),
1589    /// Invalid access to architecture-declared mutable state.
1590    #[error(transparent)]
1591    State(#[from] crate::StateError),
1592    /// Architecture execution groups did not map to one stable residency-unit order.
1593    #[error(transparent)]
1594    Layout(#[from] crate::ExecutionUnitLayoutError),
1595    /// Unit acquisition or exact-completion failure.
1596    #[error("layerwise execution policy failed: {0}")]
1597    Policy(P),
1598    /// Backend-native graph submission or dependency ordering failed.
1599    #[error("layerwise backend submission failed: {0}")]
1600    Submission(String),
1601}
1602
1603/// Bounded-unit runtime invoking the same architecture lifecycle as resident execution.
1604pub 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    /// Creates a layerwise runtime from concrete architecture, state, and policy.
1627    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    /// Creates a layerwise runtime while evaluating the policy before moving
1637    /// the architecture. This is useful when policy realization needs to
1638    /// borrow the architecture's canonical unit constructor first.
1639    pub const fn new_policy_first(policy: P, architecture: A) -> Self {
1640        Self::new(architecture, policy)
1641    }
1642
1643    /// Borrows the concrete architecture instance.
1644    pub const fn architecture(&self) -> &A {
1645        &self.architecture
1646    }
1647
1648    /// Mutably borrows the concrete architecture instance.
1649    pub fn architecture_mut(&mut self) -> &mut A {
1650        &mut self.architecture
1651    }
1652
1653    /// Borrows the concrete execution policy for cold-path diagnostics.
1654    pub const fn policy(&self) -> &P {
1655        &self.policy
1656    }
1657
1658    /// Mutably borrows the concrete execution policy.
1659    pub fn policy_mut(&mut self) -> &mut P {
1660        &mut self.policy
1661    }
1662
1663    /// Runs one complete prefill or decode pass with exact unit release points.
1664    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    /// Runs one pass and exposes mutable architecture context after each unit.
1675    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    /// Runs one pass with a statically dispatched architecture-unit executor.
1697    ///
1698    /// Composition can use this cold API to inject routed expert execution or
1699    /// observation while the runtime retains graph traversal, residency, and
1700    /// exact completion ownership.
1701    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    /// Runs the production sequential traversal with stable unit-boundary observation.
1731    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    /// Runs observed sequential traversal and retains architecture forward resources for the
1746    /// caller's completion boundary.
1747    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    /// Runs a custom production unit executor with stable boundary observation.
1769    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    /// Runs a custom observed executor while returning its retained forward resources.
1797    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    /// Runs canonical provider-backed unit execution with unit-boundary and
1860    /// routed-expert observation.
1861    ///
1862    /// Observation wraps [`RoutedLayeredArchitecture::forward_unit_with_provider`]
1863    /// instead of replacing it. Architecture-owned validation, state lookup,
1864    /// shape handling, routing, and provider dispatch therefore remain shared
1865    /// with ordinary execution.
1866    #[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    /// Runs provider-backed observed execution while retaining architecture forward resources.
1891    #[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    /// Runs provider-backed observed execution with architecture-owned pass
1969    /// classification.
1970    #[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    /// Runs provider-backed observed execution with architecture-owned pass
1994    /// classification while retaining forward resources.
1995    #[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    /// Runs one pass with both a custom unit executor and post-unit context hook.
2071    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    /// Runs one pass with a custom unit executor and exposes each post-unit
2102    /// activation together with the mutable architecture context.
2103    ///
2104    /// The activation is the ordinary output of the execution unit. Target
2105    /// state taps and inspection therefore observe the production forward
2106    /// without requiring a second family-specific model path.
2107    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    /// Runs one bounded pass through a statically dispatched traversal hook.
2138    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    /// Runs one bounded pass with custom unit execution and a shared traversal hook.
2160    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    /// Runs one complete rank-local pass through the neutral parallel lifecycle.
2383    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    /// Runs one rank-local pass and exposes mutable context after each unit.
2398    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    /// Runs one parallel pass with a custom statically dispatched unit executor.
2425    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    /// Runs the production parallel traversal with stable unit-boundary observation.
2459    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    /// Runs a custom parallel unit executor with stable boundary observation.
2486    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    /// Runs provider-backed parallel execution with boundary and routing observation.
2535    #[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    /// Runs one parallel pass with custom unit execution and a post-unit hook.
2588    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    /// Runs one parallel pass through a statically dispatched traversal hook.
2623    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    /// Runs one parallel pass with custom unit execution and a shared traversal hook.
2650    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
2885/// Indexed owned unit used by [`ResidentUnitWindow`].
2886pub 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
2905/// Minimal one-at-a-time unit window useful for conformance and resident storage.
2906pub struct ResidentUnitWindow<U> {
2907    units: Vec<Option<U>>,
2908}
2909
2910impl<U> ResidentUnitWindow<U> {
2911    /// Creates a window over an ordered set of already populated units.
2912    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/// Invalid access to an owned resident-unit window.
3014#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
3015pub enum ResidentUnitWindowError {
3016    /// The requested unit is outside the ordered window.
3017    #[error("unit {index} is outside the {count}-unit window")]
3018    UnknownUnit {
3019        /// Requested index.
3020        index: usize,
3021        /// Window size.
3022        count: usize,
3023    },
3024    /// A unit was acquired twice without an intervening completion.
3025    #[error("unit {index} is already acquired")]
3026    AlreadyAcquired {
3027        /// Requested index.
3028        index: usize,
3029    },
3030    /// Completion returned a lease for the wrong unit.
3031    #[error("unit completion expected {expected}, received {actual}")]
3032    MismatchedUnit {
3033        /// Expected unit.
3034        expected: usize,
3035        /// Lease unit.
3036        actual: usize,
3037    },
3038    /// A completion attempted to overwrite a resident unit.
3039    #[error("unit {index} is already resident")]
3040    AlreadyResident {
3041        /// Conflicting index.
3042        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}