use burn::nn::{
conv::Conv1dConfig,
conv::{Conv2dConfig, ConvTranspose2dConfig},
pool::{AvgPool2dConfig, MaxPool2dConfig},
BatchNormConfig, DropoutConfig, LinearConfig, PaddingConfig1d, PaddingConfig2d,
};
use super::ir::{ArgType, AttributeValue, Data, Node};
pub fn conv1d_config(curr: &Node) -> Conv1dConfig {
let mut kernel_shape = Vec::new(); let mut strides = vec![1];
let mut pads = vec![0, 0];
let mut dilations = vec![1];
let mut group: i64 = 1;
let weight = if let ArgType::Tensor(ref weight) = curr.inputs[1].ty {
weight
} else {
panic!("Conv1d: weight tensor must be present");
};
let bias = curr.inputs.len() == 3;
let shape = weight.shape.clone().unwrap();
let channels_in = shape[1];
let channels_out = shape[0];
for (key, value) in curr.attrs.iter() {
match key.as_str() {
"kernel_shape" => kernel_shape = value.clone().into_i64s(),
"strides" => strides = value.clone().into_i64s(),
"pads" => pads = value.clone().into_i64s(),
"dilations" => dilations = value.clone().into_i64s(),
"group" => group = value.clone().into_i64(),
_ => {}
}
}
let padding = padding_config_1d(&pads);
Conv1dConfig::new(channels_in, channels_out, kernel_shape[0] as usize)
.with_stride(strides[0] as usize)
.with_dilation(dilations[0] as usize)
.with_groups(group as usize)
.with_bias(bias)
.with_padding(padding)
}
pub fn conv2d_config(curr: &Node) -> Conv2dConfig {
let mut kernel_shape = Vec::new(); let mut strides = vec![1, 1];
let mut pads = vec![0, 0, 0, 0];
let mut dilations = vec![1, 1];
let mut group: i64 = 1;
let weight = if let ArgType::Tensor(ref weight) = curr.inputs[1].ty {
weight
} else {
panic!("Conv1d: weight tensor must be present");
};
let bias = curr.inputs.len() == 3;
let shape = weight.shape.clone().unwrap();
let channels: [usize; 2] = [shape[1], shape[0]];
for (key, value) in curr.attrs.iter() {
match key.as_str() {
"kernel_shape" => kernel_shape = value.clone().into_i64s(),
"strides" => strides = value.clone().into_i64s(),
"pads" => pads = value.clone().into_i64s(),
"dilations" => dilations = value.clone().into_i64s(),
"group" => group = value.clone().into_i64(),
_ => {}
}
}
let padding = padding_config(&pads);
Conv2dConfig::new(
channels,
[kernel_shape[0] as usize, kernel_shape[1] as usize],
)
.with_stride([strides[0] as usize, strides[1] as usize])
.with_dilation([dilations[0] as usize, dilations[1] as usize])
.with_groups(group as usize)
.with_bias(bias)
.with_padding(padding)
}
pub fn max_pool2d_config(curr: &Node) -> MaxPool2dConfig {
let mut kernel_shape = Vec::new();
let mut strides = vec![1, 1];
let mut pads = vec![0, 0, 0, 0];
let mut dilations = vec![1, 1];
for (key, value) in curr.attrs.iter() {
match key.as_str() {
"kernel_shape" => kernel_shape = value.clone().into_i64s(),
"strides" => strides = value.clone().into_i64s(),
"pads" => pads = value.clone().into_i64s(),
"dilations" => dilations = value.clone().into_i64s(),
_ => {}
}
}
let padding = padding_config(&pads);
MaxPool2dConfig::new([kernel_shape[0] as usize, kernel_shape[1] as usize])
.with_strides([strides[0] as usize, strides[1] as usize])
.with_padding(padding)
.with_dilation([dilations[0] as usize, dilations[1] as usize])
}
pub fn conv_transpose2d_config(curr: &Node) -> ConvTranspose2dConfig {
let mut attrs = curr.attrs.clone();
let kernel_shape = attrs
.remove("kernel_shape")
.map(AttributeValue::into_i64s)
.unwrap_or_default();
let stride = attrs
.remove("strides")
.map(AttributeValue::into_i64s)
.unwrap_or_else(|| vec![1, 1]);
let pads = attrs
.remove("pads")
.map(AttributeValue::into_i64s)
.unwrap_or_else(|| vec![0, 0]);
let dilations = attrs
.remove("dilations")
.map(AttributeValue::into_i64s)
.unwrap_or_else(|| vec![1, 1]);
let group = attrs
.remove("group")
.map(AttributeValue::into_i64)
.unwrap_or(1);
if !attrs.is_empty() {
panic!("Not all attributes are used: {attrs:?}");
}
let weight = if let ArgType::Tensor(ref weight) = curr.inputs[1].ty {
weight
} else {
panic!("ConvTranspose2d: weight tensor must be present");
};
let bias = curr.inputs.len() == 3;
let shape = weight.shape.clone().unwrap();
let channels: [usize; 2] = [shape[1], shape[0]];
ConvTranspose2dConfig::new(
channels,
[kernel_shape[0] as usize, kernel_shape[1] as usize],
)
.with_stride([stride[0] as usize, stride[1] as usize])
.with_padding([pads[0] as usize, pads[1] as usize])
.with_dilation([dilations[0] as usize, dilations[1] as usize])
.with_groups(group as usize)
.with_bias(bias)
}
pub fn avg_pool2d_config(curr: &Node) -> AvgPool2dConfig {
let mut kernel_shape = Vec::new();
let mut strides = vec![1, 1];
let mut pads = vec![0, 0, 0, 0];
let mut count_include_pad: i64 = 0;
let mut ceil_mode: i64 = 0;
for (key, value) in curr.attrs.iter() {
match key.as_str() {
"kernel_shape" => kernel_shape = value.clone().into_i64s(),
"strides" => strides = value.clone().into_i64s(),
"pads" => pads = value.clone().into_i64s(),
"count_include_pad" => count_include_pad = value.clone().into_i64(),
"ceil_mode" => ceil_mode = value.clone().into_i64(),
_ => {}
}
}
if ceil_mode == 1 {
panic!("ceil_mode is not supported");
}
let padding = padding_config(&pads);
AvgPool2dConfig::new([kernel_shape[0] as usize, kernel_shape[1] as usize])
.with_strides([strides[0] as usize, strides[1] as usize])
.with_padding(padding)
.with_count_include_pad(count_include_pad == 1)
}
pub fn flatten_config(curr: &Node) -> (usize, usize) {
let mut start_dim: i64 = 1;
if curr.inputs.len() != 1 {
panic!(
"Flatten: multiple inputs are not supported (got {:?})",
curr.inputs.len()
);
}
let tensor = match curr.inputs.first().unwrap().clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
if tensor.dim < 2 {
panic!(
"Flatten: input tensor must have at least 2 dimensions (got {:?})",
tensor.dim
);
}
let end_dim = tensor.dim - 1;
for (key, value) in curr.attrs.iter() {
match key.as_str() {
"axis" => start_dim = value.clone().into_i64(),
_ => {}
}
}
if start_dim < 0 {
start_dim += tensor.dim as i64;
}
(start_dim as usize, end_dim)
}
pub fn gather_config(curr: &Node) -> usize {
let mut dim: i64 = 0;
if curr.inputs.len() != 2 {
panic!("Gather: index tensor must be present");
}
let tensor = match curr.inputs.first().unwrap().clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
for (key, value) in curr.attrs.iter() {
match key.as_str() {
"axis" => dim = value.clone().into_i64(),
_ => {}
}
}
if dim < 0 {
dim += tensor.dim as i64;
}
dim as usize
}
pub fn linear_config(node: &Node) -> LinearConfig {
if node.inputs.len() < 2 {
panic!("Linear: missing weight tensor");
}
let weight = if let ArgType::Tensor(ref weight) = node.inputs[1].ty {
weight
} else {
panic!("Linear: weight tensor must be present");
};
if weight.dim < 2 {
panic!(
"Linear: weight tensor must have at least 2 dimensions (got {:?})",
weight.dim
);
}
let shape = weight.shape.clone().unwrap();
let (in_size, out_size) = (shape[0], shape[1]);
let bias = node.inputs.len() == 3 && node.inputs[2].value.is_some();
LinearConfig::new(in_size, out_size).with_bias(bias)
}
pub fn dropout_config(node: &Node) -> DropoutConfig {
if node.attrs.contains_key("ratio") {
let prob = node.attrs.get("ratio").unwrap().clone().into_f32();
return DropoutConfig::new(prob as f64);
}
if node.inputs.len() < 2 {
panic!("Dropout configuration must have at least 2 inputs");
}
let ratio = node.inputs[1]
.value
.clone()
.expect("Dropout ratio must be passed in the second input")
.into_scalar();
let prob = match ratio {
Data::Float16(ratio) => f64::from(f32::from(ratio)),
Data::Float32(ratio) => ratio as f64,
Data::Float64(ratio) => ratio,
_ => panic!("Dropout ratio must be a float"),
};
DropoutConfig::new(prob)
}
pub fn log_softmax_config(node: &Node) -> usize {
let mut axis: i64 = -1;
if node.inputs.len() != 1 {
panic!(
"LogSoftmax: multiple inputs are not supported (got {:?})",
node.inputs.len()
);
}
let tensor = match node.inputs.first().unwrap().clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
for (key, value) in node.attrs.iter() {
match key.as_str() {
"axis" => axis = value.clone().into_i64(),
_ => {}
}
}
if axis < 0 {
axis += tensor.dim as i64;
}
axis as usize
}
pub fn softmax_config(node: &Node) -> usize {
let mut axis: i64 = -1;
if node.inputs.len() != 1 {
panic!(
"Softmax: multiple inputs are not supported (got {:?})",
node.inputs.len()
);
}
let tensor = match node.inputs.first().unwrap().clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
for (key, value) in node.attrs.iter() {
match key.as_str() {
"axis" => axis = value.clone().into_i64(),
_ => {}
}
}
if axis < 0 {
axis += tensor.dim as i64;
}
axis as usize
}
pub fn concat_config(node: &Node) -> usize {
let mut axis: i64 = 1;
let tensor = match node.inputs.first().unwrap().clone().ty {
ArgType::Tensor(tensor) => tensor,
_ => panic!("Only tensor input is valid"),
};
for (key, value) in node.attrs.iter() {
match key.as_str() {
"axis" => axis = value.clone().into_i64(),
_ => {}
}
}
if axis < 0 {
axis += tensor.dim as i64;
}
axis as usize
}
pub fn batch_norm_config(node: &Node) -> BatchNormConfig {
let tensor_type = if let ArgType::Tensor(ref tensor_type) = node.inputs[1].ty {
tensor_type
} else {
panic!("BatchNorm: weight tensor must be present");
};
let num_features: usize = tensor_type.shape.clone().unwrap()[0];
let mut epsilon = 0f32;
let mut momentum = 0f32;
for (key, value) in node.attrs.iter() {
match key.as_str() {
"momentum" => momentum = value.clone().into_f32(),
"epsilon" => epsilon = value.clone().into_f32(),
_ => {}
}
}
BatchNormConfig::new(num_features)
.with_epsilon(epsilon as f64)
.with_momentum(momentum as f64)
}
fn padding_config(pads: &[i64]) -> PaddingConfig2d {
let [left, top, right, bottom] = [pads[0], pads[1], pads[2], pads[3]];
if left < 0 || top < 0 || right < 0 || bottom < 0 {
panic!("Negative pad values are not supported");
} else if (left != right) || (top != bottom) {
panic!("Asymmetric padding is not supported");
} else if left == top && top == right && right == bottom && bottom == 0 {
PaddingConfig2d::Valid
} else if left == right && top == bottom {
PaddingConfig2d::Explicit(left as usize, top as usize)
} else {
panic!("Padding configuration ({:?}) not supported", pads);
}
}
pub fn reshape_config(node: &Node) -> Vec<i64> {
let mut allowzero = 0;
for (key, value) in node.attrs.iter() {
match key.as_str() {
"allowzero" => allowzero = value.clone().into_i64(),
_ => {}
}
}
if allowzero != 0 {
panic!("Zero shape size is not supported");
}
if node.inputs.len() != 2 || node.inputs[1].value.is_none() {
panic!("Reshape: shape tensor must be present");
}
let input_value = &node.inputs[1].value;
match &node.inputs[1].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.dim, 1, "Reshape: shape tensor must be 1D");
if let Some(Data::Int64s(shape)) = input_value.as_ref() {
shape.clone()
} else {
panic!("Tensor data type must be int64")
}
}
_ => panic!("Only tensor input is valid for shape"),
}
}
pub fn clip_config(node: &Node) -> (Option<f64>, Option<f64>) {
let mut min_result: Option<f64> = None;
let mut max_result: Option<f64> = None;
for (key, value) in node.attrs.iter() {
match key.as_str() {
"min" => {
let min = value.clone().into_f32() as f64;
min_result = Some(min);
}
"max" => {
let max = value.clone().into_f32();
max_result = Some(max as f64);
}
_ => {}
}
}
if min_result.is_none() && max_result.is_none() {
let min = &node.inputs[1].value;
let max = &node.inputs[2].value;
if min_result.is_none() && min.is_some() {
let min = min.clone().unwrap().into_scalar();
min_result = match min {
Data::Float16(min) => Some(f32::from(min) as f64),
Data::Float32(min) => Some(min as f64),
Data::Float64(min) => Some(min),
_ => panic!("Clip: only float min is supported"),
};
}
if max_result.is_none() && max.is_some() {
let max = max.clone().unwrap().into_scalar();
max_result = match max {
Data::Float16(max) => Some(f32::from(max) as f64),
Data::Float32(max) => Some(max as f64),
Data::Float64(max) => Some(max),
_ => panic!("Clip: only float max is supported"),
};
}
}
if min_result.is_none() && max_result.is_none() {
panic!("Clip: min and max values must be either attributes or inputs");
}
(min_result, max_result)
}
fn padding_config_1d(pads: &[i64]) -> PaddingConfig1d {
let [left, right] = [pads[0], pads[1]];
if left < 0 || right < 0 {
panic!("Negative pad values are not supported");
} else if left != right {
panic!("Asymmetric padding is not supported");
} else if left == right && right == 0 {
PaddingConfig1d::Valid
} else if left == right {
PaddingConfig1d::Explicit(left as usize)
} else {
panic!("Padding configuration ({:?}) not supported", pads);
}
}