use std::{
collections::{HashMap, HashSet},
fs::File,
path::Path,
};
use crate::onnx::{
coalesce::coalesce, ir::TensorType, node_remap::remap_node_type,
proto_conversion::convert_node_proto,
};
use super::dim_inference::dim_inference;
use super::ir::{ArgType, Argument, Node, NodeType, ONNXGraph, Tensor};
use super::protos::{ModelProto, TensorProto};
use protobuf::Message;
const LIFT_CONSTANTS_FOR_NODE_TYPES: [NodeType; 6] = [
NodeType::BatchNormalization,
NodeType::Clip,
NodeType::Conv1d,
NodeType::Conv2d,
NodeType::Dropout,
NodeType::Reshape,
];
pub fn parse_onnx(onnx_path: &Path) -> ONNXGraph {
log::info!("Parsing ONNX file: {}", onnx_path.display());
let mut file = File::open(onnx_path).expect("Unable to open file");
let onnx_model: ModelProto =
Message::parse_from_reader(&mut file).expect("Unable to parse ONNX file");
log::debug!("Number of nodes: {:?}", onnx_model.graph.node.len());
log::debug!("Number of inputs: {:?}", onnx_model.graph.input.len());
log::debug!(
"Number of initializers: {:?}",
onnx_model.graph.initializer.len()
);
log::debug!("Number of outputs: {:?}", onnx_model.graph.output.len());
let mut nodes: Vec<Node> = vec![];
for onnx_node in onnx_model.graph.node.iter() {
let mut node = convert_node_proto(onnx_node);
remap_node_type(&mut node);
nodes.push(node);
}
assert!(nodes.is_top_sorted(), "Nodes are not topologically sorted");
move_inputs_to_state(&mut nodes, &onnx_model.graph.initializer);
handle_identity(&mut nodes);
lift_constants(&mut nodes);
coalesce(&mut nodes);
let old_node_names = rename_nodes(&mut nodes);
let mut inputs = onnx_model
.graph
.input
.iter()
.map(|x| Argument::try_from(x.clone()).unwrap())
.collect();
let mut outputs = onnx_model
.graph
.output
.iter()
.map(|x| Argument::try_from(x.clone()).unwrap())
.collect();
let old_input_names = rename_inputs(&mut nodes, &mut inputs, &mut outputs);
dim_inference(&mut nodes, &inputs, &mut outputs);
remove_unused_graph_inputs(&mut inputs, &mut outputs, &nodes);
log::info!("Finished parsing ONNX file: {}", onnx_path.display());
ONNXGraph {
nodes,
inputs,
outputs,
old_node_names,
old_input_names,
}
}
fn move_inputs_to_state(nodes: &mut Vec<Node>, initializers: &[TensorProto]) {
let initializers = initializers
.iter()
.map(|x| (x.name.clone(), x.clone()))
.collect::<HashMap<String, TensorProto>>();
nodes.iter_mut().for_each(|node| {
for input in node.inputs.iter_mut() {
if let Some(initializer) = initializers.get(&input.name) {
move_initializer_data(initializer, input);
}
}
});
}
fn move_initializer_data(initializer: &TensorProto, input: &mut Argument) {
let tensor = Tensor::try_from(initializer.clone()).expect("Invalid tensor");
if tensor.dim == 0 {
if let Some(data) = tensor.data {
input.value = Some(data.into_scalar());
} else {
input.value = None;
}
input.ty = ArgType::Scalar(tensor.elem_type);
} else {
input.value = tensor.data.clone();
input.ty = ArgType::Tensor(TensorType {
dim: tensor.dim,
elem_type: tensor.elem_type,
shape: tensor.shape,
});
}
}
fn lift_constants(nodes: &mut Vec<Node>) {
log::info!("Lifting constants into the states");
let node_types_to_process: HashSet<NodeType> =
LIFT_CONSTANTS_FOR_NODE_TYPES.into_iter().collect();
let constants = nodes
.iter()
.filter(|node| node.node_type == NodeType::Constant || node.node_type == NodeType::Identity)
.map(|node| (node.outputs[0].name.clone(), node.clone()))
.collect::<HashMap<String, Node>>();
let mut constant_to_removed = HashSet::<String>::new();
for node in nodes.iter_mut() {
if !node_types_to_process.contains(&node.node_type) {
continue;
}
node.inputs
.iter_mut()
.skip(1) .for_each(|input| {
if let Some(constant) = constants.get(&input.name) {
if !constant.inputs.is_empty() && constant.inputs[0].value.is_some() {
if let Some(constant_input) = constant.inputs.first() {
input.ty = constant_input.ty.clone();
input.value = constant_input.value.clone();
}
} else {
let arg = convert_constant_value(constant);
input.value = arg.value; input.ty = arg.ty; }
constant_to_removed.insert(constant.name.clone());
}
});
}
nodes.retain(|node| !constant_to_removed.contains(&node.name));
log::debug!(
"The number of constants lifted: {}",
constant_to_removed.len()
);
}
fn handle_identity(nodes: &mut Vec<Node>) {
log::info!("Handling identity nodes");
let mut nodes_to_remove = HashSet::new();
let identity_nodes = nodes
.iter()
.filter(|node| node.node_type == NodeType::Identity)
.cloned()
.collect::<Vec<Node>>();
for identity_node in identity_nodes {
if identity_node.node_type == NodeType::Identity && identity_node.inputs[0].value.is_none()
{
let input_name = &identity_node.inputs[0].name;
let output_name = &identity_node.outputs[0].name;
for node in nodes.iter_mut() {
if let Some(matched_input) = node.inputs.iter_mut().find(|x| x.name == *output_name)
{
matched_input.name = input_name.clone();
}
}
nodes_to_remove.insert(identity_node);
}
}
nodes.retain(|node| !nodes_to_remove.contains(node));
}
fn rename_nodes(nodes: &mut Vec<Node>) -> HashMap<String, String> {
let mut old_names = HashMap::new();
let mut counter: HashMap<NodeType, usize> = HashMap::new();
for node in nodes.iter_mut() {
counter
.entry(node.node_type.clone())
.and_modify(|e| *e += 1)
.or_insert(1);
let old_name = node.name.clone();
let new_name = format!("{}{}", node.node_type, counter[&node.node_type]).to_lowercase();
node.name = new_name.clone();
old_names.insert(old_name, new_name);
}
old_names
}
fn rename_inputs(
nodes: &mut Vec<Node>,
inputs: &mut Vec<Argument>,
outputs: &mut Vec<Argument>,
) -> HashMap<String, String> {
let mut old_names = HashMap::new();
let mut counter = 1;
for input in inputs.iter_mut() {
let old_name = input.name.clone();
let new_name = format!("input{}", counter);
input.name = new_name.clone();
old_names.insert(old_name, new_name);
counter += 1;
}
for node in nodes.iter_mut() {
let mut counter = 1;
for output in node.outputs.iter_mut() {
let old_name = output.name.clone();
let new_name = format!("{}_out{}", node.name, counter);
output.name = new_name.clone();
old_names.insert(old_name, new_name);
counter += 1;
}
}
for node in nodes.iter_mut() {
for input in node.inputs.iter_mut() {
if let Some(new_name) = old_names.get(&input.name) {
input.name = new_name.clone();
input.passed = true;
} else {
input.name = "".to_string(); input.passed = false;
}
}
}
for output in outputs.iter_mut() {
if let Some(new_name) = old_names.get(&output.name) {
output.name = new_name.clone();
} else {
log::warn!("Output {:?} not found in old_names", output.name);
}
}
old_names
}
fn remove_unused_graph_inputs(
inputs: &mut Vec<Argument>,
outputs: &mut Vec<Argument>,
nodes: &Vec<Node>,
) {
inputs.retain(|input| {
for node in nodes.iter() {
if node
.inputs
.iter()
.any(|x| x.name == input.name && x.value.is_none())
{
return true;
}
}
false
});
outputs.retain(|output| {
for node in nodes.iter() {
if node.outputs.iter().any(|x| x.name == output.name) {
return true;
}
}
false
});
}
trait TopologicalSortable {
fn is_top_sorted(&self) -> bool;
}
impl TopologicalSortable for Vec<Node> {
fn is_top_sorted(&self) -> bool {
let position: HashMap<String, usize> = self
.iter()
.enumerate()
.map(|(idx, node)| (node.name.clone(), idx))
.collect();
for node in self {
for output in &node.outputs {
for other_node in self {
if other_node.inputs.contains(output) {
if position[&node.name] > position[&other_node.name] {
return false;
}
}
}
}
}
true
}
}
pub(crate) fn convert_constant_value(node: &Node) -> Argument {
let keys = [
"value",
"value_float",
"value_floats",
"value_int",
"value_ints",
"value_string",
"value_strings",
"sparse_value",
];
let value = keys
.iter()
.find_map(|&key| node.attrs.get(key).cloned())
.expect("Constant should have a value");
Argument::from(value)
}