#![allow(clippy::too_many_arguments, clippy::type_complexity)]
use std::collections::BTreeMap;
use eredu_checkpoint::{recipe::DerivedWeightRecipe, store::CheckpointSource};
use eredu_nn::{NeuralBackend, Parameterized, Tensor};
use crate::{
observe_and_intervene, ActivationObserver, ExecutionGraph, ExecutionGroupSchedule,
ExecutionScheduleError, ExecutionUnitLayout, ExpertPass, NoAuxiliaryBoundary,
ObservedExpertProvider, RoutedExpertProvider, RoutedObservationPoint, RuntimeState,
StateLayout, SubmissionBackend,
};
pub trait StaticParameterVisitor<B: NeuralBackend> {
type Error;
fn visit<M>(&mut self, role: &str, module: &M) -> Result<(), Self::Error>
where
M: Parameterized<B::Tensor>;
}
pub trait StaticParameterVisitorMut<B: NeuralBackend> {
type Error;
fn visit_mut<M>(&mut self, role: &str, module: &mut M) -> Result<(), Self::Error>
where
M: Parameterized<B::Tensor>;
}
pub trait ArchitectureParameters<B: NeuralBackend> {
type DefinitionError;
fn state_layout(&self) -> Result<StateLayout, Self::DefinitionError>;
fn state_identity(
&self,
state: &crate::PartitionState,
topology: eredu_core::cache::PromptCacheTopology,
) -> Result<crate::ModelStateIdentity, Self::DefinitionError>;
fn parameter_description(
&self,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<crate::ArchitectureParameterDescription, Self::DefinitionError>;
fn static_parameter_recipes(
&self,
_source: &dyn CheckpointSource,
) -> Result<BTreeMap<String, DerivedWeightRecipe>, String> {
Ok(BTreeMap::new())
}
fn visit_static_parameters<V>(&self, visitor: &mut V) -> Result<(), V::Error>
where
V: StaticParameterVisitor<B>;
fn visit_static_parameters_mut<V>(&mut self, visitor: &mut V) -> Result<(), V::Error>
where
V: StaticParameterVisitorMut<B>;
}
pub struct LayeredForwardState<T, C> {
pub hidden: T,
pub context: C,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ArchitectureGroupKind {
Decoder,
Prediction,
VisionEncoder,
AudioEncoder,
Projector,
Merger,
ModalityFinalization,
}
#[derive(Debug)]
pub struct LayeredPipelineSchedule<'a> {
graph: &'a ExecutionGraph,
schedule: ExecutionGroupSchedule<'a>,
active: Vec<bool>,
completed: usize,
}
impl<'a> LayeredPipelineSchedule<'a> {
pub fn try_new<E>(
graph: &'a ExecutionGraph,
group_contracts: impl IntoIterator<Item = (ArchitectureGroupKind, bool)>,
mut request_group_active: impl FnMut(usize) -> Result<bool, E>,
) -> Result<Self, E>
where
E: From<LayeredPipelineScheduleError>,
{
let group_contracts = group_contracts.into_iter().collect::<Vec<_>>();
if group_contracts.len() != graph.groups().len() {
return Err(LayeredPipelineScheduleError::GroupContractCount {
graph: graph.groups().len(),
declared: group_contracts.len(),
}
.into());
}
let mut active = vec![false; group_contracts.len()];
for &group in graph.execution_order() {
let (kind, request_optional) = group_contracts[group];
if request_optional
&& (!matches!(
kind,
ArchitectureGroupKind::VisionEncoder | ArchitectureGroupKind::AudioEncoder
) || !graph.groups()[group].dependencies().is_empty())
{
return Err(LayeredPipelineScheduleError::InvalidRequestOptionalGroup {
group,
kind,
}
.into());
}
active[group] = match kind {
ArchitectureGroupKind::VisionEncoder | ArchitectureGroupKind::AudioEncoder => {
!request_optional || request_group_active(group)?
}
ArchitectureGroupKind::Projector | ArchitectureGroupKind::Merger => graph
.dependencies(group)
.expect("validated execution order contains a known group")
.iter()
.any(|&dependency| active[dependency]),
ArchitectureGroupKind::ModalityFinalization | ArchitectureGroupKind::Decoder => {
true
}
ArchitectureGroupKind::Prediction => false,
};
}
Ok(Self {
graph,
schedule: ExecutionGroupSchedule::new(graph),
active,
completed: 0,
})
}
pub fn is_active(&self, group: usize) -> Option<bool> {
self.active.get(group).copied()
}
pub fn activity(&self) -> &[bool] {
&self.active
}
pub fn ready_groups(&self) -> impl Iterator<Item = usize> + '_ {
self.schedule.startable_groups()
}
pub fn compatible_batch(&self, mut compatible: impl FnMut(usize, usize) -> bool) -> Vec<usize> {
let mut selected = Vec::new();
for candidate in self.ready_groups() {
if selected
.iter()
.copied()
.all(|group| compatible(group, candidate))
{
selected.push(candidate);
}
}
selected
}
pub fn dependencies(&self, group: usize) -> Option<&[usize]> {
self.graph.dependencies(group)
}
pub fn started(&mut self, group: usize) -> Result<Vec<usize>, LayeredPipelineScheduleError> {
self.schedule.started(group).map_err(Into::into)
}
pub fn ordered(&mut self, group: usize) -> Result<(), LayeredPipelineScheduleError> {
self.schedule.ordered(group)?;
self.completed += 1;
Ok(())
}
pub fn is_complete(&self) -> bool {
self.completed == self.active.len()
}
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum LayeredPipelineScheduleError {
#[error(
"execution graph contains {graph} groups but the pipeline declared {declared} group contracts"
)]
GroupContractCount {
graph: usize,
declared: usize,
},
#[error("execution group {group} of kind {kind:?} cannot be request-optional")]
InvalidRequestOptionalGroup {
group: usize,
kind: ArchitectureGroupKind,
},
#[error(transparent)]
Transition(#[from] ExecutionScheduleError),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ArchitectureGroupPlacement {
Pipeline,
OutputOwner,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ArchitectureMergeDestination {
LastOwner,
FirstPipelineOwner,
OutputOwner,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ArchitectureParallelSubgroup {
TensorSharded,
Decoder,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ArchitectureGroupTransport {
pub placement: ArchitectureGroupPlacement,
pub kind: ArchitectureGroupKind,
pub first_owner_static_roles: Vec<String>,
pub last_owner_static_roles: Vec<String>,
pub merge_destination: ArchitectureMergeDestination,
pub parallel_subgroup: Option<ArchitectureParallelSubgroup>,
pub request_optional: bool,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum LayeredTraversalPoint {
Unit {
group: usize,
index: usize,
},
Group {
group: usize,
},
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum LayeredUnitAction {
Execute,
SkipRemainingGroup,
}
pub trait LayeredTraversalHook<B, C, E>
where
B: NeuralBackend,
{
fn before_unit(
&mut self,
_group: usize,
_index: usize,
_remaining_units: usize,
_value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredUnitAction, E> {
Ok(LayeredUnitAction::Execute)
}
fn after_group_begin(
&mut self,
_group: usize,
_value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
Ok(())
}
fn after_unit(
&mut self,
_group: usize,
_index: usize,
_value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
Ok(())
}
fn after_group(
&mut self,
_group: usize,
_value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
Ok(())
}
}
pub struct CompositeLayeredTraversalHook<L, R> {
left: L,
right: R,
}
impl<L, R> CompositeLayeredTraversalHook<L, R> {
pub const fn new(left: L, right: R) -> Self {
Self { left, right }
}
pub fn into_parts(self) -> (L, R) {
(self.left, self.right)
}
}
impl<B, C, E, L, R> LayeredTraversalHook<B, C, E> for CompositeLayeredTraversalHook<L, R>
where
B: NeuralBackend,
L: LayeredTraversalHook<B, C, E>,
R: LayeredTraversalHook<B, C, E>,
{
fn before_unit(
&mut self,
group: usize,
index: usize,
remaining_units: usize,
value: &mut B::Tensor,
forward: &mut C,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredUnitAction, E> {
let left = self
.left
.before_unit(group, index, remaining_units, value, forward, context)?;
let right =
self.right
.before_unit(group, index, remaining_units, value, forward, context)?;
Ok(
if left == LayeredUnitAction::SkipRemainingGroup
|| right == LayeredUnitAction::SkipRemainingGroup
{
LayeredUnitAction::SkipRemainingGroup
} else {
LayeredUnitAction::Execute
},
)
}
fn after_unit(
&mut self,
group: usize,
index: usize,
value: &mut B::Tensor,
forward: &mut C,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
self.left
.after_unit(group, index, value, forward, context)?;
self.right.after_unit(group, index, value, forward, context)
}
fn after_group_begin(
&mut self,
group: usize,
value: &mut B::Tensor,
forward: &mut C,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
self.left
.after_group_begin(group, value, forward, context)?;
self.right.after_group_begin(group, value, forward, context)
}
fn after_group(
&mut self,
group: usize,
value: &mut B::Tensor,
forward: &mut C,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
self.left.after_group(group, value, forward, context)?;
self.right.after_group(group, value, forward, context)
}
}
struct NoopLayeredTraversalHook;
impl<B, C, E> LayeredTraversalHook<B, C, E> for NoopLayeredTraversalHook where B: NeuralBackend {}
struct ActivationObserverTraversalHook<'a, O: ?Sized> {
observer: std::rc::Rc<std::cell::RefCell<&'a mut O>>,
units: Vec<Vec<String>>,
group_inputs: Vec<Option<String>>,
group_outputs: Vec<Option<String>>,
}
impl<B, C, E, O> LayeredTraversalHook<B, C, E> for ActivationObserverTraversalHook<'_, O>
where
B: NeuralBackend,
O: ActivationObserver<B::Tensor, E> + ?Sized,
{
fn before_unit(
&mut self,
group: usize,
index: usize,
_remaining_units: usize,
value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredUnitAction, E> {
let path = eredu_core::UnitObservation::Input.path(&self.units[group][index]);
let mut observer = self.observer.borrow_mut();
*value = observe_and_intervene(&mut **observer, &path, value)?;
Ok(LayeredUnitAction::Execute)
}
fn after_unit(
&mut self,
group: usize,
index: usize,
value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
let path = eredu_core::UnitObservation::Output.path(&self.units[group][index]);
let mut observer = self.observer.borrow_mut();
*value = observe_and_intervene(&mut **observer, &path, value)?;
Ok(())
}
fn after_group_begin(
&mut self,
group: usize,
value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
if let Some(path) = self.group_inputs.get(group).and_then(Option::as_deref) {
let mut observer = self.observer.borrow_mut();
*value = observe_and_intervene(&mut **observer, path, value)?;
}
Ok(())
}
fn after_group(
&mut self,
group: usize,
value: &mut B::Tensor,
_forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
if let Some(path) = self.group_outputs.get(group).and_then(Option::as_deref) {
let mut observer = self.observer.borrow_mut();
*value = observe_and_intervene(&mut **observer, path, value)?;
}
Ok(())
}
}
struct AfterUnitTraversalHook<F> {
after_unit: F,
}
struct AfterUnitContextTraversalHook<F> {
after_unit: F,
}
impl<B, C, E, F> LayeredTraversalHook<B, C, E> for AfterUnitTraversalHook<F>
where
B: NeuralBackend,
F: FnMut(usize, usize, &B::Tensor, &mut C) -> Result<(), E>,
{
fn after_unit(
&mut self,
group: usize,
index: usize,
value: &mut B::Tensor,
forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
(self.after_unit)(group, index, value, forward)
}
}
impl<B, C, E, F> LayeredTraversalHook<B, C, E> for AfterUnitContextTraversalHook<F>
where
B: NeuralBackend,
F: FnMut(usize, usize, &mut C) -> Result<(), E>,
{
fn after_unit(
&mut self,
group: usize,
index: usize,
_value: &mut B::Tensor,
forward: &mut C,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), E> {
(self.after_unit)(group, index, forward)
}
}
pub trait LayeredArchitecture<B, S>:
ArchitectureParameters<B, DefinitionError = Self::Error>
where
B: NeuralBackend,
S: RuntimeState<B>,
{
type Input<'a>
where
Self: 'a;
type StaticModules: Parameterized<B::Tensor>;
type Unit: Parameterized<B::Tensor>;
type ForwardContext;
type RetainedContextValues<'a>: Iterator<Item = &'a B::Tensor>
where
Self: 'a,
B::Tensor: 'a;
type Error;
fn group_transport(&self, group: usize) -> ArchitectureGroupTransport;
fn primary_execution_group(&self) -> &str;
fn prediction_execution_groups(&self) -> Vec<String> {
Vec::new()
}
fn prediction_target_capture(_context: &Self::ForwardContext) -> Option<&B::Tensor> {
None
}
fn prediction_target_placeholder_shape(
&self,
_forward: &Self::ForwardContext,
) -> Result<Option<Vec<i32>>, Self::Error> {
Ok(None)
}
fn state_partition_plan(&self, layout: &StateLayout) -> crate::ArchitectureStatePartitionPlan;
fn execution_graph(&self) -> Result<ExecutionGraph, Self::Error>;
fn group_unit_count(&self, group: usize) -> Result<usize, Self::Error>;
fn unit_path(&self, group: usize, index: usize) -> Result<String, Self::Error>;
fn group_input_observation_path(&self, _group: usize) -> Result<Option<String>, Self::Error> {
Ok(None)
}
fn group_output_observation_path(&self, _group: usize) -> Result<Option<String>, Self::Error> {
Ok(None)
}
fn static_modules(&self) -> &Self::StaticModules;
fn static_modules_mut(&mut self) -> &mut Self::StaticModules;
fn build_unit(
&self,
group: usize,
index: usize,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<Self::Unit, Self::Error>;
fn begin_forward<'a>(
&mut self,
input: Self::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
fn begin_execution_group(
&mut self,
group: usize,
initial: &B::Tensor,
dependencies: &[&B::Tensor],
state: &mut S,
forward: &mut Self::ForwardContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>;
fn should_execute_group(&self, _group: usize, _forward: &Self::ForwardContext) -> bool {
true
}
fn state_ordinal(&self, _group: usize, _index: usize, ordinal: usize) -> usize {
ordinal
}
fn retained_state_ordinals(
&self,
group: usize,
index: usize,
ordinal: usize,
) -> std::ops::Range<usize> {
let state = self.state_ordinal(group, index, ordinal);
state..state + 1
}
fn forward_unit(
&mut self,
group: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>;
fn complete_execution_group(
&mut self,
_group: usize,
hidden: &B::Tensor,
_state: &mut S,
_forward: &mut Self::ForwardContext,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error> {
Ok(hidden.clone())
}
fn finish_forward(
&mut self,
hidden: &B::Tensor,
state: &mut S,
forward: &Self::ForwardContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>;
fn retained_context_values<'a>(
&'a self,
forward: &'a Self::ForwardContext,
group: usize,
index: usize,
) -> Self::RetainedContextValues<'a>;
}
pub trait ParallelLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
where
B: NeuralBackend,
S: RuntimeState<B>,
{
fn begin_forward_parallel<'a>(
&mut self,
input: Self::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
fn forward_unit_parallel(
&mut self,
group_index: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>;
fn begin_execution_group_parallel(
&mut self,
group_index: usize,
initial: &B::Tensor,
dependencies: &[&B::Tensor],
state: &mut S,
forward: &mut Self::ForwardContext,
_parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error> {
self.begin_execution_group(group_index, initial, dependencies, state, forward, context)
}
fn complete_execution_group_parallel(
&mut self,
group_index: usize,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
_parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error> {
self.complete_execution_group(group_index, hidden, state, forward, context)
}
fn finish_forward_parallel(
&mut self,
hidden: &B::Tensor,
state: &mut S,
forward: &Self::ForwardContext,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>;
}
#[derive(Debug)]
pub enum LayeredPartitionInput<'a, T, A = NoAuxiliaryBoundary> {
Tokens(&'a T),
Hidden {
hidden: T,
auxiliary: A,
},
}
pub enum LayeredPartitionOutput<T, A = NoAuxiliaryBoundary> {
Final {
output: T,
retained: Option<T>,
},
Boundary {
hidden: T,
auxiliary: A,
},
}
pub trait PartitionedLayeredArchitecture<B, S>: ParallelLayeredArchitecture<B, S>
where
B: NeuralBackend,
S: RuntimeState<B>,
{
type Boundary: crate::ArchitectureBoundary;
fn boundary_schema(&self) -> Result<Self::Boundary, Self::Error>;
fn begin_partition<'a>(
&mut self,
input: LayeredPartitionInput<
'a,
B::Tensor,
<Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
>,
mask: Option<&B::Tensor>,
state: &mut S,
expected: &crate::StateLayout,
first_state_ordinal: usize,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
#[allow(clippy::too_many_arguments)]
fn begin_partition_parallel<'a>(
&mut self,
input: LayeredPartitionInput<
'a,
B::Tensor,
<Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
>,
mask: Option<&B::Tensor>,
state: &mut S,
expected: &crate::StateLayout,
first_state_ordinal: usize,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<LayeredForwardState<B::Tensor, Self::ForwardContext>, Self::Error>;
#[allow(clippy::too_many_arguments)]
fn enter_partition_group(
&mut self,
group: usize,
initial: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
parallel: Option<&B::ParallelContext>,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error> {
match parallel {
Some(parallel) => self.begin_execution_group_parallel(
group,
initial,
&[],
state,
forward,
parallel,
context,
),
None => self.begin_execution_group(group, initial, &[], state, forward, context),
}
}
#[allow(clippy::too_many_arguments)]
fn leave_partition_group(
&mut self,
group: usize,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
parallel: Option<&B::ParallelContext>,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error> {
match parallel {
Some(parallel) => self.complete_execution_group_parallel(
group, hidden, state, forward, parallel, context,
),
None => self.complete_execution_group(group, hidden, state, forward, context),
}
}
#[allow(clippy::too_many_arguments)]
fn finish_partition(
&mut self,
hidden: &B::Tensor,
state: &mut S,
forward: &Self::ForwardContext,
owns_output: bool,
parallel: Option<&B::ParallelContext>,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<
LayeredPartitionOutput<
B::Tensor,
<Self::Boundary as crate::ArchitectureBoundary>::Boundary<B::Tensor>,
>,
Self::Error,
>;
}
pub trait RoutedLayeredArchitecture<B, S>: LayeredArchitecture<B, S>
where
B: eredu_nn::GroupedNeuralBackend,
S: RuntimeState<B>,
{
fn expert_pass_for_unit(
&self,
_group: usize,
_index: usize,
hidden: &B::Tensor,
_forward: &Self::ForwardContext,
) -> ExpertPass {
let sequence = hidden
.shape()
.get(hidden.shape().len().saturating_sub(2))
.copied()
.unwrap_or(1);
if sequence > 1 {
ExpertPass::Prefill
} else {
ExpertPass::Decode
}
}
fn routed_observation_point(
&self,
_group: usize,
_index: usize,
) -> Result<Option<RoutedObservationPoint>, Self::Error> {
Ok(None)
}
#[allow(clippy::too_many_arguments)]
fn forward_unit_with_provider<P>(
&mut self,
group: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
pass: ExpertPass,
provider: &mut P,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>
where
P: RoutedExpertProvider<B>,
P::Error: std::fmt::Display;
#[allow(clippy::too_many_arguments)]
fn forward_unit_with_inferred_provider<P>(
&mut self,
group: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
provider: &mut P,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>
where
P: RoutedExpertProvider<B>,
P::Error: std::fmt::Display,
{
let pass = self.expert_pass_for_unit(group, index, hidden, forward);
self.forward_unit_with_provider(
group, index, unit, hidden, state, forward, pass, provider, context,
)
}
#[allow(clippy::too_many_arguments)]
fn forward_unit_observed_with_provider<P, O>(
&mut self,
group: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
pass: ExpertPass,
provider: &mut P,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut O,
) -> Result<B::Tensor, Self::Error>
where
P: RoutedExpertProvider<B>,
P::Error: std::fmt::Display,
O: ActivationObserver<B::Tensor, Self::Error> + ?Sized,
Self::Error: std::fmt::Display,
{
match self.routed_observation_point(group, index)? {
Some(point) => {
let mut observed = ObservedExpertProvider::new(provider, observer, point);
self.forward_unit_with_provider(
group,
index,
unit,
hidden,
state,
forward,
pass,
&mut observed,
context,
)
}
None => self.forward_unit_with_provider(
group, index, unit, hidden, state, forward, pass, provider, context,
),
}
}
#[allow(clippy::too_many_arguments)]
fn forward_unit_observed_with_inferred_provider<P, O>(
&mut self,
group: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
provider: &mut P,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut O,
) -> Result<B::Tensor, Self::Error>
where
P: RoutedExpertProvider<B>,
P::Error: std::fmt::Display,
O: ActivationObserver<B::Tensor, Self::Error> + ?Sized,
Self::Error: std::fmt::Display,
{
let pass = self.expert_pass_for_unit(group, index, hidden, forward);
self.forward_unit_observed_with_provider(
group, index, unit, hidden, state, forward, pass, provider, context, observer,
)
}
}
pub trait ParallelRoutedLayeredArchitecture<B, S>:
RoutedLayeredArchitecture<B, S> + ParallelLayeredArchitecture<B, S>
where
B: eredu_nn::GroupedNeuralBackend,
S: RuntimeState<B>,
{
#[allow(clippy::too_many_arguments)]
fn forward_unit_parallel_with_provider<P>(
&mut self,
group: usize,
index: usize,
unit: &mut Self::Unit,
hidden: &B::Tensor,
state: &mut S,
forward: &mut Self::ForwardContext,
pass: ExpertPass,
provider: &mut P,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, Self::Error>
where
P: crate::TensorParallelRoutedExpertProvider<B>,
P::Error: std::fmt::Display;
}
pub struct ResidentRuntime<A, B, S>
where
B: NeuralBackend,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S>,
{
architecture: A,
graph: ExecutionGraph,
units: Vec<Vec<A::Unit>>,
backend: std::marker::PhantomData<fn() -> (B, S)>,
}
impl<A, B, S> ResidentRuntime<A, B, S>
where
B: NeuralBackend,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S>,
{
pub fn new(
architecture: A,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<Self, A::Error> {
let graph = architecture.execution_graph()?;
let mut units = Vec::with_capacity(graph.groups().len());
for group in 0..graph.groups().len() {
let count = architecture.group_unit_count(group)?;
units.push(
(0..count)
.map(|index| architecture.build_unit(group, index, context))
.collect::<Result<Vec<_>, _>>()?,
);
}
Ok(Self {
architecture,
graph,
units,
backend: std::marker::PhantomData,
})
}
pub fn forward<'a>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error> {
self.forward_with_context(input, state, context)
.map(|(output, _)| output)
}
pub fn forward_with_context<'a>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(B::Tensor, A::ForwardContext), A::Error> {
self.forward_with_traversal_hook(input, state, context, &mut NoopLayeredTraversalHook)
}
pub fn forward_with_traversal_hook<'a, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
hook: &mut H,
) -> Result<(B::Tensor, A::ForwardContext), A::Error>
where
H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
{
let forward = self.architecture.begin_forward(input, state, context)?;
let initial = forward.hidden;
let mut forward_context = forward.context;
let mut schedule = ExecutionGroupSchedule::new(&self.graph);
let mut outputs: Vec<Option<B::Tensor>> = vec![None; self.graph.groups().len()];
for &group in self.graph.execution_order() {
let dependencies = schedule
.dependencies(group)
.expect("validated execution order contains a known group")
.iter()
.map(|&dependency| {
outputs[dependency]
.as_ref()
.expect("topological dependency has completed")
.clone()
})
.collect::<Vec<_>>();
let dependency_refs = dependencies.iter().collect::<Vec<_>>();
let mut hidden = self.architecture.begin_execution_group(
group,
&initial,
&dependency_refs,
state,
&mut forward_context,
context,
)?;
hook.after_group_begin(group, &mut hidden, &mut forward_context, context)?;
for dependency in schedule
.started(group)
.expect("topological execution starts only ready groups")
{
outputs[dependency] = None;
}
if self
.architecture
.should_execute_group(group, &forward_context)
{
let unit_count = self.units[group].len();
for (index, unit) in self.units[group].iter_mut().enumerate() {
if hook.before_unit(
group,
index,
unit_count - index,
&mut hidden,
&mut forward_context,
context,
)? == LayeredUnitAction::SkipRemainingGroup
{
break;
}
hidden = self.architecture.forward_unit(
group,
index,
unit,
&hidden,
state,
&mut forward_context,
context,
)?;
hook.after_unit(group, index, &mut hidden, &mut forward_context, context)?;
}
}
hidden = self.architecture.complete_execution_group(
group,
&hidden,
state,
&mut forward_context,
context,
)?;
hook.after_group(group, &mut hidden, &mut forward_context, context)?;
outputs[group] = Some(hidden);
schedule
.ordered(group)
.expect("started group can be ordered exactly once");
}
let hidden = outputs[self.graph.output()]
.take()
.expect("validated graph output completed");
let output = self
.architecture
.finish_forward(&hidden, state, &forward_context, context)?;
Ok((output, forward_context))
}
pub const fn architecture(&self) -> &A {
&self.architecture
}
pub fn architecture_mut(&mut self) -> &mut A {
&mut self.architecture
}
pub fn units(&self) -> &[Vec<A::Unit>] {
&self.units
}
pub fn units_mut(&mut self) -> &mut [Vec<A::Unit>] {
&mut self.units
}
pub fn into_parts(self) -> (A, Vec<A::Unit>) {
(
self.architecture,
self.units.into_iter().flatten().collect(),
)
}
}
pub trait LayerwisePolicy<B, U>
where
B: NeuralBackend,
{
type Lease: std::ops::DerefMut<Target = U>;
type Error;
fn begin(
&mut self,
initial: &B::Tensor,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), Self::Error>;
fn abort(
&mut self,
active: Option<(usize, crate::ExecutionUnitAddress, Self::Lease)>,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) {
drop(active);
}
fn acquire<E, F>(
&mut self,
ordinal: usize,
address: crate::ExecutionUnitAddress,
build: F,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<Self::Lease, LayerwiseAcquireError<E, Self::Error>>
where
F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>;
fn complete<'a, StateValues, ContextValues>(
&mut self,
ordinal: usize,
address: crate::ExecutionUnitAddress,
lease: Self::Lease,
output: &'a B::Tensor,
state_values: StateValues,
context_values: ContextValues,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), Self::Error>
where
B::Tensor: 'a,
StateValues: Iterator<Item = &'a B::Tensor>,
ContextValues: Iterator<Item = &'a B::Tensor>;
fn finish(
&mut self,
output: &B::Tensor,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), Self::Error>;
}
pub struct LayerwisePolicyForward<'a, B, U, P>
where
B: NeuralBackend,
P: LayerwisePolicy<B, U>,
{
policy: &'a mut P,
context: &'a <B::Tensor as eredu_nn::Tensor>::Context,
active: Option<(usize, crate::ExecutionUnitAddress, P::Lease)>,
finished: bool,
unit: std::marker::PhantomData<fn() -> U>,
}
impl<'a, B, U, P> LayerwisePolicyForward<'a, B, U, P>
where
B: NeuralBackend,
P: LayerwisePolicy<B, U>,
{
pub fn begin(
policy: &'a mut P,
initial: &B::Tensor,
context: &'a <B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<Self, P::Error> {
if let Err(error) = policy.begin(initial, context) {
policy.abort(None, context);
return Err(error);
}
Ok(Self {
policy,
context,
active: None,
finished: false,
unit: std::marker::PhantomData,
})
}
pub fn acquire<E, F>(
&mut self,
ordinal: usize,
address: crate::ExecutionUnitAddress,
build: F,
) -> Result<&mut P::Lease, LayerwiseAcquireError<E, P::Error>>
where
F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>,
{
debug_assert!(self.active.is_none());
let lease = self.policy.acquire(ordinal, address, build, self.context)?;
self.active = Some((ordinal, address, lease));
Ok(&mut self
.active
.as_mut()
.expect("acquired policy lease is active")
.2)
}
pub fn complete<'value, StateValues, ContextValues>(
&mut self,
output: &'value B::Tensor,
state_values: StateValues,
context_values: ContextValues,
) -> Result<(), P::Error>
where
B::Tensor: 'value,
StateValues: Iterator<Item = &'value B::Tensor>,
ContextValues: Iterator<Item = &'value B::Tensor>,
{
let (ordinal, address, lease) = self
.active
.take()
.expect("policy completion follows one acquisition");
self.policy.complete(
ordinal,
address,
lease,
output,
state_values,
context_values,
self.context,
)
}
pub fn finish(&mut self, output: &B::Tensor) -> Result<(), P::Error> {
self.policy.finish(output, self.context)?;
self.finished = true;
Ok(())
}
}
impl<B, U, P> Drop for LayerwisePolicyForward<'_, B, U, P>
where
B: NeuralBackend,
P: LayerwisePolicy<B, U>,
{
fn drop(&mut self) {
if !self.finished {
self.policy.abort(self.active.take(), self.context);
}
}
}
#[derive(Debug)]
pub enum LayerwiseAcquireError<A, P> {
Architecture(A),
Policy(P),
}
#[derive(Debug, thiserror::Error)]
pub enum LayerwiseRuntimeError<A, P>
where
A: std::fmt::Display,
P: std::fmt::Display,
{
#[error("layered architecture failed: {0}")]
Architecture(A),
#[error(transparent)]
State(#[from] crate::StateError),
#[error(transparent)]
Layout(#[from] crate::ExecutionUnitLayoutError),
#[error("layerwise execution policy failed: {0}")]
Policy(P),
#[error("layerwise backend submission failed: {0}")]
Submission(String),
}
pub struct LayerwiseRuntime<A, B, S, P>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as eredu_nn::Tensor>::Context>,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S>,
P: LayerwisePolicy<B, A::Unit>,
{
architecture: A,
policy: P,
executors: Option<Vec<B::OwnedExecutor>>,
backend: std::marker::PhantomData<fn() -> (B, S)>,
}
impl<A, B, S, P> LayerwiseRuntime<A, B, S, P>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as eredu_nn::Tensor>::Context>,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S>,
P: LayerwisePolicy<B, A::Unit>,
A::Error: std::fmt::Display,
P::Error: std::fmt::Display,
{
pub const fn new(architecture: A, policy: P) -> Self {
Self {
architecture,
policy,
executors: None,
backend: std::marker::PhantomData,
}
}
pub const fn new_policy_first(policy: P, architecture: A) -> Self {
Self::new(architecture, policy)
}
pub const fn architecture(&self) -> &A {
&self.architecture
}
pub fn architecture_mut(&mut self) -> &mut A {
&mut self.architecture
}
pub const fn policy(&self) -> &P {
&self.policy
}
pub fn policy_mut(&mut self) -> &mut P {
&mut self.policy
}
pub fn forward<'a>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>> {
self.forward_with_context_hook(input, state, context, |_, _, _| Ok(()))
.map(|(output, _)| output)
}
pub fn forward_with_context_hook<'a, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
hook: H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
{
self.forward_with_unit_executor_and_context_hook(
input,
state,
context,
|architecture, group, index, unit, hidden, state, forward, context| {
architecture.forward_unit(group, index, unit, hidden, state, forward, context)
},
hook,
)
}
pub fn forward_with_unit_executor<'a, E>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
{
self.forward_with_unit_executor_and_context_hook(
input,
state,
context,
execute,
|_, _, _| Ok(()),
)
.map(|(output, _)| output)
}
pub fn forward_with_observer<'a, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_with_observer_and_context(input, state, context, observer)
.map(|(output, _)| output)
}
pub fn forward_with_observer_and_context<'a, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_with_unit_executor_and_observer_and_context(
input,
state,
context,
|architecture, group, index, unit, hidden, state, forward, context| {
architecture.forward_unit(group, index, unit, hidden, state, forward, context)
},
observer,
)
}
pub fn forward_with_unit_executor_and_observer<'a, E, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_with_unit_executor_and_observer_and_context(
input, state, context, execute, observer,
)
.map(|(output, _)| output)
}
pub fn forward_with_unit_executor_and_observer_and_context<'a, E, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
observer: &mut Observer,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
let graph = self
.architecture
.execution_graph()
.map_err(LayerwiseRuntimeError::Architecture)?;
let mut units = Vec::with_capacity(graph.groups().len());
let mut group_inputs = Vec::with_capacity(graph.groups().len());
let mut group_outputs = Vec::with_capacity(graph.groups().len());
for group in 0..graph.groups().len() {
let count = self
.architecture
.group_unit_count(group)
.map_err(LayerwiseRuntimeError::Architecture)?;
units.push(
(0..count)
.map(|index| self.architecture.unit_path(group, index))
.collect::<Result<Vec<_>, _>>()
.map_err(LayerwiseRuntimeError::Architecture)?,
);
group_inputs.push(
self.architecture
.group_input_observation_path(group)
.map_err(LayerwiseRuntimeError::Architecture)?,
);
group_outputs.push(
self.architecture
.group_output_observation_path(group)
.map_err(LayerwiseRuntimeError::Architecture)?,
);
}
let observer = std::rc::Rc::new(std::cell::RefCell::new(observer));
let mut hook = ActivationObserverTraversalHook {
observer,
units,
group_inputs,
group_outputs,
};
self.forward_with_unit_executor_and_traversal_hook(
input, state, context, execute, &mut hook,
)
}
#[allow(clippy::too_many_arguments)]
pub fn forward_with_provider_and_observer<'a, Provider, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
pass: ExpertPass,
provider: &mut Provider,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
B: eredu_nn::GroupedNeuralBackend,
A: RoutedLayeredArchitecture<B, S>,
A::Error: std::fmt::Display,
Provider: RoutedExpertProvider<B>,
Provider::Error: std::fmt::Display,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_with_provider_and_observer_and_context(
input, state, pass, provider, context, observer,
)
.map(|(output, _)| output)
}
#[allow(clippy::too_many_arguments)]
pub fn forward_with_provider_and_observer_and_context<'a, Provider, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
pass: ExpertPass,
provider: &mut Provider,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
B: eredu_nn::GroupedNeuralBackend,
A: RoutedLayeredArchitecture<B, S>,
A::Error: std::fmt::Display,
Provider: RoutedExpertProvider<B>,
Provider::Error: std::fmt::Display,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
let graph = self
.architecture
.execution_graph()
.map_err(LayerwiseRuntimeError::Architecture)?;
let mut units = Vec::with_capacity(graph.groups().len());
let mut group_inputs = Vec::with_capacity(graph.groups().len());
let mut group_outputs = Vec::with_capacity(graph.groups().len());
for group in 0..graph.groups().len() {
let count = self
.architecture
.group_unit_count(group)
.map_err(LayerwiseRuntimeError::Architecture)?;
units.push(
(0..count)
.map(|index| self.architecture.unit_path(group, index))
.collect::<Result<Vec<_>, _>>()
.map_err(LayerwiseRuntimeError::Architecture)?,
);
group_inputs.push(
self.architecture
.group_input_observation_path(group)
.map_err(LayerwiseRuntimeError::Architecture)?,
);
group_outputs.push(
self.architecture
.group_output_observation_path(group)
.map_err(LayerwiseRuntimeError::Architecture)?,
);
}
let observer = std::rc::Rc::new(std::cell::RefCell::new(observer));
let routed_observer = observer.clone();
let mut hook = ActivationObserverTraversalHook {
observer,
units,
group_inputs,
group_outputs,
};
self.forward_with_unit_executor_and_traversal_hook(
input,
state,
context,
|architecture, group, index, unit, hidden, state, forward, context| {
architecture.forward_unit_observed_with_provider(
group,
index,
unit,
hidden,
state,
forward,
pass,
provider,
context,
&mut **routed_observer.borrow_mut(),
)
},
&mut hook,
)
}
#[allow(clippy::too_many_arguments)]
pub fn forward_with_inferred_provider_and_observer<'a, Provider, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
provider: &mut Provider,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
B: eredu_nn::GroupedNeuralBackend,
A: RoutedLayeredArchitecture<B, S>,
A::Error: std::fmt::Display,
Provider: RoutedExpertProvider<B>,
Provider::Error: std::fmt::Display,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_with_inferred_provider_and_observer_and_context(
input, state, provider, context, observer,
)
.map(|(output, _)| output)
}
#[allow(clippy::too_many_arguments)]
pub fn forward_with_inferred_provider_and_observer_and_context<'a, Provider, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
provider: &mut Provider,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
B: eredu_nn::GroupedNeuralBackend,
A: RoutedLayeredArchitecture<B, S>,
A::Error: std::fmt::Display,
Provider: RoutedExpertProvider<B>,
Provider::Error: std::fmt::Display,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
let graph = self
.architecture
.execution_graph()
.map_err(LayerwiseRuntimeError::Architecture)?;
let mut units = Vec::with_capacity(graph.groups().len());
let mut group_inputs = Vec::with_capacity(graph.groups().len());
let mut group_outputs = Vec::with_capacity(graph.groups().len());
for group in 0..graph.groups().len() {
let count = self
.architecture
.group_unit_count(group)
.map_err(LayerwiseRuntimeError::Architecture)?;
units.push(
(0..count)
.map(|index| self.architecture.unit_path(group, index))
.collect::<Result<Vec<_>, _>>()
.map_err(LayerwiseRuntimeError::Architecture)?,
);
group_inputs.push(
self.architecture
.group_input_observation_path(group)
.map_err(LayerwiseRuntimeError::Architecture)?,
);
group_outputs.push(
self.architecture
.group_output_observation_path(group)
.map_err(LayerwiseRuntimeError::Architecture)?,
);
}
let observer = std::rc::Rc::new(std::cell::RefCell::new(observer));
let routed_observer = observer.clone();
let mut hook = ActivationObserverTraversalHook {
observer,
units,
group_inputs,
group_outputs,
};
self.forward_with_unit_executor_and_traversal_hook(
input,
state,
context,
|architecture, group, index, unit, hidden, state, forward, context| {
architecture.forward_unit_observed_with_inferred_provider(
group,
index,
unit,
hidden,
state,
forward,
provider,
context,
&mut **routed_observer.borrow_mut(),
)
},
&mut hook,
)
}
pub fn forward_with_unit_executor_and_context_hook<'a, E, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
mut hook: H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
{
self.forward_with_unit_executor_and_activation_hook(
input,
state,
context,
execute,
|group, index, _hidden, forward| hook(group, index, forward),
)
}
pub fn forward_with_unit_executor_and_activation_hook<'a, E, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
hook: H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
H: FnMut(usize, usize, &B::Tensor, &mut A::ForwardContext) -> Result<(), A::Error>,
{
self.forward_with_unit_executor_and_traversal_hook(
input,
state,
context,
execute,
&mut AfterUnitTraversalHook { after_unit: hook },
)
}
pub fn forward_with_traversal_hook<'a, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
hook: &mut H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
{
self.forward_with_unit_executor_and_traversal_hook(
input,
state,
context,
|architecture, group, index, unit, hidden, state, forward, context| {
architecture.forward_unit(group, index, unit, hidden, state, forward, context)
},
hook,
)
}
pub fn forward_with_unit_executor_and_traversal_hook<'a, E, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
mut execute: E,
hook: &mut H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
{
let graph = self
.architecture
.execution_graph()
.map_err(LayerwiseRuntimeError::Architecture)?;
let counts = (0..graph.groups().len())
.map(|group| {
self.architecture
.group_unit_count(group)
.map_err(LayerwiseRuntimeError::Architecture)
})
.collect::<Result<Vec<_>, _>>()?;
let layout = ExecutionUnitLayout::new(&graph, counts)?;
if self.executors.as_ref().map(Vec::len) != Some(graph.groups().len()) {
self.executors = Some(
B::fork_executors(context, graph.groups().len())
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
);
}
let executors = self
.executors
.as_ref()
.expect("layered runtime initialized its executor cache");
let forward = self
.architecture
.begin_forward(input, state, context)
.map_err(LayerwiseRuntimeError::Architecture)?;
let initial_completion = (graph.groups().len() > 1)
.then(|| B::submit(context, [&forward.hidden]))
.transpose()
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
let mut policy = LayerwisePolicyForward::begin(&mut self.policy, &forward.hidden, context)
.map_err(LayerwiseRuntimeError::Policy)?;
let initial = forward.hidden;
let mut forward_context = forward.context;
let mut schedule = ExecutionGroupSchedule::new(&graph);
let mut outputs: Vec<Option<B::Tensor>> = vec![None; graph.groups().len()];
let mut completions: Vec<Option<B::Completion>> =
(0..graph.groups().len()).map(|_| None).collect();
for &group in graph.execution_order() {
let executor = std::borrow::Borrow::borrow(&executors[group]);
let group_dependencies = schedule
.dependencies(group)
.expect("validated execution order contains a known group");
if group_dependencies.is_empty() {
if let Some(completion) = &initial_completion {
B::order_after(completion, executor)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
}
}
for &dependency in group_dependencies {
B::order_after(
completions[dependency]
.as_ref()
.expect("topological dependency has a completion"),
executor,
)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
}
let dependencies = schedule
.dependencies(group)
.expect("validated execution order contains a known group")
.iter()
.map(|&dependency| {
outputs[dependency]
.as_ref()
.expect("topological dependency has completed")
.clone()
})
.collect::<Vec<_>>();
let dependency_refs = dependencies.iter().collect::<Vec<_>>();
let mut hidden = self
.architecture
.begin_execution_group(
group,
&initial,
&dependency_refs,
state,
&mut forward_context,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?;
hook.after_group_begin(group, &mut hidden, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
for dependency in schedule
.started(group)
.expect("topological execution starts only ready groups")
{
outputs[dependency] = None;
}
if self
.architecture
.should_execute_group(group, &forward_context)
{
let unit_count = layout
.group_range(group)
.expect("layout covers every graph group")
.len();
for index in 0..unit_count {
if hook
.before_unit(
group,
index,
unit_count - index,
&mut hidden,
&mut forward_context,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?
== LayeredUnitAction::SkipRemainingGroup
{
break;
}
let ordinal = layout
.ordinal(group, index)
.expect("group-local unit belongs to the layout");
let address = layout
.address(ordinal)
.expect("group-local unit has a stable policy address");
let lease = policy
.acquire(ordinal, address, |executor| {
self.architecture.build_unit(group, index, executor)
})
.map_err(|error| match error {
LayerwiseAcquireError::Architecture(error) => {
LayerwiseRuntimeError::Architecture(error)
}
LayerwiseAcquireError::Policy(error) => {
LayerwiseRuntimeError::Policy(error)
}
})?;
hidden = execute(
&mut self.architecture,
group,
index,
lease,
&hidden,
state,
&mut forward_context,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?;
hook.after_unit(group, index, &mut hidden, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
let mut state_values = Vec::new();
for state_ordinal in self
.architecture
.retained_state_ordinals(group, index, ordinal)
{
state_values.extend(
state
.retained_values(state_ordinal, address.with_index(state_ordinal))
.map_err(LayerwiseRuntimeError::State)?,
);
}
let context_values =
self.architecture
.retained_context_values(&forward_context, group, index);
policy
.complete(&hidden, state_values.into_iter(), context_values)
.map_err(LayerwiseRuntimeError::Policy)?;
}
}
hidden = self
.architecture
.complete_execution_group(group, &hidden, state, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
hook.after_group(group, &mut hidden, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
outputs[group] = Some(hidden);
if graph.groups().len() > 1 {
completions[group] = Some(
B::submit(
executor,
[outputs[group]
.as_ref()
.expect("group output was stored before submission")],
)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
);
}
schedule
.ordered(group)
.expect("started group can be ordered exactly once");
}
let hidden = outputs[graph.output()]
.take()
.expect("validated graph output completed");
if let Some(completion) = &completions[graph.output()] {
B::order_after(completion, context)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
}
let output = self
.architecture
.finish_forward(&hidden, state, &forward_context, context)
.map_err(LayerwiseRuntimeError::Architecture)?;
policy
.finish(&output)
.map_err(LayerwiseRuntimeError::Policy)?;
Ok((output, forward_context))
}
pub fn forward_parallel<'a>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
{
self.forward_parallel_with_context_hook(input, state, parallel, context, |_, _, _| Ok(()))
.map(|(output, _)| output)
}
pub fn forward_parallel_with_context_hook<'a, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
hook: H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
{
self.forward_parallel_with_unit_executor_and_traversal_hook(
input,
state,
parallel,
context,
|architecture, group, index, unit, hidden, state, forward, parallel, context| {
architecture.forward_unit_parallel(
group, index, unit, hidden, state, forward, parallel, context,
)
},
&mut AfterUnitContextTraversalHook { after_unit: hook },
)
}
pub fn forward_parallel_with_unit_executor<'a, E>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&B::ParallelContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
{
self.forward_parallel_with_unit_executor_and_context_hook(
input,
state,
parallel,
context,
execute,
|_, _, _| Ok(()),
)
.map(|(output, _)| output)
}
pub fn forward_parallel_with_observer<'a, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_parallel_with_unit_executor_and_observer(
input,
state,
parallel,
context,
|architecture, group, index, unit, hidden, state, forward, parallel, context| {
architecture.forward_unit_parallel(
group, index, unit, hidden, state, forward, parallel, context,
)
},
observer,
)
}
pub fn forward_parallel_with_unit_executor_and_observer<'a, E, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
mut execute: E,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&B::ParallelContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_parallel_with_unit_executor(
input,
state,
parallel,
context,
|architecture, group, index, unit, hidden, state, forward, parallel, context| {
let path = architecture.unit_path(group, index)?;
let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
let output = execute(
architecture,
group,
index,
unit,
&input,
state,
forward,
parallel,
context,
)?;
observe_and_intervene(observer, &format!("{path}.output"), &output)
},
)
}
#[allow(clippy::too_many_arguments)]
pub fn forward_parallel_with_provider_and_observer<'a, Provider, Observer>(
&mut self,
input: A::Input<'a>,
state: &mut S,
pass: ExpertPass,
provider: &mut Provider,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
observer: &mut Observer,
) -> Result<B::Tensor, LayerwiseRuntimeError<A::Error, P::Error>>
where
B: eredu_nn::GroupedNeuralBackend,
A: ParallelRoutedLayeredArchitecture<B, S>,
Provider: crate::TensorParallelRoutedExpertProvider<B>,
Provider::Error: std::fmt::Display,
Observer: ActivationObserver<B::Tensor, A::Error> + ?Sized,
{
self.forward_parallel_with_unit_executor(
input,
state,
parallel,
context,
|architecture, group, index, unit, hidden, state, forward, parallel, context| {
let path = architecture.unit_path(group, index)?;
let input = observe_and_intervene(observer, &format!("{path}.input"), hidden)?;
let output = match architecture.routed_observation_point(group, index)? {
Some(point) => {
let mut observed = ObservedExpertProvider::new(provider, observer, point);
architecture.forward_unit_parallel_with_provider(
group,
index,
unit,
&input,
state,
forward,
pass,
&mut observed,
parallel,
context,
)
}
None => architecture.forward_unit_parallel_with_provider(
group, index, unit, &input, state, forward, pass, provider, parallel,
context,
),
}?;
observe_and_intervene(observer, &format!("{path}.output"), &output)
},
)
}
pub fn forward_parallel_with_unit_executor_and_context_hook<'a, E, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
execute: E,
hook: H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&B::ParallelContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
H: FnMut(usize, usize, &mut A::ForwardContext) -> Result<(), A::Error>,
{
self.forward_parallel_with_unit_executor_and_traversal_hook(
input,
state,
parallel,
context,
execute,
&mut AfterUnitContextTraversalHook { after_unit: hook },
)
}
pub fn forward_parallel_with_traversal_hook<'a, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
hook: &mut H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
{
self.forward_parallel_with_unit_executor_and_traversal_hook(
input,
state,
parallel,
context,
|architecture, group, index, unit, hidden, state, forward, parallel, context| {
architecture.forward_unit_parallel(
group, index, unit, hidden, state, forward, parallel, context,
)
},
hook,
)
}
pub fn forward_parallel_with_unit_executor_and_traversal_hook<'a, E, H>(
&mut self,
input: A::Input<'a>,
state: &mut S,
parallel: &B::ParallelContext,
context: &<B::Tensor as eredu_nn::Tensor>::Context,
mut execute: E,
hook: &mut H,
) -> Result<(B::Tensor, A::ForwardContext), LayerwiseRuntimeError<A::Error, P::Error>>
where
A: ParallelLayeredArchitecture<B, S>,
E: FnMut(
&mut A,
usize,
usize,
&mut A::Unit,
&B::Tensor,
&mut S,
&mut A::ForwardContext,
&B::ParallelContext,
&<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<B::Tensor, A::Error>,
H: LayeredTraversalHook<B, A::ForwardContext, A::Error> + ?Sized,
{
let graph = self
.architecture
.execution_graph()
.map_err(LayerwiseRuntimeError::Architecture)?;
let counts = (0..graph.groups().len())
.map(|group| {
self.architecture
.group_unit_count(group)
.map_err(LayerwiseRuntimeError::Architecture)
})
.collect::<Result<Vec<_>, _>>()?;
let layout = ExecutionUnitLayout::new(&graph, counts)?;
if self.executors.as_ref().map(Vec::len) != Some(graph.groups().len()) {
self.executors = Some(
B::fork_executors(context, graph.groups().len())
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
);
}
let executors = self
.executors
.as_ref()
.expect("layered runtime initialized its executor cache");
let forward = self
.architecture
.begin_forward_parallel(input, state, parallel, context)
.map_err(LayerwiseRuntimeError::Architecture)?;
let initial_completion = (graph.groups().len() > 1)
.then(|| B::submit(context, [&forward.hidden]))
.transpose()
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
let mut policy = LayerwisePolicyForward::begin(&mut self.policy, &forward.hidden, context)
.map_err(LayerwiseRuntimeError::Policy)?;
let initial = forward.hidden;
let mut forward_context = forward.context;
let mut schedule = ExecutionGroupSchedule::new(&graph);
let mut outputs: Vec<Option<B::Tensor>> = vec![None; graph.groups().len()];
let mut completions: Vec<Option<B::Completion>> =
(0..graph.groups().len()).map(|_| None).collect();
for &group in graph.execution_order() {
let executor = std::borrow::Borrow::borrow(&executors[group]);
let group_dependencies = schedule
.dependencies(group)
.expect("validated execution order contains a known group");
if group_dependencies.is_empty() {
if let Some(completion) = &initial_completion {
B::order_after(completion, executor)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
}
}
for &dependency in group_dependencies {
B::order_after(
completions[dependency]
.as_ref()
.expect("topological dependency has a completion"),
executor,
)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
}
let dependencies = schedule
.dependencies(group)
.expect("validated execution order contains a known group")
.iter()
.map(|&dependency| {
outputs[dependency]
.as_ref()
.expect("topological dependency has completed")
.clone()
})
.collect::<Vec<_>>();
let dependency_refs = dependencies.iter().collect::<Vec<_>>();
let mut hidden = self
.architecture
.begin_execution_group_parallel(
group,
&initial,
&dependency_refs,
state,
&mut forward_context,
parallel,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?;
hook.after_group_begin(group, &mut hidden, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
for dependency in schedule
.started(group)
.expect("topological execution starts only ready groups")
{
outputs[dependency] = None;
}
if self
.architecture
.should_execute_group(group, &forward_context)
{
let unit_count = layout
.group_range(group)
.expect("layout covers every graph group")
.len();
for index in 0..unit_count {
if hook
.before_unit(
group,
index,
unit_count - index,
&mut hidden,
&mut forward_context,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?
== LayeredUnitAction::SkipRemainingGroup
{
break;
}
let ordinal = layout
.ordinal(group, index)
.expect("group-local unit belongs to the layout");
let address = layout
.address(ordinal)
.expect("group-local unit has a stable policy address");
let lease = policy
.acquire(ordinal, address, |executor| {
self.architecture.build_unit(group, index, executor)
})
.map_err(|error| match error {
LayerwiseAcquireError::Architecture(error) => {
LayerwiseRuntimeError::Architecture(error)
}
LayerwiseAcquireError::Policy(error) => {
LayerwiseRuntimeError::Policy(error)
}
})?;
hidden = execute(
&mut self.architecture,
group,
index,
lease,
&hidden,
state,
&mut forward_context,
parallel,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?;
hook.after_unit(group, index, &mut hidden, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
let mut state_values = Vec::new();
for state_ordinal in self
.architecture
.retained_state_ordinals(group, index, ordinal)
{
state_values.extend(
state
.retained_values(state_ordinal, address.with_index(state_ordinal))
.map_err(LayerwiseRuntimeError::State)?,
);
}
let context_values =
self.architecture
.retained_context_values(&forward_context, group, index);
policy
.complete(&hidden, state_values.into_iter(), context_values)
.map_err(LayerwiseRuntimeError::Policy)?;
}
}
hidden = self
.architecture
.complete_execution_group_parallel(
group,
&hidden,
state,
&mut forward_context,
parallel,
executor,
)
.map_err(LayerwiseRuntimeError::Architecture)?;
hook.after_group(group, &mut hidden, &mut forward_context, executor)
.map_err(LayerwiseRuntimeError::Architecture)?;
outputs[group] = Some(hidden);
if graph.groups().len() > 1 {
completions[group] = Some(
B::submit(
executor,
[outputs[group]
.as_ref()
.expect("group output was stored before submission")],
)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?,
);
}
schedule
.ordered(group)
.expect("started group can be ordered exactly once");
}
let hidden = outputs[graph.output()]
.take()
.expect("validated graph output completed");
if let Some(completion) = &completions[graph.output()] {
B::order_after(completion, context)
.map_err(|error| LayerwiseRuntimeError::Submission(error.to_string()))?;
}
let output = self
.architecture
.finish_forward_parallel(&hidden, state, &forward_context, parallel, context)
.map_err(LayerwiseRuntimeError::Architecture)?;
policy
.finish(&output)
.map_err(LayerwiseRuntimeError::Policy)?;
Ok((output, forward_context))
}
}
pub struct ResidentUnitLease<U> {
index: usize,
unit: U,
}
impl<U> std::ops::Deref for ResidentUnitLease<U> {
type Target = U;
fn deref(&self) -> &Self::Target {
&self.unit
}
}
impl<U> std::ops::DerefMut for ResidentUnitLease<U> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.unit
}
}
pub struct ResidentUnitWindow<U> {
units: Vec<Option<U>>,
}
impl<U> ResidentUnitWindow<U> {
pub fn new(units: Vec<U>) -> Self {
Self {
units: units.into_iter().map(Some).collect(),
}
}
}
impl<B, U> LayerwisePolicy<B, U> for ResidentUnitWindow<U>
where
B: NeuralBackend,
{
type Lease = ResidentUnitLease<U>;
type Error = ResidentUnitWindowError;
fn begin(
&mut self,
_initial: &B::Tensor,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), Self::Error> {
Ok(())
}
fn abort(
&mut self,
active: Option<(usize, crate::ExecutionUnitAddress, Self::Lease)>,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) {
let Some((ordinal, _, lease)) = active else {
return;
};
debug_assert_eq!(lease.index, ordinal);
if let Some(slot) = self.units.get_mut(lease.index) {
debug_assert!(slot.is_none());
if slot.is_none() {
*slot = Some(lease.unit);
}
}
}
fn acquire<E, F>(
&mut self,
index: usize,
_address: crate::ExecutionUnitAddress,
_build: F,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<Self::Lease, LayerwiseAcquireError<E, Self::Error>>
where
F: FnOnce(&<B::Tensor as eredu_nn::Tensor>::Context) -> Result<U, E>,
{
let count = self.units.len();
let unit = self
.units
.get_mut(index)
.ok_or(ResidentUnitWindowError::UnknownUnit { index, count })
.map_err(LayerwiseAcquireError::Policy)?
.take()
.ok_or(ResidentUnitWindowError::AlreadyAcquired { index })
.map_err(LayerwiseAcquireError::Policy)?;
Ok(ResidentUnitLease { index, unit })
}
fn complete<'a, StateValues, ContextValues>(
&mut self,
index: usize,
_address: crate::ExecutionUnitAddress,
lease: Self::Lease,
_output: &'a B::Tensor,
_state_values: StateValues,
_context_values: ContextValues,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), Self::Error>
where
B::Tensor: 'a,
StateValues: Iterator<Item = &'a B::Tensor>,
ContextValues: Iterator<Item = &'a B::Tensor>,
{
if lease.index != index {
return Err(ResidentUnitWindowError::MismatchedUnit {
expected: index,
actual: lease.index,
});
}
let slot = self
.units
.get_mut(index)
.expect("acquired unit index remains in the window");
if slot.replace(lease.unit).is_some() {
return Err(ResidentUnitWindowError::AlreadyResident { index });
}
Ok(())
}
fn finish(
&mut self,
_output: &B::Tensor,
_context: &<B::Tensor as eredu_nn::Tensor>::Context,
) -> Result<(), Self::Error> {
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum ResidentUnitWindowError {
#[error("unit {index} is outside the {count}-unit window")]
UnknownUnit {
index: usize,
count: usize,
},
#[error("unit {index} is already acquired")]
AlreadyAcquired {
index: usize,
},
#[error("unit completion expected {expected}, received {actual}")]
MismatchedUnit {
expected: usize,
actual: usize,
},
#[error("unit {index} is already resident")]
AlreadyResident {
index: usize,
},
}
#[cfg(test)]
mod tests {
use super::{ArchitectureGroupKind, LayeredPipelineSchedule, LayeredPipelineScheduleError};
use crate::{ExecutionGraph, ExecutionGroupSpec, ExecutionScheduleError};
fn pipeline_graph() -> ExecutionGraph {
ExecutionGraph::new(
vec![
ExecutionGroupSpec::root("vision"),
ExecutionGroupSpec::root("audio"),
ExecutionGroupSpec::with_dependencies("projector", ["vision"]),
ExecutionGroupSpec::with_dependencies("merge", ["projector", "audio"]),
ExecutionGroupSpec::with_dependencies("decoder", ["merge"]),
ExecutionGroupSpec::with_dependencies("prediction", ["decoder"]),
],
"prediction",
)
.unwrap()
}
#[test]
fn pipeline_schedule_owns_activity_propagation_and_ready_batches() {
let graph = pipeline_graph();
let contracts = [
(ArchitectureGroupKind::VisionEncoder, true),
(ArchitectureGroupKind::AudioEncoder, true),
(ArchitectureGroupKind::Projector, false),
(ArchitectureGroupKind::Merger, false),
(ArchitectureGroupKind::Decoder, false),
(ArchitectureGroupKind::Prediction, false),
];
let mut queried = Vec::new();
let mut schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |group| {
queried.push(group);
Ok::<_, LayeredPipelineScheduleError>(group == 0)
})
.unwrap();
assert_eq!(queried, [0, 1]);
assert_eq!(schedule.activity(), [true, false, true, true, true, false]);
assert_eq!(schedule.compatible_batch(|_, _| true), [0, 1]);
schedule.started(0).unwrap();
schedule.started(1).unwrap();
schedule.ordered(0).unwrap();
schedule.ordered(1).unwrap();
assert_eq!(schedule.ready_groups().collect::<Vec<_>>(), [2]);
for group in [2, 3, 4, 5] {
schedule.started(group).unwrap();
schedule.ordered(group).unwrap();
}
assert!(schedule.is_complete());
}
#[test]
fn pipeline_schedule_rejects_kind_and_transition_drift() {
let graph = pipeline_graph();
let error = LayeredPipelineSchedule::try_new(
&graph,
[(ArchitectureGroupKind::Decoder, false)],
|_| Ok::<_, LayeredPipelineScheduleError>(true),
)
.unwrap_err();
assert_eq!(
error,
LayeredPipelineScheduleError::GroupContractCount {
graph: 6,
declared: 1,
}
);
let contracts = [
(ArchitectureGroupKind::VisionEncoder, true),
(ArchitectureGroupKind::AudioEncoder, true),
(ArchitectureGroupKind::Projector, false),
(ArchitectureGroupKind::Merger, false),
(ArchitectureGroupKind::Decoder, false),
(ArchitectureGroupKind::Prediction, false),
];
let mut schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |_| {
Ok::<_, LayeredPipelineScheduleError>(true)
})
.unwrap();
assert_eq!(
schedule.started(2),
Err(LayeredPipelineScheduleError::Transition(
ExecutionScheduleError::DependenciesPending { group: 2 }
))
);
}
#[test]
fn pipeline_schedule_consumes_declared_request_optionality() {
let graph = ExecutionGraph::new(
vec![
ExecutionGroupSpec::root("mandatory_vision"),
ExecutionGroupSpec::root("optional_audio"),
ExecutionGroupSpec::with_dependencies(
"decoder",
["mandatory_vision", "optional_audio"],
),
],
"decoder",
)
.unwrap();
let contracts = [
(ArchitectureGroupKind::VisionEncoder, false),
(ArchitectureGroupKind::AudioEncoder, true),
(ArchitectureGroupKind::Decoder, false),
];
let mut queried = Vec::new();
let schedule = LayeredPipelineSchedule::try_new(&graph, contracts, |group| {
queried.push(group);
Ok::<_, LayeredPipelineScheduleError>(false)
})
.unwrap();
assert_eq!(queried, [1]);
assert_eq!(schedule.activity(), [true, false, true]);
let invalid = [
(ArchitectureGroupKind::VisionEncoder, false),
(ArchitectureGroupKind::AudioEncoder, false),
(ArchitectureGroupKind::Decoder, true),
];
assert_eq!(
LayeredPipelineSchedule::try_new(&graph, invalid, |_| {
Ok::<_, LayeredPipelineScheduleError>(true)
})
.unwrap_err(),
LayeredPipelineScheduleError::InvalidRequestOptionalGroup {
group: 2,
kind: ArchitectureGroupKind::Decoder,
}
);
}
}