use std::{
cell::RefCell,
collections::{HashMap, HashSet},
rc::Rc,
};
use crate::{
graph_state::GraphState,
ir::{Argument, NodeType, RawNode},
processor::get_processor_registry,
proto_conversion::DEFAULT_OPSET_VERSION,
};
struct IdentityEliminationPlan {
rewire_map: HashMap<String, String>,
nodes_to_remove: HashSet<usize>,
}
fn rewire_argument(
arg: &mut Argument,
rewire_map: &HashMap<String, String>,
output_arg_map: &HashMap<String, Argument>,
) {
if let Some(new_name) = rewire_map.get(&arg.name) {
if let Some(source_arg) = output_arg_map.get(new_name) {
arg.name = new_name.clone();
arg.value_store = source_arg.value_store.clone();
arg.ty = source_arg.ty.clone();
arg.value_source = source_arg.value_source;
} else {
arg.name = new_name.clone();
}
}
}
fn plan_identity_elimination(
nodes: &[RawNode],
node_output_map: &HashMap<String, (usize, usize)>,
) -> IdentityEliminationPlan {
let mut rewire_map = HashMap::new();
let mut nodes_to_remove = HashSet::new();
let identity_indices: Vec<usize> = nodes
.iter()
.enumerate()
.filter_map(|(i, node)| (node.node_type == NodeType::Identity).then_some(i))
.collect();
for &idx in &identity_indices {
let node = &nodes[idx];
if node.inputs.is_empty() {
log::warn!("Identity node {} has no inputs, skipping", node.name);
continue;
}
let input_name = &node.inputs[0].name;
let output_name = &node.outputs[0].name;
rewire_map.insert(output_name.clone(), input_name.clone());
for (original_name, &(node_idx, output_idx)) in node_output_map.iter() {
if node_idx == idx && output_idx == 0 {
if original_name != output_name {
rewire_map.insert(original_name.clone(), input_name.clone());
}
break;
}
}
nodes_to_remove.insert(idx);
}
IdentityEliminationPlan {
rewire_map,
nodes_to_remove,
}
}
fn apply_identity_elimination(
nodes: &mut Vec<RawNode>,
outputs: &mut [Argument],
plan: IdentityEliminationPlan,
) {
let IdentityEliminationPlan {
rewire_map,
nodes_to_remove,
} = plan;
if nodes_to_remove.is_empty() {
return;
}
let mut output_arg_map: HashMap<String, Argument> = HashMap::new();
for node in nodes.iter() {
for output in &node.outputs {
output_arg_map.insert(output.name.clone(), output.clone());
}
}
let mut resolved_rewire_map = rewire_map.clone();
for (output, input) in rewire_map.iter() {
let mut current = input.clone();
let mut visited = HashSet::new();
visited.insert(output.clone());
while let Some(next) = resolved_rewire_map.get(¤t) {
if visited.contains(next) {
log::warn!("Cycle detected in rewiring: {:?}", visited);
break;
}
visited.insert(current.clone());
current = next.clone();
}
resolved_rewire_map.insert(output.clone(), current);
}
for node in nodes.iter_mut() {
for input in &mut node.inputs {
rewire_argument(input, &resolved_rewire_map, &output_arg_map);
}
}
for output in outputs.iter_mut() {
rewire_argument(output, &resolved_rewire_map, &output_arg_map);
}
*nodes = nodes
.drain(..)
.enumerate()
.filter_map(|(i, node)| (!nodes_to_remove.contains(&i)).then_some(node))
.collect();
}
pub(crate) fn post_process(
state_rc: &Rc<RefCell<GraphState>>,
) -> (Vec<RawNode>, Vec<Argument>, Vec<Argument>) {
let (mut nodes, inputs, mut outputs, node_output_map) = {
let mut state = state_rc.borrow_mut();
let tensor_store_rc = state.tensor_store.clone();
let constant_map_rc = state.constant_map_rc();
let node_output_map = state.node_output_map().clone();
let result = std::mem::replace(&mut *state, GraphState::new(&[], &[], &[], &[])).consume();
state.restore_stores(tensor_store_rc, constant_map_rc);
(result.0, result.1, result.2, node_output_map)
};
log::debug!("Starting Identity elimination");
{
let elimination_plan = plan_identity_elimination(&nodes, &node_output_map);
apply_identity_elimination(&mut nodes, &mut outputs, elimination_plan);
}
log::debug!("Re-running constant lifting after Identity elimination");
{
let mut state = state_rc.borrow_mut();
state.processed_nodes = nodes.clone();
let value_store = state.build_value_store();
drop(state);
for node in &mut nodes {
for arg in &mut node.inputs {
let should_preserve =
if let crate::ir::ValueSource::Static(data_id) = arg.value_source {
arg.value_store
.as_ref()
.map(|store| store.get_tensor_data(data_id).is_some())
.unwrap_or(false)
} else {
false
};
if !should_preserve {
arg.set_value_store(value_store.clone());
}
}
let registry = get_processor_registry();
let processor = registry.get(&node.node_type);
if let Err(e) = processor.lift_constants(node, DEFAULT_OPSET_VERSION) {
log::debug!(
"Could not lift constants for node '{}' (type: {:?}): {:?}",
node.name,
node.node_type,
e
);
}
}
}
(nodes, inputs, outputs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{ArgType, Argument, DType, NodeType, RawNode, TensorType};
fn create_identity_node(name: &str, input_name: &str, output_name: &str) -> RawNode {
RawNode {
node_type: NodeType::Identity,
name: name.to_string(),
inputs: vec![Argument {
name: input_name.to_string(),
ty: ArgType::Tensor(TensorType {
dtype: DType::F32,
rank: 2,
static_shape: None,
}),
value_source: crate::ir::ValueSource::Dynamic,
value_store: None,
}],
outputs: vec![Argument {
name: output_name.to_string(),
ty: ArgType::Tensor(TensorType {
dtype: DType::F32,
rank: 2,
static_shape: None,
}),
value_source: crate::ir::ValueSource::Dynamic,
value_store: None,
}],
attrs: Default::default(),
}
}
fn create_add_node(name: &str, input1: &str, input2: &str, output: &str) -> RawNode {
RawNode {
node_type: NodeType::Add,
name: name.to_string(),
inputs: vec![
Argument {
name: input1.to_string(),
ty: ArgType::Tensor(TensorType {
dtype: DType::F32,
rank: 2,
static_shape: None,
}),
value_source: crate::ir::ValueSource::Dynamic,
value_store: None,
},
Argument {
name: input2.to_string(),
ty: ArgType::Tensor(TensorType {
dtype: DType::F32,
rank: 2,
static_shape: None,
}),
value_source: crate::ir::ValueSource::Dynamic,
value_store: None,
},
],
outputs: vec![Argument {
name: output.to_string(),
ty: ArgType::Tensor(TensorType {
dtype: DType::F32,
rank: 2,
static_shape: None,
}),
value_source: crate::ir::ValueSource::Dynamic,
value_store: None,
}],
attrs: Default::default(),
}
}
#[test]
fn test_remove_single_identity() {
let nodes = vec![
create_identity_node("identity1", "input1", "identity1_out"),
create_add_node("add1", "identity1_out", "input2", "output1"),
];
let node_output_map = HashMap::new();
let plan = plan_identity_elimination(&nodes, &node_output_map);
assert_eq!(plan.nodes_to_remove.len(), 1);
assert!(plan.nodes_to_remove.contains(&0));
assert_eq!(
plan.rewire_map.get("identity1_out"),
Some(&"input1".to_string())
);
}
#[test]
fn test_remove_all_identities_in_empty_graph() {
let nodes = vec![
create_identity_node("identity1", "input1", "output1"),
create_identity_node("identity2", "input2", "output2"),
];
let node_output_map = HashMap::new();
let plan = plan_identity_elimination(&nodes, &node_output_map);
assert_eq!(plan.nodes_to_remove.len(), 2);
assert!(plan.nodes_to_remove.contains(&0));
assert!(plan.nodes_to_remove.contains(&1));
}
#[test]
fn test_apply_identity_elimination() {
let mut nodes = vec![
create_identity_node("identity1", "input1", "identity1_out"),
create_add_node("add1", "identity1_out", "input2", "add1_out"),
];
let mut outputs = vec![Argument {
name: "add1_out".to_string(),
ty: ArgType::Tensor(TensorType {
dtype: DType::F32,
rank: 2,
static_shape: None,
}),
value_source: crate::ir::ValueSource::Dynamic,
value_store: None,
}];
let node_output_map = HashMap::new();
let plan = plan_identity_elimination(&nodes, &node_output_map);
apply_identity_elimination(&mut nodes, &mut outputs, plan);
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].node_type, NodeType::Add);
assert_eq!(nodes[0].inputs[0].name, "input1");
}
}