use crate::ir::{ArgType, Node, TensorType};
pub fn split_update_outputs(node: &mut Node) {
log::debug!("Split rank inference for node {}", node.name);
let tensor = match &node.inputs[0].ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Split: Input must be a tensor"),
};
log::debug!("Split input rank for {}: {}", node.name, tensor.rank);
log::debug!(
"Split will generate {} outputs for {}",
node.outputs.len(),
node.name
);
for (i, output_arg) in node.outputs.iter_mut().enumerate() {
output_arg.ty = ArgType::Tensor(TensorType {
elem_type: tensor.elem_type.clone(),
rank: tensor.rank,
static_shape: None,
});
log::debug!("Split output {} rank for {}: {}", i, node.name, tensor.rank);
}
}
#[derive(Clone, Debug)]
pub struct SplitConfig {
pub axis: usize,
pub split_size: Option<usize>,
pub split_sizes: Option<Vec<usize>>,
}
impl SplitConfig {
pub fn new(axis: usize, split_size: Option<usize>, split_sizes: Option<Vec<usize>>) -> Self {
SplitConfig {
axis,
split_size,
split_sizes,
}
}
}
pub fn split_config(node: &Node) -> SplitConfig {
let mut axis: i64 = 0;
let mut split_size: Option<usize> = None;
let mut split_sizes: Option<Vec<usize>> = None;
let tensor = match node.inputs.first().unwrap().ty {
ArgType::Tensor(ref tensor) => tensor,
_ => panic!("Split: Input must be a valid tensor"),
};
let mut num_outputs: Option<usize> = None;
for (key, value) in node.attrs.iter() {
match key.as_str() {
"axis" => axis = value.clone().into_i64(),
"num_outputs" => num_outputs = Some(value.clone().into_i64() as usize),
_ => {}
}
}
if let Some(num_outputs) = num_outputs {
if num_outputs == 0 {
panic!("Split: 'num_outputs' must be a positive value greater than zero");
}
let dim_size = tensor
.static_shape
.as_ref()
.expect("Split: Static shape must be known to calculate split size")[axis as usize];
let calculated_split_size =
dim_size / (num_outputs - (dim_size % num_outputs != 0) as usize);
if calculated_split_size == 0 {
panic!(
"Split: Calculated split size is zero. Please ensure 'num_outputs' is valid for the dimension size"
);
}
split_size = Some(calculated_split_size);
}
if axis < 0 {
axis += tensor.rank as i64;
}
if node.inputs.len() > 1 && node.inputs[1].value.is_some() {
let sizes = node.inputs[1]
.value
.as_ref()
.unwrap()
.data
.clone()
.into_usizes();
if !sizes.is_empty() {
split_sizes = Some(sizes);
}
}
if split_sizes.is_some() && split_size.is_some() {
panic!(
"Split: Cannot specify both 'split' input and 'num_outputs' attribute simultaneously"
);
}
if split_sizes.is_none() && split_size.is_none() {
let num_outputs = node.outputs.len();
let dim_size = tensor
.static_shape
.as_ref()
.expect("Split: Static shape must be known to infer split size")[axis as usize];
let calculated_split_size =
dim_size / (num_outputs - (dim_size % num_outputs != 0) as usize);
if calculated_split_size == 0 {
panic!(
"Split: Inferred split size is zero. Please ensure the number of outputs is valid for the dimension size"
);
}
split_size = Some(calculated_split_size);
}
SplitConfig {
axis: axis as usize,
split_size,
split_sizes,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{ArgType, AttributeValue, ElementType, NodeType};
use crate::node::test_utils::NodeBuilder;
use std::collections::HashMap;
fn create_test_node(
input_rank: usize,
num_outputs: usize,
static_shape: Option<Vec<usize>>,
attrs: Option<HashMap<String, AttributeValue>>,
split_sizes_input: Option<Vec<i64>>,
) -> Node {
let mut builder = NodeBuilder::new(NodeType::Split, "test_split").input_tensor_f32(
"input",
input_rank,
static_shape,
);
if let Some(sizes) = split_sizes_input {
builder = builder.input_tensor_i64_data("split", sizes.clone(), vec![sizes.len()]);
}
for i in 0..num_outputs {
builder = builder.output_tensor_f32(
&format!("output_{i}"),
0, None,
);
}
let mut node = builder.build();
if let Some(attributes) = attrs {
node.attrs = attributes;
}
node
}
#[test]
fn test_split_single_output() {
let mut node = create_test_node(3, 1, None, None, None);
split_update_outputs(&mut node);
assert_eq!(node.outputs.len(), 1);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 3);
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_split_multiple_outputs() {
let mut node = create_test_node(4, 3, None, None, None);
split_update_outputs(&mut node);
assert_eq!(node.outputs.len(), 3);
for output in &node.outputs {
match &output.ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 4);
}
_ => panic!("Expected tensor output"),
}
}
}
#[test]
#[should_panic(expected = "Split: Input must be a tensor")]
fn test_split_invalid_input() {
let mut node = create_test_node(3, 2, None, None, None);
node.inputs[0].ty = ArgType::Scalar(ElementType::Float32);
split_update_outputs(&mut node);
}
#[test]
fn test_split_config_default_axis() {
let static_shape = Some(vec![10, 20, 30]);
let node = create_test_node(3, 2, static_shape, None, None);
let config = split_config(&node);
assert_eq!(config.axis, 0);
assert_eq!(config.split_size, Some(5)); assert_eq!(config.split_sizes, None);
}
#[test]
fn test_split_config_specified_axis() {
let static_shape = Some(vec![10, 20, 30]);
let mut attrs = HashMap::new();
attrs.insert("axis".to_string(), AttributeValue::Int64(1));
let node = create_test_node(3, 2, static_shape, Some(attrs), None);
let config = split_config(&node);
assert_eq!(config.axis, 1);
assert_eq!(config.split_size, Some(10)); assert_eq!(config.split_sizes, None);
}
#[test]
fn test_split_config_negative_axis() {
let static_shape = Some(vec![10, 20, 30]);
let mut attrs = HashMap::new();
attrs.insert("axis".to_string(), AttributeValue::Int64(-1));
let node = create_test_node(3, 3, static_shape, Some(attrs), None);
let config = split_config(&node);
assert_eq!(config.axis, 2); assert_eq!(config.split_size, Some(10)); assert_eq!(config.split_sizes, None);
}
#[test]
fn test_split_config_num_outputs_attr() {
let static_shape = Some(vec![12, 24, 36]);
let mut attrs = HashMap::new();
attrs.insert("num_outputs".to_string(), AttributeValue::Int64(4));
let node = create_test_node(3, 4, static_shape, Some(attrs), None);
let config = split_config(&node);
assert_eq!(config.axis, 0);
assert_eq!(config.split_size, Some(3)); assert_eq!(config.split_sizes, None);
}
#[test]
fn test_split_config_with_split_sizes_input() {
let static_shape = Some(vec![10, 20, 30]);
let split_sizes = vec![5, 15];
let node = create_test_node(3, 2, static_shape, None, Some(split_sizes.clone()));
let config = split_config(&node);
assert_eq!(config.axis, 0);
assert_eq!(config.split_size, None);
assert_eq!(config.split_sizes, Some(vec![5, 15]));
}
#[test]
#[should_panic(
expected = "Split: Cannot specify both 'split' input and 'num_outputs' attribute simultaneously"
)]
fn test_split_config_both_splits_and_num_outputs() {
let static_shape = Some(vec![10, 20, 30]);
let mut attrs = HashMap::new();
attrs.insert("num_outputs".to_string(), AttributeValue::Int64(2));
let split_sizes = vec![3, 7];
let node = create_test_node(3, 2, static_shape, Some(attrs), Some(split_sizes));
let _ = split_config(&node);
}
#[test]
#[should_panic(expected = "Split: 'num_outputs' must be a positive value greater than zero")]
fn test_split_config_zero_num_outputs() {
let static_shape = Some(vec![10, 20, 30]);
let mut attrs = HashMap::new();
attrs.insert("num_outputs".to_string(), AttributeValue::Int64(0));
let node = create_test_node(3, 0, static_shape, Some(attrs), None);
let _ = split_config(&node);
}
#[test]
#[should_panic(expected = "Split: Calculated split size is zero")]
fn test_split_config_invalid_num_outputs() {
let static_shape = Some(vec![5, 10, 15]);
let mut attrs = HashMap::new();
attrs.insert("num_outputs".to_string(), AttributeValue::Int64(10));
let node = create_test_node(3, 10, static_shape, Some(attrs), None);
let _ = split_config(&node);
}
#[test]
#[should_panic(expected = "Split: Static shape must be known to calculate split size")]
fn test_split_config_no_static_shape() {
let mut attrs = HashMap::new();
attrs.insert("num_outputs".to_string(), AttributeValue::Int64(2));
let node = create_test_node(3, 2, None, Some(attrs), None);
let _ = split_config(&node);
}
#[test]
#[should_panic(expected = "Split: Input must be a valid tensor")]
fn test_split_config_invalid_input_type() {
let mut node = create_test_node(3, 2, Some(vec![10, 20, 30]), None, None);
node.inputs[0].ty = ArgType::Scalar(ElementType::Float32);
let _ = split_config(&node);
}
#[test]
fn test_split_config_non_even_split() {
let static_shape = Some(vec![11, 22, 33]); let mut attrs = HashMap::new();
attrs.insert("axis".to_string(), AttributeValue::Int64(0));
let node = create_test_node(3, 3, static_shape, Some(attrs), None);
let config = split_config(&node);
assert_eq!(config.split_size, Some(5));
}
}