use crate::Argument;
use crate::ir::{ArgType, Data, Node, TensorType};
#[derive(Debug, Clone)]
pub struct GatherConfig {
pub indices: GatherInput,
pub axis: usize,
}
#[derive(Debug, Clone)]
pub enum GatherInput {
Static(Vec<i64>),
Runtime(Argument),
}
pub fn gather_update_outputs(node: &mut Node) {
log::debug!("Gather rank inference for node {}", node.name);
if node.inputs.len() != 2 {
panic!("Gather requires two inputs: data and indices");
}
let indices_rank = match &node.inputs[1].ty {
ArgType::Tensor(tensor) => tensor.rank,
ArgType::Scalar(_) => 0,
ArgType::Shape(shape_rank) => {
log::debug!("Gather indices are Shape({}) for {}", shape_rank, node.name);
1 }
};
log::debug!("Gather indices rank for {}: {}", node.name, indices_rank);
match &node.inputs[0].ty {
ArgType::Tensor(input_tensor) => {
log::debug!(
"Gather input tensor rank for {}: {}",
node.name,
input_tensor.rank
);
let output_rank = indices_rank + input_tensor.rank - 1;
log::debug!("Gather output rank for {}: {}", node.name, output_rank);
if output_rank == 0 {
node.outputs[0].ty = ArgType::Scalar(input_tensor.elem_type.clone());
log::debug!("Gather result for {} is scalar", node.name);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: input_tensor.elem_type.clone(),
rank: output_rank,
static_shape: None,
});
log::debug!(
"Gather result for {} is tensor with rank {}",
node.name,
output_rank
);
}
}
ArgType::Shape(_shape_rank) => {
log::debug!("Gather input is shape for {}", node.name);
if indices_rank == 0 {
node.outputs[0].ty = ArgType::Scalar(crate::ir::ElementType::Int64);
log::debug!("Gather result for {} is scalar (from shape)", node.name);
} else {
let output_shape_rank = match &node.inputs[1].ty {
ArgType::Shape(shape_rank) => *shape_rank,
ArgType::Tensor(_) => indices_rank, _ => indices_rank,
};
node.outputs[0].ty = ArgType::Shape(output_shape_rank);
log::debug!(
"Gather result for {} is shape with rank {} (from shape)",
node.name,
output_shape_rank
);
}
}
ty => panic!("Only tensor/shape input is valid, got {ty:?}"),
}
}
pub fn gather_config(curr: &Node) -> GatherConfig {
let mut dim: i64 = 0;
if curr.inputs.len() != 2 {
panic!("Gather: index tensor must be present");
}
let input_dim = match curr.inputs.first().unwrap().clone().ty {
ArgType::Tensor(tensor) => tensor.rank as i64,
ArgType::Shape(shape_rank) => shape_rank as i64, other => panic!("Only tensor or shape input is valid, got {other:?}"),
};
for (key, value) in curr.attrs.iter() {
if key.as_str() == "axis" {
dim = value.clone().into_i64()
}
}
if dim < 0 {
dim += input_dim;
}
let indices_input = &curr.inputs[1];
log::debug!(
"Gather indices input for {}: {:?}",
curr.name,
indices_input
);
let indices = if let Some(value) = &indices_input.value {
log::debug!("Gather {} has static indices value: {:?}", curr.name, value);
match &value.data {
Data::Int64s(vals) => {
log::debug!("Gather {} static indices: {:?}", curr.name, vals);
GatherInput::Static(vals.clone())
}
Data::Int32s(vals) => {
let int64_vals = vals.iter().map(|&v| v as i64).collect::<Vec<_>>();
log::debug!(
"Gather {} static indices (from int32): {:?}",
curr.name,
int64_vals
);
GatherInput::Static(int64_vals)
}
other => panic!("Gather indices must be int32 or int64, got {other:?}"),
}
} else {
log::debug!("Gather {} has runtime indices", curr.name);
GatherInput::Runtime(indices_input.clone())
};
GatherConfig {
indices,
axis: dim as usize,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::NodeType;
use crate::node::test_utils::NodeBuilder;
fn create_test_node(axis: i64, input_rank: usize, is_shape: bool) -> Node {
let mut builder = NodeBuilder::new(NodeType::Gather, "test_gather").attr_int("axis", axis);
if is_shape {
builder = builder.add_input("data", ArgType::Shape(input_rank));
} else {
builder = builder.input_tensor_f32("data", input_rank, None);
}
builder = builder
.input_tensor_i64("indices", 1, None)
.output_tensor_f32("output", input_rank, None);
builder.build()
}
#[test]
fn test_gather_config_basic() {
let node = create_test_node(0, 3, false);
let config = gather_config(&node);
assert_eq!(config.axis, 0);
}
#[test]
fn test_gather_config_negative_axis() {
let node = create_test_node(-2, 3, false);
let config = gather_config(&node);
assert_eq!(config.axis, 1); }
#[test]
fn test_gather_config_shape_input() {
let node = create_test_node(0, 4, true); let config = gather_config(&node);
assert_eq!(config.axis, 0);
}
#[test]
#[should_panic(expected = "Gather: index tensor must be present")]
fn test_gather_config_missing_index() {
let mut node = create_test_node(0, 3, false);
node.inputs.pop(); let _ = gather_config(&node);
}
fn create_runtime_gather_node(axis: i64, input_rank: usize) -> Node {
let builder = NodeBuilder::new(NodeType::Gather, "test_runtime_gather")
.attr_int("axis", axis)
.input_tensor_f32("data", input_rank, None)
.input_tensor_i64("indices", 1, None) .output_tensor_f32("output", input_rank, None);
builder.build()
}
#[test]
fn test_gather_config_runtime_indices() {
let node = create_runtime_gather_node(0, 3);
let config = gather_config(&node);
assert_eq!(config.axis, 0);
match config.indices {
GatherInput::Runtime(arg) => {
assert_eq!(arg.name, "indices");
}
_ => panic!("Expected runtime indices"),
}
}
#[test]
fn test_gather_config_static_indices() {
let builder = NodeBuilder::new(NodeType::Gather, "test_static_gather")
.attr_int("axis", 1)
.input_tensor_f32("data", 3, None)
.input_tensor_i64_data("indices", vec![0, 2, 1], vec![3])
.output_tensor_f32("output", 3, None);
let node = builder.build();
let config = gather_config(&node);
assert_eq!(config.axis, 1);
match config.indices {
GatherInput::Static(vals) => {
assert_eq!(vals, vec![0, 2, 1]);
}
_ => panic!("Expected static indices"),
}
}
#[test]
fn test_gather_update_outputs_scalar_result() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_scalar_gather")
.attr_int("axis", 0)
.input_tensor_f32("data", 1, None)
.add_input("indices", ArgType::Scalar(crate::ir::ElementType::Int64))
.output_tensor_f32("output", 1, None)
.build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Scalar(elem_type) => {
assert_eq!(*elem_type, crate::ir::ElementType::Float32);
}
other => panic!("Expected scalar output, got {:?}", other),
}
}
#[test]
fn test_gather_update_outputs_tensor_result() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_tensor_gather")
.attr_int("axis", 0)
.input_tensor_f32("data", 2, None)
.input_tensor_i64("indices", 1, None)
.output_tensor_f32("output", 2, None)
.build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.rank, 2);
assert_eq!(tensor.elem_type, crate::ir::ElementType::Float32);
}
other => panic!("Expected tensor output, got {:?}", other),
}
}
#[test]
fn test_gather_update_outputs_shape_indices() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_gather_shape_indices")
.attr_int("axis", 0)
.input_shape("data", 3) .add_input("indices", ArgType::Shape(1)) .output_shape("output", 1) .build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Shape(rank) => {
assert_eq!(*rank, 1);
}
other => panic!("Expected Shape output, got {:?}", other),
}
}
#[test]
fn test_gather_update_outputs_shape_scalar_indices() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_gather_shape_scalar")
.attr_int("axis", 0)
.input_shape("data", 2) .add_input("indices", ArgType::Scalar(crate::ir::ElementType::Int64)) .output_tensor_i64("output", 0, None) .build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Scalar(elem_type) => {
assert_eq!(*elem_type, crate::ir::ElementType::Int64);
}
other => panic!("Expected scalar output, got {:?}", other),
}
}
#[test]
fn test_gather_update_outputs_shape_with_shape_indices_rank_2() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_gather_shape_shape_2")
.attr_int("axis", 0)
.input_shape("data", 4) .add_input("indices", ArgType::Shape(2)) .output_shape("output", 1) .build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Shape(rank) => {
assert_eq!(*rank, 2, "Expected Shape(2) output for Shape(2) indices");
}
other => panic!("Expected Shape(2) output, got {:?}", other),
}
}
#[test]
fn test_gather_update_outputs_shape_with_shape_indices_rank_3() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_gather_shape_shape_3")
.attr_int("axis", 0)
.input_shape("data", 5) .add_input("indices", ArgType::Shape(3)) .output_shape("output", 1) .build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Shape(rank) => {
assert_eq!(*rank, 3, "Expected Shape(3) output for Shape(3) indices");
}
other => panic!("Expected Shape(3) output, got {:?}", other),
}
}
#[test]
fn test_gather_update_outputs_shape_with_tensor_indices() {
let mut node = NodeBuilder::new(NodeType::Gather, "test_gather_shape_tensor")
.attr_int("axis", 0)
.input_shape("data", 4) .input_tensor_i64("indices", 1, None) .output_shape("output", 1) .build();
gather_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Shape(rank) => {
assert_eq!(*rank, 1, "Expected Shape(1) output for 1D tensor indices");
}
other => panic!("Expected Shape(1) output, got {:?}", other),
}
}
#[test]
fn test_gather_config_with_shape_indices() {
let node = NodeBuilder::new(NodeType::Gather, "test_gather_config_shape")
.attr_int("axis", 0)
.input_shape("data", 3)
.add_input("indices", ArgType::Shape(2)) .output_shape("output", 2)
.build();
let config = gather_config(&node);
assert_eq!(config.axis, 0);
match config.indices {
GatherInput::Runtime(arg) => {
assert_eq!(arg.name, "indices");
match arg.ty {
ArgType::Shape(rank) => assert_eq!(rank, 2),
_ => panic!("Expected Shape(2) indices"),
}
}
_ => panic!("Expected runtime Shape indices"),
}
}
}