use core::cmp::max;
use core::panic;
use log::debug;
use protobuf::Enum;
use crate::{
ir::{ArgType, AttributeValue, Data, ElementType, Node, NodeType, TensorType},
protos::tensor_proto::DataType,
util::{flatten_config, shape_config},
};
pub fn dim_inference(node: &mut Node) {
match node.node_type {
NodeType::Add => same_as_input_broadcast(node),
NodeType::ArgMax => argmax_update_outputs(node),
NodeType::AveragePool1d => same_as_input(node),
NodeType::AveragePool2d => same_as_input(node),
NodeType::BatchNormalization => same_as_input(node),
NodeType::Cast => cast_update_outputs(node),
NodeType::Clip => same_as_input(node),
NodeType::Concat => concat_update_outputs(node),
NodeType::Constant => constant_update_outputs(node),
NodeType::ConstantOfShape => constant_of_shape_update_output(node),
NodeType::Conv1d => conv1d_update_outputs(node),
NodeType::Conv2d => conv2d_update_outputs(node),
NodeType::Cos => same_as_input(node),
NodeType::Div => same_as_input_broadcast(node),
NodeType::Dropout => same_as_input(node),
NodeType::Equal => elementwise_comparison_outputs(node),
NodeType::Erf => same_as_input(node),
NodeType::Exp => same_as_input(node),
NodeType::Expand => expand_update_outputs(node),
NodeType::Flatten => flatten_update_outputs(node),
NodeType::Gelu => same_as_input(node),
NodeType::Gather => gather_update_outputs(node),
NodeType::GatherElements => same_as_input(node),
NodeType::Greater => elementwise_comparison_outputs(node),
NodeType::GreaterOrEqual => elementwise_comparison_outputs(node),
NodeType::HardSigmoid => same_as_input(node),
NodeType::GlobalAveragePool => same_as_input(node),
NodeType::ConvTranspose1d => conv_transpose1d_update_outputs(node),
NodeType::ConvTranspose2d => conv_transpose2d_update_outputs(node),
NodeType::LayerNormalization => same_as_input(node),
NodeType::LeakyRelu => same_as_input(node),
NodeType::Less => elementwise_comparison_outputs(node),
NodeType::LessOrEqual => elementwise_comparison_outputs(node),
NodeType::Linear => linear_update_outputs(node),
NodeType::Log => same_as_input(node),
NodeType::LogSoftmax => same_as_input(node),
NodeType::MatMul => matmul_update_outputs(node),
NodeType::Max => same_as_input_broadcast(node),
NodeType::MaxPool1d => same_as_input(node),
NodeType::MaxPool2d => same_as_input(node),
NodeType::Min => same_as_input_broadcast(node),
NodeType::Mul => same_as_input(node),
NodeType::Neg => same_as_input(node),
NodeType::Not => same_as_input(node),
NodeType::Pad => same_as_input(node),
NodeType::PRelu => same_as_input_broadcast(node),
NodeType::Pow => same_as_input_broadcast(node),
NodeType::RandomNormal => random_update_output(node),
NodeType::RandomUniform => random_update_output(node),
NodeType::Range => range_update_outputs(node),
NodeType::Reciprocal => same_as_input(node),
NodeType::ReduceMax => reduce_max_update_outputs(node),
NodeType::ReduceMin => reduce_min_update_outputs(node),
NodeType::ReduceMean => reduce_mean_update_outputs(node),
NodeType::ReduceProd => reduce_prod_update_outputs(node),
NodeType::ReduceSum => reduce_sum_update_outputs(node),
NodeType::Relu => same_as_input(node),
NodeType::Reshape => reshape_update_outputs(node),
NodeType::Resize => same_as_input(node),
NodeType::Shape => shape_update_outputs(node),
NodeType::Sigmoid => same_as_input(node),
NodeType::Sign => same_as_input(node),
NodeType::Sin => same_as_input(node),
NodeType::Slice => same_as_input(node),
NodeType::Softmax => same_as_input(node),
NodeType::Squeeze => squeeze_update_output(node),
NodeType::Sqrt => same_as_input(node),
NodeType::Sub => same_as_input_broadcast(node),
NodeType::Sum => same_as_input_broadcast(node),
NodeType::Tanh => same_as_input(node),
NodeType::Transpose => same_as_input(node),
NodeType::Trilu => same_as_input(node),
NodeType::Unsqueeze => unsqueeze_update_output(node),
NodeType::Where => where_update_outputs(node),
_ => temporary_pass_through_stub(node),
}
}
fn constant_update_outputs(node: &mut Node) {
let keys = [
"value",
"value_float",
"value_floats",
"value_int",
"value_ints",
"value_string",
"value_strings",
"sparse_value",
];
let matched_value = keys.iter().find_map(|&key| node.attrs.get(key).cloned());
node.outputs[0].ty = match matched_value {
Some(value) => match &value {
AttributeValue::Tensor(tensor) if tensor.dim == 0 => {
ArgType::Scalar(tensor.elem_type.clone())
}
AttributeValue::Tensor(tensor) => ArgType::Tensor(TensorType {
elem_type: tensor.elem_type.clone(),
dim: tensor.dim,
shape: tensor.shape.clone(),
}),
AttributeValue::Float32(_) => ArgType::Scalar(ElementType::Float32),
AttributeValue::Float32s(value) => ArgType::Tensor(TensorType {
elem_type: ElementType::Float32,
dim: 1,
shape: Some(vec![value.len()]),
}),
AttributeValue::Int64(_) => ArgType::Scalar(ElementType::Int64),
AttributeValue::Int64s(value) => ArgType::Tensor(TensorType {
elem_type: ElementType::Int64,
dim: 1,
shape: Some(vec![value.len()]),
}),
ty => panic!("Constant value of {:?} is not supported", ty),
},
None => panic!("Constant node must have a value attribute"),
};
}
fn constant_of_shape_update_output(node: &mut Node) {
let value_type = node
.attrs
.get("value")
.map(|v| v.clone().into_tensor().elem_type)
.unwrap_or(ElementType::Float32);
let dim = match &node.inputs[0].ty {
ArgType::Shape(dim) => *dim,
ArgType::Tensor(tensor_type) => tensor_type
.shape
.as_ref()
.and_then(|shape| shape.first())
.copied()
.expect("ConstantOfShape node must have a Tensor with a non-empty shape"),
_ => panic!("ConstantOfShape node must have a Tensor or Shape type input"),
};
node.inputs[0].ty = ArgType::Shape(dim);
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: value_type,
dim,
shape: None,
});
}
fn random_update_output(node: &mut Node) {
let dtype = node
.attrs
.get("dtype")
.map(|val| DataType::from_i32(val.clone().into_i32()).unwrap())
.unwrap_or(DataType::FLOAT);
let mut shape = node
.attrs
.get("shape")
.expect("required shape attribute missing")
.clone()
.into_i64s();
let elem_type = match dtype {
DataType::FLOAT => ElementType::Float32,
DataType::DOUBLE => ElementType::Float64,
_ => panic!("tensor with type {dtype:?} not supported for random output"),
};
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type,
dim: shape.len(),
shape: Some(
shape
.drain(..)
.map(usize::try_from)
.collect::<Result<Vec<usize>, _>>()
.unwrap(),
),
})
}
fn linear_update_outputs(node: &mut Node) {
let node_input = &node.inputs[0];
let weight = &node.inputs[1];
if let ArgType::Tensor(tensor) = node_input.clone().ty {
let mut tensor = tensor.clone();
if let Some(mut shape) = tensor.shape.clone() {
if let ArgType::Tensor(weight_tensor) = weight.clone().ty {
let last = shape.last_mut().unwrap();
*last = *weight_tensor.shape.unwrap().first().unwrap();
} else {
panic!("Weight must be a tensor");
}
tensor.shape = Some(shape);
}
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
panic!("Only tensor input is valid");
}
}
fn cast_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("Cast: multiple inputs are not supported");
}
let input = &mut node.inputs[0];
let output = &mut node.outputs[0];
let elem_type = match node.attrs.get("to") {
Some(value) => match &value {
AttributeValue::Int64(type_id) => match DataType::from_i32(*type_id as i32).unwrap() {
DataType::FLOAT => ElementType::Float32,
DataType::INT32 => ElementType::Int32,
DataType::INT64 => ElementType::Int64,
DataType::DOUBLE => ElementType::Float64,
DataType::BOOL => ElementType::Bool,
_ => panic!("Cast: unsupported type"),
},
_ => panic!("'to' attribute must be an Int64"),
},
None => panic!("Constant node must have a value attribute"),
};
match input.ty.clone() {
ArgType::Tensor(tensor) => {
if tensor.dim == 0 {
output.ty = ArgType::Scalar(elem_type);
input.ty = ArgType::Scalar(tensor.elem_type);
} else {
output.ty = ArgType::Tensor(TensorType {
elem_type,
dim: tensor.dim,
shape: tensor.shape.clone(),
});
}
}
ArgType::Scalar(_scalar) => {
output.ty = ArgType::Scalar(elem_type);
}
_ => panic!("Cast: only scalar and tensor inputs are valid"),
}
log::debug!(
"Cast: input type: {:?}, output type: {:?}",
input.ty,
output.ty
);
}
fn concat_update_outputs(node: &mut Node) {
let tensor = node
.inputs
.iter()
.find_map(|input| match &input.ty {
ArgType::Tensor(tensor) => Some(tensor),
_ => None,
})
.unwrap();
node.outputs[0].ty = ArgType::Tensor(tensor.clone());
}
fn reshape_update_outputs(node: &mut Node) {
let shape = if node.inputs.len() == 2 {
match &node.inputs[1].value {
Some(value) => match value {
Data::Int64s(shape) => Some(shape.clone()),
_ => panic!("Reshape: invalid input types"),
},
None => None,
}
} else {
node.attrs.get("shape").cloned().map(|v| v.into_i64s())
};
let output = match &node.outputs[0].ty {
ArgType::Tensor(tensor) => tensor.clone(),
_ => panic!("Reshape: invalid output types"),
};
if let Some(shape) = shape {
node.outputs[0].ty = ArgType::Tensor(TensorType {
dim: shape.len(),
shape: None, ..output
});
}
}
fn reduce_mean_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("Mean: multiple inputs are not supported");
}
let node_input = &mut node.inputs[0];
let tensor = match node_input.clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
let dim_only = match node.attrs.get("axes") {
Some(value) => match &value {
AttributeValue::Int64(_) => true,
AttributeValue::Int64s(ints) => ints.len() == 1,
_ => false,
},
None => false,
};
if dim_only {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType { dim: 1, ..tensor });
}
}
fn argmax_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("Mean: multiple inputs are not supported");
}
let node_input = &mut node.inputs[0];
let tensor = match node_input.clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
node.outputs[0].ty = ArgType::Tensor(TensorType {
dim: tensor.dim,
shape: tensor.shape.clone(),
elem_type: ElementType::Int64,
});
}
fn squeeze_update_output(node: &mut Node) {
let axes = if node.inputs.len() == 2 {
match &node.inputs[1].value {
Some(value) => match value {
Data::Int64s(axes) => Some(axes.clone()),
_ => panic!("Squeeze: invalid input types"),
},
None => None,
}
} else {
node.attrs.get("axes").cloned().map(|v| v.into_i64s())
};
if axes.is_none() {
panic!("Squeeze must specify an axis");
}
let input_dim = match &node.inputs[0].ty {
ArgType::Tensor(tensor) => tensor.dim,
_ => panic!("Squeeze: invalid input type"),
};
let new_dim = input_dim - axes.unwrap().len();
let output_elem = match &node.outputs[0].ty {
ArgType::Tensor(tensor) => tensor.elem_type.clone(),
_ => panic!("Squeeze: invalid output type"),
};
node.outputs[0].ty = ArgType::Tensor(TensorType {
dim: new_dim,
shape: None, elem_type: output_elem,
});
}
fn same_as_input_broadcast(node: &mut Node) {
if node.inputs.iter().all(|input| input.ty.is_scalar()) {
node.outputs[0].ty = node.inputs[0].ty.clone();
} else {
node.outputs[0].ty = node
.inputs
.iter()
.find(|input| input.ty.is_tensor())
.map(|input| input.ty.clone())
.unwrap_or_else(|| ArgType::Shape(0));
set_broadcasting_output_shape(node);
}
}
fn unsqueeze_update_output(node: &mut Node) {
let axes = if node.inputs.len() == 2 {
match &node.inputs[1].value {
Some(value) => match value {
Data::Int64s(axes) => Some(axes.clone()),
_ => panic!("Unsqueeze: invalid input types"),
},
None => None,
}
} else {
node.attrs.get("axes").cloned().map(|v| v.into_i64s())
};
if axes.is_none() {
return;
}
let input_dim = match &node.inputs[0].ty {
ArgType::Tensor(tensor) => tensor.dim,
ArgType::Scalar(_) => 0, _ => panic!("Unsqueeze: invalid input type"),
};
let output_elem = match &node.outputs[0].ty {
ArgType::Tensor(tensor) => tensor.elem_type.clone(),
ArgType::Scalar(elem_type) => elem_type.clone(),
_ => panic!("Unsqueeze: invalid output type"),
};
if let Some(axes) = axes {
node.outputs[0].ty = ArgType::Tensor(TensorType {
dim: input_dim + axes.len(),
shape: None, elem_type: output_elem,
});
}
}
fn same_as_input(node: &mut Node) {
node.outputs[0].ty = node.inputs[0].ty.clone();
}
fn temporary_pass_through_stub(node: &mut Node) {
log::warn!("Must implement dimension inference for {:?}", node);
log::warn!("Temporarily setting the output type to the input type.");
node.outputs[0].ty = node.inputs[0].ty.clone();
}
fn elementwise_comparison_outputs(node: &mut Node) {
let input1_type = &node.inputs[0].ty;
let input2_type = &node.inputs[1].ty;
match (input1_type, input2_type) {
(ArgType::Tensor(tensor), _) | (_, ArgType::Tensor(tensor)) => {
assert_ne!(
tensor.dim, 0,
"Got a rank 0 Tensor, that should have been a Scalar!"
);
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Bool,
..tensor.clone()
});
set_broadcasting_output_shape(node);
}
(ArgType::Scalar(_), ArgType::Scalar(_)) => {
node.outputs[0].ty = ArgType::Scalar(ElementType::Bool);
}
_ => {
panic!("Invalid input types for comparison op: {input1_type:?}, {input2_type:?}")
}
}
}
fn expand_update_outputs(node: &mut Node) {
let shape = if node.inputs.len() == 2 {
match &node.inputs[1].value {
Some(value) => match value {
Data::Int64s(shape) => Some(shape.clone()),
_ => panic!("Expand: invalid input types"),
},
None => None,
}
} else {
panic!("Expand: invalid number of inputs");
};
let output = match &node.outputs[0].ty {
ArgType::Tensor(tensor) => tensor.clone(),
_ => panic!("Expand: invalid output types"),
};
if let Some(shape) = shape {
node.outputs[0].ty = ArgType::Tensor(TensorType {
dim: shape.len(),
shape: None, ..output
});
}
}
fn shape_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("Shape: multiple inputs are not supported: {:?}", node);
}
let (start, end) = shape_config(node);
let dim = end - start;
node.outputs[0].ty = ArgType::Shape(dim);
}
fn flatten_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("Flatten: multiple inputs are not supported");
}
let tensor = node
.inputs
.iter()
.find_map(|input| match &input.ty {
ArgType::Tensor(tensor) => Some(tensor),
_ => None,
})
.unwrap();
let input_dim = tensor.dim;
let (start_dim, end_dim) = flatten_config(node);
let collapsed_dims = end_dim - start_dim;
let output_dim = input_dim - collapsed_dims;
node.outputs[0].ty = ArgType::Tensor(TensorType {
dim: output_dim,
..tensor.clone()
});
}
fn conv1d_update_outputs(node: &mut Node) {
if let ArgType::Tensor(tensor) = node.inputs[0].clone().ty {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
panic!("Only tensor input is valid");
}
}
fn conv2d_update_outputs(node: &mut Node) {
if let ArgType::Tensor(tensor) = node.inputs[0].clone().ty {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
panic!("Only tensor input is valid");
}
}
fn conv_transpose1d_update_outputs(node: &mut Node) {
if let ArgType::Tensor(tensor) = node.inputs[0].clone().ty {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
panic!("Only tensor input is valid");
}
}
fn conv_transpose2d_update_outputs(node: &mut Node) {
if let ArgType::Tensor(tensor) = node.inputs[0].clone().ty {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
panic!("Only tensor input is valid");
}
}
fn matmul_update_outputs(node: &mut Node) {
match (node.inputs[0].ty.clone(), node.inputs[1].ty.clone()) {
(ArgType::Tensor(a), ArgType::Tensor(b)) => {
let mut out_dim = max(a.dim, b.dim);
if (a.dim >= 2 && b.dim == 1) || (a.dim == 1 && b.dim >= 2) {
out_dim -= 1;
}
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: a.elem_type.clone(),
dim: out_dim,
shape: a.shape.clone(),
});
}
_ => panic!("Only tensor input is valid"),
}
}
fn range_update_outputs(node: &mut Node) {
if node.inputs.len() != 3 {
panic!("Range: expected 3 inputs, found {}", node.inputs.len());
}
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Int64,
dim: 1,
shape: None,
});
}
fn reduce_max_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("ReduceMax: multiple inputs are not supported");
}
let node_input = &mut node.inputs[0];
let tensor = match node_input.clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
let dim_only = match node.attrs.get("axes") {
Some(value) => match &value {
AttributeValue::Int64(_) => true,
AttributeValue::Int64s(ints) => ints.len() == 1,
_ => false,
},
None => false,
};
if dim_only {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType { dim: 1, ..tensor });
}
}
fn reduce_min_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("ReduceMin: multiple inputs are not supported");
}
let node_input = &mut node.inputs[0];
let tensor = match node_input.clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
let dim_only = match node.attrs.get("axes") {
Some(value) => match &value {
AttributeValue::Int64(_) => true,
AttributeValue::Int64s(ints) => ints.len() == 1,
_ => false,
},
None => false,
};
if dim_only {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType { dim: 1, ..tensor });
}
}
fn reduce_prod_update_outputs(node: &mut Node) {
if node.inputs.len() != 1 {
panic!("ReduceProd: multiple inputs are not supported");
}
let node_input = &mut node.inputs[0];
let tensor = match node_input.clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
let dim_only = match node.attrs.get("axes") {
Some(value) => match &value {
AttributeValue::Int64(_) => true,
AttributeValue::Int64s(ints) => ints.len() == 1,
_ => false,
},
None => false,
};
if dim_only {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType { dim: 1, ..tensor });
}
}
fn reduce_sum_update_outputs(node: &mut Node) {
let node_input = &mut node.inputs[0];
let tensor = match node_input.clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
let dim_only = match node.attrs.get("axes") {
Some(value) => match &value {
AttributeValue::Int64(_) => true,
AttributeValue::Int64s(ints) => ints.len() == 1,
_ => false,
},
None => false,
};
let dim_only = match node.inputs.get(1).and_then(|arg| arg.value.as_ref()) {
Some(value) => match &value {
Data::Int64(_) => true,
Data::Int64s(ints) => ints.len() == 1,
_ => false,
},
None => dim_only,
};
if dim_only {
node.outputs[0].ty = ArgType::Tensor(tensor);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType { dim: 1, ..tensor });
}
}
fn where_update_outputs(node: &mut Node) {
let condition = &node.inputs[0].ty;
let x = &node.inputs[1].ty;
let y = &node.inputs[2].ty;
let elem_type = x.elem_type().clone();
assert_eq!(
*condition.elem_type(),
ElementType::Bool,
"Where condition must be boolean!"
);
assert_eq!(
elem_type,
*y.elem_type(),
"Where x and y have different element types!"
);
let output_rank = max(condition.rank(), max(x.rank(), y.rank()));
if output_rank == 0 {
node.outputs[0].ty = ArgType::Scalar(elem_type);
} else {
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type,
dim: output_rank,
..Default::default()
});
set_broadcasting_output_shape(node);
}
}
fn gather_update_outputs(node: &mut Node) {
if node.inputs.len() != 2 {
panic!("Gather requires two inputs: data and indices");
}
let indices_dim = match &node.inputs[1].ty {
ArgType::Tensor(tensor) => tensor.dim,
ArgType::Scalar(_) => 0,
_ => panic!("Only tensor indices is valid, got {:?}", node.inputs[1].ty),
};
match &node.inputs[0].ty {
ArgType::Tensor(input_tensor) => {
let output_rank = indices_dim + input_tensor.dim - 1;
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: input_tensor.elem_type.clone(),
dim: output_rank,
shape: None,
});
}
ArgType::Shape(_dim) => {
let shape_dim = 1;
let output_rank = indices_dim + shape_dim - 1;
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Int64,
dim: output_rank,
shape: None,
})
}
ty => panic!("Only tensor/shape input is valid but received: {:?}", ty),
}
}
fn set_broadcasting_output_shape(node: &mut Node) {
let mut reverse_out_shape: Vec<usize> = vec![1];
for (idx, input_type) in node.inputs.iter().enumerate() {
match &input_type.ty {
ArgType::Tensor(t) => {
if let Some(shape) = &t.shape {
for (rev_idx, dimension) in shape.iter().rev().enumerate() {
if let Some(current_out_dim) = reverse_out_shape.get_mut(rev_idx) {
if *dimension == 1 {
continue;
}
if current_out_dim != dimension && *current_out_dim != 1 {
panic!("Invalid shape for broadcasting - the dimension from the {rev_idx}. to last position has conflicting values {current_out_dim} and {dimension} from different inputs");
}
*current_out_dim = *dimension;
} else {
reverse_out_shape.push(*dimension);
}
}
} else {
debug!("Input {idx} has no known shape, cannot predict broadcast result shape");
return;
}
}
ArgType::Scalar(_) => {
}
ArgType::Shape(s) => {
let current_out_dim = &mut reverse_out_shape[0];
if *current_out_dim != 1 && *current_out_dim != *s {
panic!("Invalid shape for broadcasting - the last position has conflicting values {current_out_dim} and {} from different inputs", s);
}
*current_out_dim = *s;
}
}
}
let mut out_shape = reverse_out_shape;
out_shape.reverse();
match &mut node.outputs[0].ty {
ArgType::Tensor(t) => {
t.dim = out_shape.len();
t.shape = Some(out_shape);
}
ArgType::Scalar(_) => {
if out_shape.len() > 1 || out_shape[0] > 1 {
panic!("Output is a Scalar, but broadcasting results in tensor shape {out_shape:?}")
}
}
ArgType::Shape(s) => {
if out_shape.len() > 1 {
panic!("Output is a Shape, but broadcasting results in higher-rank tensor shape {out_shape:?}")
}
*s = out_shape[0];
}
}
}