onnx_runtime_optimizer/
dead_node.rs1use std::collections::HashSet;
5
6use onnx_runtime_ir::{Graph, NodeId, ValueId};
7
8use crate::error::Result;
9use crate::pass::{OptimizationPass, PassContext};
10
11#[derive(Clone, Copy, Debug, Default)]
20pub struct DeadNodeElimination;
21
22impl DeadNodeElimination {
23 fn live_nodes(graph: &Graph) -> HashSet<NodeId> {
25 let mut live: HashSet<NodeId> = HashSet::new();
26 let mut seen_values: HashSet<ValueId> = HashSet::new();
27 let mut stack: Vec<ValueId> = graph.outputs.clone();
28
29 while let Some(v) = stack.pop() {
30 if !seen_values.insert(v) {
31 continue;
32 }
33 let Some(val) = graph.try_value(v) else {
34 continue;
35 };
36 if let Some(prod) = val.producer
37 && live.insert(prod)
38 {
39 for iv in graph.node(prod).input_values() {
40 stack.push(iv);
41 }
42 }
43 }
44 live
45 }
46}
47
48impl OptimizationPass for DeadNodeElimination {
49 fn name(&self) -> &str {
50 "DeadNodeElimination"
51 }
52
53 fn run(&self, graph: &mut Graph, _ctx: &PassContext) -> Result<()> {
54 let live = Self::live_nodes(graph);
55 let dead: Vec<NodeId> = graph
56 .nodes
57 .keys()
58 .filter(|nid| !live.contains(nid))
59 .collect();
60 for nid in dead {
61 graph.remove_node(nid);
62 }
63 Ok(())
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use onnx_runtime_ir::{DataType, Node, NodeId, static_shape};
71
72 fn graph_with_dangling_branch() -> (Graph, NodeId, NodeId) {
75 let mut g = Graph::new();
76 g.opset_imports.insert(String::new(), 17);
77 let a = g.create_named_value("a", DataType::Float32, static_shape([4]));
78 let c = g.create_named_value("c", DataType::Float32, static_shape([4]));
79 g.add_input(a);
80 g.add_input(c);
81
82 let b = g.create_value(DataType::Float32, static_shape([4]));
83 let relu = g.insert_node(Node::new(NodeId(0), "Relu", vec![Some(a)], vec![b]));
84
85 let d = g.create_named_value("d", DataType::Float32, static_shape([4]));
86 g.insert_node(Node::new(NodeId(0), "Add", vec![Some(b), Some(c)], vec![d]));
87 g.add_output(d);
88
89 let e = g.create_value(DataType::Float32, static_shape([4]));
91 let neg = g.insert_node(Node::new(NodeId(0), "Neg", vec![Some(a)], vec![e]));
92
93 (g, relu, neg)
94 }
95
96 #[test]
97 fn removes_dangling_branch() {
98 let (mut g, _relu, neg) = graph_with_dangling_branch();
99 assert_eq!(g.num_nodes(), 3);
100 DeadNodeElimination
101 .run(&mut g, &PassContext::new())
102 .unwrap();
103 assert_eq!(g.num_nodes(), 2);
104 assert!(g.try_node(neg).is_none());
105 assert!(g.validate().is_ok());
106 }
107
108 #[test]
109 fn keeps_live_nodes() {
110 let (mut g, relu, _neg) = graph_with_dangling_branch();
111 DeadNodeElimination
112 .run(&mut g, &PassContext::new())
113 .unwrap();
114 assert!(g.try_node(relu).is_some());
116 assert!(g.validate().is_ok());
117 }
118
119 #[test]
120 fn dead_value_is_garbage_collected() {
121 let (mut g, _relu, _neg) = graph_with_dangling_branch();
122 let before = g.num_values();
123 DeadNodeElimination
124 .run(&mut g, &PassContext::new())
125 .unwrap();
126 assert!(g.num_values() < before);
128 }
129
130 #[test]
131 fn noop_on_all_live_graph() {
132 let mut g = Graph::new();
133 g.opset_imports.insert(String::new(), 17);
134 let a = g.create_named_value("a", DataType::Float32, static_shape([2]));
135 g.add_input(a);
136 let b = g.create_named_value("b", DataType::Float32, static_shape([2]));
137 g.insert_node(Node::new(NodeId(0), "Relu", vec![Some(a)], vec![b]));
138 g.add_output(b);
139
140 DeadNodeElimination
141 .run(&mut g, &PassContext::new())
142 .unwrap();
143 assert_eq!(g.num_nodes(), 1);
144 assert!(g.validate().is_ok());
145 }
146
147 #[test]
148 fn removes_transitively_dead_chain() {
149 let mut g = Graph::new();
151 g.opset_imports.insert(String::new(), 17);
152 let a = g.create_named_value("a", DataType::Float32, static_shape([2]));
153 g.add_input(a);
154
155 let b = g.create_value(DataType::Float32, static_shape([2]));
156 g.insert_node(Node::new(NodeId(0), "Relu", vec![Some(a)], vec![b]));
157 let e = g.create_value(DataType::Float32, static_shape([2]));
158 g.insert_node(Node::new(NodeId(0), "Neg", vec![Some(b)], vec![e]));
159
160 let out = g.create_named_value("out", DataType::Float32, static_shape([2]));
161 g.insert_node(Node::new(NodeId(0), "Abs", vec![Some(a)], vec![out]));
162 g.add_output(out);
163
164 DeadNodeElimination
165 .run(&mut g, &PassContext::new())
166 .unwrap();
167 assert_eq!(g.num_nodes(), 1); assert!(g.validate().is_ok());
169 }
170}