use std::{iter::Peekable, slice::IterMut};
use super::ir::{AttributeValue, Node, NodeType};
use crate::onnx::ir::{ArgType, Data, TensorType};
pub fn coalesce(nodes: &mut Vec<Node>) {
let mut iter_mut = nodes.iter_mut().peekable();
let mut nodes_to_remove: Vec<String> = vec![];
while let Some(node) = iter_mut.next() {
match node.node_type {
NodeType::Gemm => convert_gemm_to_linear(node),
NodeType::MatMul => {
convert_matmul_to_linear(node, &mut iter_mut, &mut nodes_to_remove);
}
_ => {}
}
}
for node_to_remove in nodes_to_remove {
nodes.retain(|n| n.name != node_to_remove);
}
}
fn convert_gemm_to_linear(node: &mut Node) {
if node.outputs.len() != 1 {
panic!("Gemm node must have 1 output");
}
let straight_linear = match (
node.attrs.get("alpha"),
node.attrs.get("beta"),
node.attrs.get("transB"),
) {
(
Some(AttributeValue::Float32(alpha)),
Some(AttributeValue::Float32(beta)),
Some(AttributeValue::Int64(trans_b)),
) => *alpha == 1.0 && *beta == 1.0 && *trans_b == 1,
_ => false,
};
if straight_linear {
node.node_type = NodeType::Linear;
node.attrs.remove("alpha");
node.attrs.remove("beta");
node.attrs.remove("transB");
transpose_linear_node_weights(node);
} else {
panic!("Full Gemm node not supported yet.");
}
}
fn transpose_linear_node_weights(node: &mut Node) {
assert!(
node.inputs.len() > 1,
"Linear node must have at least 2 input"
);
assert!(node.inputs[1].value.is_some(), "Input must have a value");
let weight = node.inputs[1]
.clone()
.into_tensor()
.expect("Tensor input is expected");
assert_eq!(weight.dim, 2, "Weight must be a 2D tensor");
let shape = weight.shape.unwrap();
match weight.data.expect("Tensor must have data") {
Data::Float32s(data) => {
let data_t = transpose_flattened(data, shape[0], shape[1]);
node.inputs[1].value = Some(Data::Float32s(data_t));
}
Data::Float64s(data) => {
let data_t = transpose_flattened(data, shape[0], shape[1]);
node.inputs[1].value = Some(Data::Float64s(data_t));
}
Data::Float16s(data) => {
let data_t = transpose_flattened(data, shape[0], shape[1]);
node.inputs[1].value = Some(Data::Float16s(data_t));
}
_ => panic!("Only float types are supported for Linear node"),
}
let shape = Some(vec![shape[1], shape[0]]); node.inputs[1].ty = ArgType::Tensor(TensorType {
shape,
elem_type: weight.elem_type,
dim: 2,
});
}
fn transpose_flattened<T: Copy>(matrix: Vec<T>, rows: usize, cols: usize) -> Vec<T> {
assert_eq!(matrix.len(), rows * cols, "Matrix must be flattened");
let mut transposed: Vec<T> = vec![matrix[0]; matrix.len()];
for i in 0..rows {
for j in 0..cols {
transposed[j * rows + i] = matrix[i * cols + j];
}
}
transposed
}
fn convert_matmul_to_linear(
node: &mut Node,
iter_mut: &mut Peekable<IterMut<Node>>,
nodes_to_remove: &mut Vec<String>,
) {
if node.inputs.len() != 2 {
panic!("MatMul node must have 2 inputs");
}
if node.inputs[1].value.is_none() {
return;
}
if let ArgType::Tensor(ref tensor_type) = node.inputs[1].ty {
assert_eq!(tensor_type.dim, 2, "Weight must be a 2D tensor");
} else {
panic!("Tensor input is expected");
}
node.node_type = NodeType::Linear;
if let Some(peek_node) = iter_mut.peek() {
if is_add_node_with_bias(peek_node, node) {
convert_and_remove_add_node(iter_mut, nodes_to_remove, node);
}
}
}
fn is_add_node_with_bias(peek_node: &Node, current_node: &Node) -> bool {
peek_node.node_type == NodeType::Add
&& peek_node.inputs.len() == 2
&& ((peek_node.inputs[0].name == current_node.outputs[0].name
&& peek_node.inputs[1].value.is_some())
|| (peek_node.inputs[1].name == current_node.outputs[0].name
&& peek_node.inputs[0].value.is_some()))
}
fn convert_and_remove_add_node(
iter_mut: &mut Peekable<IterMut<Node>>,
nodes_to_remove: &mut Vec<String>,
current_node: &mut Node,
) {
let bias_node = iter_mut.next().unwrap();
let bias_input = if bias_node.inputs[0].value.is_some() {
bias_node.inputs[0].clone()
} else {
bias_node.inputs[1].clone()
};
current_node.inputs.push(bias_input);
current_node.outputs[0].name = bias_node.outputs[0].name.clone();
nodes_to_remove.push(bias_node.name.clone());
}