use std::collections::HashMap;
use onnx_runtime_ir::{Graph, ValueId};
use crate::error::PlanError;
use crate::liveness::compute_liveness;
use crate::options::PlanOptions;
use crate::oracle::static_size_oracle;
use crate::view_map::ViewMap;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub struct SlotId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SlotInfo {
pub id: SlotId,
pub capacity_bytes: usize,
}
#[derive(Clone, Debug)]
pub struct ActivationPlan {
pub assignments: HashMap<ValueId, SlotId>,
pub slots: Vec<SlotInfo>,
pub peak_bytes: usize,
pub num_slots: usize,
pub naive_bytes: usize,
pub savings_ratio: f64,
}
#[derive(Clone, Debug)]
pub enum PlanStatus {
Complete(ActivationPlan),
Deferred {
unknown_sizes: Vec<ValueId>,
},
}
impl PlanStatus {
pub fn as_complete(&self) -> Option<&ActivationPlan> {
match self {
PlanStatus::Complete(p) => Some(p),
PlanStatus::Deferred { .. } => None,
}
}
pub fn is_deferred(&self) -> bool {
matches!(self, PlanStatus::Deferred { .. })
}
pub fn unwrap_complete(self) -> ActivationPlan {
match self {
PlanStatus::Complete(p) => p,
PlanStatus::Deferred { unknown_sizes } => {
panic!("plan was deferred; unknown sizes for {unknown_sizes:?}")
}
}
}
}
pub fn plan_activations<F>(
graph: &Graph,
view_map: &ViewMap,
size_oracle: F,
options: &PlanOptions,
) -> Result<PlanStatus, PlanError>
where
F: Fn(ValueId) -> Option<usize>,
{
let live = compute_liveness(graph, view_map, options)?;
let mut sizes: HashMap<ValueId, usize> = HashMap::new();
let mut unknown: Vec<ValueId> = Vec::new();
for &owner in &live.owners {
match size_oracle(owner) {
Some(bytes) => {
sizes.insert(owner, bytes);
}
None => unknown.push(owner),
}
}
if !unknown.is_empty() {
unknown.sort_by_key(|v| v.0);
return Ok(PlanStatus::Deferred {
unknown_sizes: unknown,
});
}
let naive_bytes: usize = live.owners.iter().map(|o| sizes[o]).sum();
let mut retire_at: HashMap<usize, Vec<ValueId>> = HashMap::new();
for (&owner, interval) in &live.intervals {
retire_at.entry(interval.use_end).or_default().push(owner);
}
let mut slots: Vec<SlotInfo> = Vec::new();
let mut free: Vec<SlotId> = Vec::new();
let mut assignments: HashMap<ValueId, SlotId> = HashMap::new();
let allocate = |need: usize, slots: &mut Vec<SlotInfo>, free: &mut Vec<SlotId>| -> SlotId {
let mut best: Option<(usize, usize, u32)> = None;
for (i, &sid) in free.iter().enumerate() {
let cap = slots[sid.0 as usize].capacity_bytes;
if cap < need {
continue;
}
let better = match best {
None => true,
Some((_, best_cap, best_id)) => {
cap < best_cap || (cap == best_cap && sid.0 < best_id)
}
};
if better {
best = Some((i, cap, sid.0));
}
}
if let Some((idx, _, _)) = best {
free.remove(idx)
} else {
let sid = SlotId(slots.len() as u32);
slots.push(SlotInfo {
id: sid,
capacity_bytes: need,
});
sid
}
};
if options.include_graph_inputs {
let mut input_owners: Vec<ValueId> = live
.owners
.iter()
.copied()
.filter(|v| graph.value(*v).producer.is_none())
.collect();
input_owners.sort_by_key(|v| v.0);
for owner in input_owners {
let sid = allocate(sizes[&owner], &mut slots, &mut free);
assignments.insert(owner, sid);
}
}
for (i, &node_id) in live.order.iter().enumerate() {
for &out in &graph.node(node_id).outputs {
if !live.intervals.contains_key(&out) {
continue; }
let sid = allocate(sizes[&out], &mut slots, &mut free);
assignments.insert(out, sid);
}
if let Some(retiring) = retire_at.get(&i) {
for owner in retiring {
if let Some(&sid) = assignments.get(owner) {
free.push(sid);
}
}
}
}
let peak_bytes: usize = slots.iter().map(|s| s.capacity_bytes).sum();
let num_slots = slots.len();
let savings_ratio = if naive_bytes == 0 {
0.0
} else {
1.0 - (peak_bytes as f64 / naive_bytes as f64)
};
Ok(PlanStatus::Complete(ActivationPlan {
assignments,
slots,
peak_bytes,
num_slots,
naive_bytes,
savings_ratio,
}))
}
pub fn plan_activations_static(
graph: &Graph,
view_map: &ViewMap,
options: &PlanOptions,
) -> Result<PlanStatus, PlanError> {
let oracle = static_size_oracle(graph);
plan_activations(graph, view_map, oracle, options)
}
pub fn peak_activation_bytes_at_bounds(
graph: &Graph,
view_map: &ViewMap,
bounds: &std::collections::HashMap<onnx_runtime_ir::SymbolId, usize>,
options: &PlanOptions,
) -> Result<Option<usize>, PlanError> {
let oracle = crate::oracle::bounded_size_oracle(graph, bounds);
Ok(match plan_activations(graph, view_map, oracle, options)? {
PlanStatus::Complete(plan) => Some(plan.peak_bytes),
PlanStatus::Deferred { .. } => None,
})
}