use onnx_runtime_ir::{Attribute, DataType};
use crate::context::{InferenceContext, TensorType, ValueType, unify_tensor_type};
use crate::dim_expr::DimExpr;
use crate::error::ShapeInferError;
use crate::handlers::checked_axis;
use crate::registry::InferenceRegistry;
fn dtype_attr(ctx: &InferenceContext) -> Option<DataType> {
let raw = ctx.node.attr("dtype").and_then(Attribute::as_int)?;
i32::try_from(raw).ok().and_then(DataType::from_onnx)
}
fn sequence_empty(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let dtype = dtype_attr(ctx).unwrap_or(DataType::Float32);
let element = ValueType::Tensor(TensorType::dtype_only(dtype));
ctx.set_output_value_type(0, ValueType::sequence(element));
Ok(())
}
fn sequence_construct(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let mut element: Option<TensorType> = None;
let mut shape_known = true;
for i in 0..ctx.num_inputs() {
if !ctx.has_input(i) {
continue;
}
let Some(input) = ctx.input_type(i).cloned() else {
shape_known = false;
continue;
};
element = Some(match element.take() {
None => TensorType::from(input),
Some(acc) => {
unify_tensor_type(ctx.interner_mut(), "SequenceConstruct", acc, input.into())?
}
});
}
let Some(mut element) = element else {
return Ok(());
};
if !shape_known {
element.shape = None;
}
ctx.set_output_value_type(0, ValueType::sequence(ValueType::Tensor(element)));
Ok(())
}
fn sequence_length(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
ctx.set_output(0, DataType::Int64, Vec::new());
Ok(())
}
fn sequence_at(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(element) = ctx
.input_value_type(0)
.and_then(ValueType::as_sequence_element)
else {
return Ok(());
};
if let Some(tensor) = element.as_tensor()
&& let Some(type_info) = tensor.to_type_info()
{
ctx.set_output_type(0, type_info);
}
Ok(())
}
fn sequence_element_tensor(ctx: &InferenceContext, i: usize) -> Option<TensorType> {
ctx.input_value_type(i)?
.as_sequence_element()?
.as_tensor()
.cloned()
}
fn sequence_insert(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let existing = sequence_element_tensor(ctx, 0);
let inserted = ctx.input_type(1).cloned().map(TensorType::from);
let element = match (existing, inserted) {
(Some(acc), Some(ins)) => {
unify_tensor_type(ctx.interner_mut(), "SequenceInsert", acc, ins)?
}
(Some(mut acc), None) => {
acc.shape = None;
acc
}
(None, Some(ins)) => ins,
(None, None) => return Ok(()),
};
ctx.set_output_value_type(0, ValueType::sequence(ValueType::Tensor(element)));
Ok(())
}
fn sequence_erase(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
if let Some(element) = ctx
.input_value_type(0)
.and_then(ValueType::as_sequence_element)
.cloned()
{
ctx.set_output_value_type(0, ValueType::sequence(element));
}
Ok(())
}
fn split_to_sequence(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(input) = ctx.input_type(0).cloned() else {
return Ok(());
};
let rank = input.rank();
if rank == 0 {
return Err(ShapeInferError::InvalidRank {
op: "SplitToSequence".into(),
index: 0,
rank,
detail: "input must have rank at least 1".into(),
});
}
let axis_attr = ctx
.node
.attr("axis")
.and_then(Attribute::as_int)
.unwrap_or(0);
let axis = checked_axis(axis_attr, rank).ok_or_else(|| ShapeInferError::Invalid {
op: "SplitToSequence".into(),
detail: format!("axis {axis_attr} is outside [-{rank}, {rank})"),
})?;
let keepdims = ctx
.node
.attr("keepdims")
.and_then(Attribute::as_int)
.unwrap_or(1)
!= 0;
let mut shape = input.shape.clone();
if ctx.has_input(1) {
shape[axis] = ctx.fresh_dim();
} else if keepdims {
shape[axis] = DimExpr::constant(1);
} else {
shape.remove(axis);
}
let element = TensorType::new(input.dtype, shape);
ctx.set_output_value_type(0, ValueType::sequence(ValueType::Tensor(element)));
Ok(())
}
fn concat_from_sequence(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let axis_attr = ctx
.node
.attr("axis")
.and_then(Attribute::as_int)
.ok_or_else(|| ShapeInferError::Invalid {
op: "ConcatFromSequence".into(),
detail: "requires the mandatory 'axis' attribute".into(),
})?;
let new_axis = ctx
.node
.attr("new_axis")
.and_then(Attribute::as_int)
.unwrap_or(0)
!= 0;
let Some(element) = sequence_element_tensor(ctx, 0) else {
return Ok(());
};
let Some(mut shape) = element.shape else {
return Ok(());
};
let output_rank = shape.len() + usize::from(new_axis);
let axis = checked_axis(axis_attr, output_rank).ok_or_else(|| ShapeInferError::Invalid {
op: "ConcatFromSequence".into(),
detail: format!("axis {axis_attr} is outside [-{output_rank}, {output_rank})"),
})?;
if new_axis {
shape.insert(axis, ctx.fresh_dim());
} else {
shape[axis] = ctx.fresh_dim();
}
ctx.set_output(0, element.dtype, shape);
Ok(())
}
pub fn register(reg: &mut InferenceRegistry) {
reg.register("", "SequenceEmpty", 11, sequence_empty);
reg.register("", "SequenceConstruct", 11, sequence_construct);
reg.register("", "SequenceLength", 11, sequence_length);
reg.register("", "SequenceAt", 11, sequence_at);
reg.register("", "SequenceInsert", 11, sequence_insert);
reg.register("", "SequenceErase", 11, sequence_erase);
reg.register("", "SplitToSequence", 11, split_to_sequence);
reg.register("", "ConcatFromSequence", 11, concat_from_sequence);
}