use std::collections::BTreeMap;
use super::SequenceCheckpointUnsupportedReason;
use crate::vnext::{
ExecutionPlan, PlanNode, ProgramCheckpointInputs, ProgramValueId, ResolvedValueRole,
TensorAccess, VNextError,
};
pub(super) fn state_dependencies(
inputs: &ProgramCheckpointInputs,
nodes: &[PlanNode],
) -> Result<Vec<SequenceCheckpointUnsupportedReason>, VNextError> {
if inputs.output_only_inputs().is_empty() {
return Ok(Vec::new());
}
let aliases = ExecutionPlan::alias_classes(nodes)?;
let mut affected: BTreeMap<ProgramValueId, ProgramValueId> = inputs
.output_only_inputs()
.iter()
.map(|id| (id.clone(), id.clone()))
.collect();
loop {
let before = affected.len();
let affected_classes: BTreeMap<_, _> = affected
.iter()
.filter_map(|(value, origin)| aliases.get(value).map(|class| (class, origin.clone())))
.collect();
for (value, class) in &aliases {
if let Some(origin) = affected_classes.get(class) {
affected
.entry(value.clone())
.or_insert_with(|| origin.clone());
}
}
for node in nodes {
let origin = node
.values()
.iter()
.filter(|binding| binding.role() == ResolvedValueRole::Input)
.map(|binding| binding.value_id())
.chain(node.work().token_source().map(|source| source.value_id()))
.find_map(|value| affected.get(value))
.cloned();
let Some(origin) = origin else { continue };
if let Some(effect) = node.state_effects().first() {
return Ok(vec![
SequenceCheckpointUnsupportedReason::OutputOnlyInputAffectsState {
value_id: origin,
node_id: node.id().clone(),
state_id: effect.state_id().clone(),
},
]);
}
for binding in node.values().iter().filter(|binding| {
binding.role() == ResolvedValueRole::Output
|| matches!(
binding.access(),
TensorAccess::Write | TensorAccess::ReadWrite
)
}) {
affected
.entry(binding.value_id().clone())
.or_insert_with(|| origin.clone());
}
}
if affected.len() == before {
return Ok(Vec::new());
}
}
}
#[cfg(test)]
mod tests;