use crate::{
Argument, TensorData,
ir::{ArgType, Data, Node, TensorType},
};
pub fn unsqueeze_update_output(node: &mut Node) {
log::debug!("Unsqueeze rank inference for node {}", node.name);
let axes = if node.inputs.len() == 2 {
match &node.inputs[1].value {
Some(value) => match &value.data {
Data::Int64s(a) => Some(a.clone()),
_ => panic!("Unsqueeze: invalid input types"),
},
None => None,
}
} else {
node.attrs.get("axes").cloned().map(|v| {
let axes = v.into_i64s();
log::debug!(
"Unsqueeze axes from attribute for {}: {:?}",
node.name,
axes
);
axes
})
};
let input_rank = match &node.inputs[0].ty {
ArgType::Tensor(tensor) => tensor.rank,
ArgType::Scalar(_) => {
0 }
_ => panic!("Unsqueeze: invalid input type"),
};
let output_rank = if let Some(axes) = axes {
input_rank + axes.len()
} else if let ArgType::Tensor(tensor) = &node.inputs[1].ty {
if let Some(static_shape) = &tensor.static_shape {
input_rank + *static_shape.first().expect("Empty shape")
} else {
panic!("Unsqueeze: should have static shape")
}
} else {
panic!("Unsqueeze: missing axes information")
};
match &node.inputs[0].ty {
ArgType::Scalar(elem_type) if output_rank == 1 => {
match elem_type {
crate::ir::ElementType::Int32 | crate::ir::ElementType::Int64 => {
node.outputs[0].ty = ArgType::Shape(1);
}
_ => {
node.outputs[0].ty = ArgType::Tensor(TensorType {
rank: output_rank,
static_shape: None,
elem_type: elem_type.clone(),
});
}
}
}
_ => {
let output_elem = match &node.outputs[0].ty {
ArgType::Tensor(_) => node.inputs[0].ty.elem_type().clone(),
ArgType::Scalar(elem_type) => elem_type.clone(),
ArgType::Shape(_) => crate::ir::ElementType::Int64, };
node.outputs[0].ty = ArgType::Tensor(TensorType {
rank: output_rank,
static_shape: None, elem_type: output_elem,
});
}
}
log::debug!("Unsqueeze output rank for {}: {}", node.name, output_rank);
}
#[derive(Debug, Clone)]
pub enum UnsqueezeConfig {
Static(Vec<i64>),
Runtime(Argument),
}
pub fn unsqueeze_config(node: &Node) -> UnsqueezeConfig {
for (key, value) in node.attrs.iter() {
if key.as_str() == "axes" {
return UnsqueezeConfig::Static(value.clone().into_i64s());
}
}
assert!(
!node.inputs.is_empty(),
"Unsqueeze: axes tensor must be present"
);
let input_value = &node.inputs[1];
match &node.inputs[1].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.rank, 1, "Unsqueeze: axes tensor must be 1D");
if let Some(TensorData {
data: Data::Int64s(shape),
..
}) = input_value.value.as_ref()
{
UnsqueezeConfig::Static(shape.clone())
} else {
UnsqueezeConfig::Runtime(node.inputs[1].clone())
}
}
_ => panic!("Arg for unsqueeze must be tensor or scalar"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{ElementType, NodeType};
use crate::node::test_utils::NodeBuilder;
impl PartialEq<UnsqueezeConfig> for UnsqueezeConfig {
fn eq(&self, other: &UnsqueezeConfig) -> bool {
match (self, other) {
(UnsqueezeConfig::Static(a), UnsqueezeConfig::Static(b)) => a == b,
(UnsqueezeConfig::Runtime(a), UnsqueezeConfig::Runtime(b)) => a.name == b.name,
_ => false,
}
}
}
fn create_test_node_with_attr(input_rank: usize, axes: Vec<i64>) -> Node {
let builder = NodeBuilder::new(NodeType::Unsqueeze, "test_unsqueeze")
.input_tensor_f32("X", input_rank, None)
.output_tensor_f32("Y", 0, None) .attr_ints("axes", axes);
builder.build()
}
fn create_test_node_with_input(input_rank: usize, axes: Vec<i64>, with_value: bool) -> Node {
let axes_len = axes.len();
let mut builder = NodeBuilder::new(NodeType::Unsqueeze, "test_unsqueeze")
.input_tensor_f32("X", input_rank, None)
.output_tensor_f32("Y", 0, None);
if with_value {
builder = builder.input_tensor_i64_data("axes", axes.clone(), vec![axes_len]);
} else {
builder = builder.input_tensor_i64("axes", 1, Some(vec![axes_len]));
}
builder.build()
}
#[test]
fn test_unsqueeze_with_attr() {
let mut node = create_test_node_with_attr(2, vec![0, 3]);
unsqueeze_update_output(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 4); }
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_unsqueeze_with_input() {
let mut node = create_test_node_with_input(3, vec![1, 2, 4], true);
unsqueeze_update_output(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 6); }
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_unsqueeze_scalar_float() {
let mut node = create_test_node_with_attr(0, vec![0]);
node.inputs[0].ty = ArgType::Scalar(ElementType::Float32);
unsqueeze_update_output(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 1); }
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_unsqueeze_scalar_int_to_shape() {
let mut node = create_test_node_with_attr(0, vec![0]);
node.inputs[0].ty = ArgType::Scalar(ElementType::Int64);
unsqueeze_update_output(&mut node);
match &node.outputs[0].ty {
ArgType::Shape(rank) => {
assert_eq!(*rank, 1); }
_ => panic!("Expected Shape output for Int scalar unsqueeze"),
}
}
#[test]
fn test_unsqueeze_scalar_int32_to_shape() {
let mut node = create_test_node_with_attr(0, vec![0]);
node.inputs[0].ty = ArgType::Scalar(ElementType::Int32);
unsqueeze_update_output(&mut node);
match &node.outputs[0].ty {
ArgType::Shape(rank) => {
assert_eq!(*rank, 1); }
_ => panic!("Expected Shape output for Int32 scalar unsqueeze"),
}
}
#[test]
fn test_unsqueeze_scalar_int_multiple_axes() {
let mut node = create_test_node_with_attr(0, vec![0, 1]);
node.inputs[0].ty = ArgType::Scalar(ElementType::Int64);
unsqueeze_update_output(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Int64);
assert_eq!(tensor.rank, 2); }
_ => panic!("Expected tensor output for multi-axis unsqueeze"),
}
}
#[test]
#[should_panic(expected = "Unsqueeze: invalid input type")]
fn test_unsqueeze_invalid_input() {
let mut node = create_test_node_with_attr(2, vec![0]);
node.inputs[0].ty = ArgType::Shape(1);
unsqueeze_update_output(&mut node);
}
#[test]
fn test_unsqueeze_config_with_attr() {
let axes = vec![0, 2, 4];
let node = create_test_node_with_attr(3, axes.clone());
let config = unsqueeze_config(&node);
assert_eq!(config, UnsqueezeConfig::Static(axes));
}
#[test]
fn test_unsqueeze_config_with_static_input() {
let axes = vec![1, 3];
let node = create_test_node_with_input(2, axes.clone(), true);
let config = unsqueeze_config(&node);
assert_eq!(config, UnsqueezeConfig::Static(axes));
}
#[test]
fn test_unsqueeze_config_with_runtime_input() {
let axes = vec![0, 2];
let node = create_test_node_with_input(2, axes.clone(), false);
let config = unsqueeze_config(&node);
match config {
UnsqueezeConfig::Static(_) => panic!("Expected Runtime config"),
UnsqueezeConfig::Runtime(arg) => {
assert_eq!(arg.name, "axes");
}
}
}
#[test]
fn test_unsqueeze_config_negative_axes() {
let axes = vec![-1, -3];
let node = create_test_node_with_attr(3, axes.clone());
let config = unsqueeze_config(&node);
assert_eq!(config, UnsqueezeConfig::Static(axes));
}
#[test]
fn test_unsqueeze_config_empty_axes() {
let axes = vec![];
let node = create_test_node_with_attr(2, axes.clone());
let config = unsqueeze_config(&node);
assert_eq!(config, UnsqueezeConfig::Static(axes));
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_unsqueeze_config_missing_axes() {
let mut node = create_test_node_with_attr(2, vec![0]);
node.attrs.clear(); node.inputs = vec![node.inputs[0].clone()];
let _ = unsqueeze_config(&node);
}
#[test]
#[should_panic(expected = "Unsqueeze: axes tensor must be 1D")]
fn test_unsqueeze_config_invalid_axes_rank() {
let mut node = create_test_node_with_input(2, vec![0, 1], true);
if let ArgType::Tensor(ref mut tensor) = node.inputs[1].ty {
tensor.rank = 2; }
let _ = unsqueeze_config(&node);
}
#[test]
#[should_panic(expected = "Arg for unsqueeze must be tensor or scalar")]
fn test_unsqueeze_config_invalid_axes_type() {
let mut node = create_test_node_with_input(2, vec![0], false);
node.inputs[1].ty = ArgType::Shape(1);
let _ = unsqueeze_config(&node);
}
}