use crate::{
collections::HashMap,
graph::{ComputingProperty, NodeId},
tensor::AutodiffTensor,
};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use burn_backend::Backend;
use core::any::Any;
use super::{
base::{Checkpointer, NodeTree},
retro_forward::{RetroForward, RetroForwards},
state::{BackwardStates, State},
};
#[derive(Debug)]
pub enum CheckpointingAction {
Computed {
node_id: NodeId,
state_content: Box<dyn Any + Send>,
},
Recompute {
node_id: NodeId,
retro_forward: Arc<dyn RetroForward>,
},
}
unsafe impl Send for CheckpointingAction {}
impl CheckpointingAction {
pub fn id(&self) -> NodeId {
match self {
CheckpointingAction::Computed {
node_id: node_ref,
state_content: _,
} => *node_ref,
CheckpointingAction::Recompute {
node_id: node_ref,
retro_forward: _,
} => *node_ref,
}
}
}
#[derive(new, Debug, Default)]
pub struct CheckpointerBuilder {
explicit_actions: Vec<CheckpointingAction>,
backup_actions: Vec<CheckpointingAction>,
}
pub(crate) enum ActionType {
Explicit,
Backup,
}
impl CheckpointerBuilder {
pub(crate) fn checkpoint<B: Backend>(
&mut self,
tensor: &AutodiffTensor<B>,
action_type: ActionType,
) {
let action_list = match action_type {
ActionType::Explicit => &mut self.explicit_actions,
ActionType::Backup => &mut self.backup_actions,
};
match &tensor.node.properties {
ComputingProperty::ComputeBound | ComputingProperty::Ambiguous => {
action_list.push(CheckpointingAction::Computed {
node_id: tensor.node.id,
state_content: Box::new(tensor.primitive.clone()),
})
}
ComputingProperty::MemoryBound { retro_forward } => {
action_list.push(CheckpointingAction::Recompute {
node_id: tensor.node.id,
retro_forward: retro_forward.clone(),
})
}
}
}
pub(crate) fn extend(&mut self, other: CheckpointerBuilder) {
for other_action in other.explicit_actions {
self.explicit_actions.push(other_action)
}
for other_unsure in other.backup_actions {
self.backup_actions.push(other_unsure)
}
}
pub(crate) fn build(self, node_tree: NodeTree) -> Checkpointer {
let mut backward_states_map = HashMap::new();
let mut retro_forwards_map = HashMap::new();
let stop_nodes: Vec<NodeId> = self.find_stop_nodes();
let n_required_map = self.build_n_required_map(&node_tree, stop_nodes);
self.insert_checkpoints(
&mut backward_states_map,
&mut retro_forwards_map,
n_required_map,
);
Checkpointer::new(
BackwardStates::new(backward_states_map),
RetroForwards::new(retro_forwards_map),
node_tree,
)
}
fn find_stop_nodes(&self) -> Vec<NodeId> {
let mut stop_nodes = Vec::default();
for action in self
.explicit_actions
.iter()
.chain(self.backup_actions.iter())
{
match action {
CheckpointingAction::Computed {
node_id: node_ref,
state_content: _,
} => stop_nodes.push(*node_ref),
CheckpointingAction::Recompute {
node_id: _,
retro_forward: _,
} => {}
}
}
stop_nodes
}
fn build_n_required_map(
&self,
node_tree: &NodeTree,
stop_nodes: Vec<NodeId>,
) -> HashMap<NodeId, usize> {
let mut n_required_map = HashMap::<NodeId, usize>::default();
for action in self.explicit_actions.iter() {
match action {
CheckpointingAction::Computed {
node_id: node_ref,
state_content: _,
} => {
let id = *node_ref;
match n_required_map.remove(&id) {
Some(n) => {
n_required_map.insert(id, n + 1);
}
None => {
n_required_map.insert(id, 1);
}
};
}
CheckpointingAction::Recompute {
node_id: node_ref,
retro_forward: _,
} => {
let id = *node_ref;
Self::update_n_required_of_parents(
id,
&mut n_required_map,
node_tree,
&stop_nodes,
);
}
}
}
n_required_map
}
fn insert_checkpoints(
mut self,
backward_states_map: &mut HashMap<NodeId, State>,
retro_forward_map: &mut HashMap<NodeId, Arc<dyn RetroForward>>,
n_required_map: HashMap<NodeId, usize>,
) {
for (node_id, n_required) in n_required_map {
let action = match self
.explicit_actions
.iter()
.position(|action| action.id() == node_id)
{
Some(pos) => self.explicit_actions.remove(pos),
None => {
let pos = self
.backup_actions
.iter()
.position(|action| action.id() == node_id);
self.backup_actions.remove(pos.unwrap_or_else(|| {
panic!("Node {:?} is needed but never checkpointed", &node_id)
}))
}
};
match action {
CheckpointingAction::Computed {
node_id: _,
state_content,
} => {
self.checkpoint_compute(backward_states_map, node_id, state_content, n_required)
}
CheckpointingAction::Recompute {
node_id: _,
retro_forward,
} => self.checkpoint_lazy(
backward_states_map,
retro_forward_map,
node_id,
retro_forward,
n_required,
),
};
}
}
fn update_n_required_of_parents(
id: NodeId,
n_required_map: &mut HashMap<NodeId, usize>,
node_tree: &NodeTree,
stop_nodes: &Vec<NodeId>,
) {
match n_required_map.remove(&id) {
Some(n) => {
n_required_map.insert(id, n + 1);
}
None => {
n_required_map.insert(id, 1);
if !stop_nodes.contains(&id)
&& let Some(parents) = node_tree.parents(&id)
{
for p in parents {
Self::update_n_required_of_parents(
p,
n_required_map,
node_tree,
stop_nodes,
);
}
}
}
}
}
fn checkpoint_compute(
&self,
backward_states_map: &mut HashMap<NodeId, State>,
node_id: NodeId,
state_content: Box<dyn Any + Send>,
n_required: usize,
) {
backward_states_map.insert(
node_id,
State::Computed {
state_content,
n_required,
},
);
}
fn checkpoint_lazy(
&self,
backward_states_map: &mut HashMap<NodeId, State>,
retro_forward_map: &mut HashMap<NodeId, Arc<dyn RetroForward>>,
node_id: NodeId,
retro_forward: Arc<dyn RetroForward>,
n_required: usize,
) {
retro_forward_map.insert(node_id, retro_forward);
backward_states_map.insert(node_id, State::Recompute { n_required });
}
}