use rlx_ir::{Dim, Graph, NodeId, Op};
use std::collections::{HashMap, HashSet};
fn static_dims(g: &Graph, id: NodeId) -> Vec<usize> {
g.shape(id)
.dims()
.iter()
.map(|d| match d {
Dim::Static(n) => *n,
Dim::Dynamic(_) => 0,
})
.collect()
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Stage {
pub index: usize,
pub graph: Graph,
pub inputs: Vec<String>,
pub outputs: Vec<String>,
pub output_shapes: Vec<Vec<usize>>,
pub params: Vec<String>,
}
fn boundary_name(id: NodeId) -> String {
format!("__stage_boundary_{}", id.0)
}
fn is_leaf(g: &Graph, id: NodeId) -> bool {
g.node(id).op.is_leaf()
}
pub fn balanced_stage_of(i: usize, m: usize, n_stages: usize) -> usize {
if m == 0 {
return 0;
}
((i * n_stages) / m).min(n_stages - 1)
}
pub fn partition(graph: &Graph, n_stages: usize) -> Vec<Stage> {
assert!(n_stages >= 1, "n_stages must be >= 1");
partition_with(graph, n_stages, |i, m| balanced_stage_of(i, m, n_stages))
}
pub fn partition_with(
graph: &Graph,
n_stages: usize,
assign: impl Fn(usize, usize) -> usize,
) -> Vec<Stage> {
assert!(n_stages >= 1, "n_stages must be >= 1");
let n = graph.len();
let compute: Vec<NodeId> = (0..n as u32)
.map(NodeId)
.filter(|&id| !is_leaf(graph, id))
.collect();
let m = compute.len();
let mut stage_of: HashMap<NodeId, usize> = HashMap::with_capacity(m);
let mut last = 0usize;
for (i, &id) in compute.iter().enumerate() {
let s = assign(i, m).min(n_stages - 1);
assert!(
s >= last,
"stage assignment must be non-decreasing in topo order"
);
last = s;
stage_of.insert(id, s);
}
let graph_outputs: HashSet<NodeId> = graph.outputs.iter().copied().collect();
let mut users_of: Vec<Vec<NodeId>> = vec![Vec::new(); n];
for node in graph.nodes() {
let mut seen: Vec<NodeId> = Vec::with_capacity(node.inputs.len());
for &input in &node.inputs {
if (input.0 as usize) < n && !seen.contains(&input) {
seen.push(input);
users_of[input.0 as usize].push(node.id);
}
}
}
let mut stages = Vec::with_capacity(n_stages);
for s in 0..n_stages {
let mut g = Graph::new(format!("{}__stage{s}", graph.name));
let mut map: HashMap<NodeId, NodeId> = HashMap::new(); let mut leaf_seen: HashMap<NodeId, NodeId> = HashMap::new();
let mut boundary_seen: HashMap<NodeId, NodeId> = HashMap::new();
let mut inputs: Vec<String> = Vec::new();
let mut params: Vec<String> = Vec::new();
for &cid in compute.iter().filter(|&&id| stage_of[&id] == s) {
let node = graph.node(cid);
let mut new_inputs = Vec::with_capacity(node.inputs.len());
for &inp in &node.inputs {
let new_in = if is_leaf(graph, inp) {
*leaf_seen.entry(inp).or_insert_with(|| {
let ln = graph.node(inp);
match &ln.op {
Op::Input { name } => inputs.push(name.clone()),
Op::Param { name } => params.push(name.clone()),
_ => {}
}
g.append_node(ln.op.clone(), vec![], ln.shape.clone(), ln.name.clone())
})
} else if stage_of[&inp] == s {
map[&inp]
} else {
*boundary_seen.entry(inp).or_insert_with(|| {
let bn = boundary_name(inp);
inputs.push(bn.clone());
g.append_node(
Op::Input { name: bn.clone() },
vec![],
graph.shape(inp).clone(),
Some(bn),
)
})
};
new_inputs.push(new_in);
}
let nid = g.append_node(
node.op.clone(),
new_inputs,
node.shape.clone(),
node.name.clone(),
);
map.insert(cid, nid);
}
let mut out_ids = Vec::new();
let mut out_names = Vec::new();
let mut out_shapes = Vec::new();
for &cid in compute.iter().filter(|&&id| stage_of[&id] == s) {
let final_out = graph_outputs.contains(&cid);
let used_later = users_of[cid.0 as usize]
.iter()
.any(|u| stage_of.get(u).is_some_and(|&us| us > s));
if final_out || used_later {
out_ids.push(map[&cid]);
out_names.push(boundary_name(cid));
out_shapes.push(static_dims(graph, cid));
}
}
g.set_outputs(out_ids);
inputs.sort();
inputs.dedup();
params.sort();
params.dedup();
stages.push(Stage {
index: s,
graph: g,
inputs,
outputs: out_names,
output_shapes: out_shapes,
params,
});
}
stages
}