use crate::quant::quantize_multiplier;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WeightEncoding {
#[default]
SignedInt,
Fp4E2M1,
}
#[derive(Debug, Clone)]
pub enum Layer {
Conv2d {
name: &'static str,
h_in: usize,
w_in: usize,
c_in: usize,
c_out: usize,
kh: usize,
kw: usize,
pad_h: usize,
pad_w: usize,
stride_h: usize,
stride_w: usize,
x_zp: i32,
w_zp: i32,
out_zp: i32,
weight_bits: u8,
weight_encoding: WeightEncoding,
requant: Vec<(i32, i32)>,
weights: Vec<i8>,
bias: Option<Vec<i32>>,
},
Relu {
name: &'static str,
len: usize,
zero_point: i32,
},
MaxPool2d {
name: &'static str,
h_in: usize,
w_in: usize,
c: usize,
kh: usize,
kw: usize,
stride_h: usize,
stride_w: usize,
},
Dense {
name: &'static str,
in_features: usize,
out_features: usize,
x_zp: i32,
w_zp: i32,
out_zp: i32,
weight_bits: u8,
weight_encoding: WeightEncoding,
requant: Vec<(i32, i32)>,
weights: Vec<i8>,
bias: Option<Vec<i32>>,
},
Argmax {
name: &'static str,
len: usize,
},
}
impl Layer {
pub fn name(&self) -> &'static str {
match self {
Layer::Conv2d { name, .. }
| Layer::Relu { name, .. }
| Layer::MaxPool2d { name, .. }
| Layer::Dense { name, .. }
| Layer::Argmax { name, .. } => name,
}
}
pub fn out_len(&self) -> usize {
match self {
Layer::Conv2d {
h_in,
w_in,
c_out,
kh,
kw,
pad_h,
pad_w,
stride_h,
stride_w,
..
} => {
let h_out = (h_in + 2 * pad_h - kh) / stride_h + 1;
let w_out = (w_in + 2 * pad_w - kw) / stride_w + 1;
h_out * w_out * c_out
}
Layer::Relu { len, .. } => *len,
Layer::MaxPool2d {
h_in,
w_in,
c,
kh,
kw,
stride_h,
stride_w,
..
} => {
let h_out = (h_in - kh) / stride_h + 1;
let w_out = (w_in - kw) / stride_w + 1;
h_out * w_out * c
}
Layer::Dense { out_features, .. } => *out_features,
Layer::Argmax { .. } => 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtraOutput {
pub name: String,
pub len: usize,
pub after_layer: usize,
}
#[derive(Debug, Clone)]
pub struct Model {
pub name: String,
pub input_len: usize,
pub layers: Vec<Layer>,
pub extra_outputs: Vec<ExtraOutput>,
}
impl Model {
pub fn scratch_len(&self) -> usize {
let mut m = self.input_len;
for l in &self.layers {
m = m.max(l.out_len());
}
m
}
}
pub fn tinyconv_mnist_from_cortexm() -> Model {
use WeightEncoding::SignedInt;
use rlx_cortexm::model_weights as w;
fn requant_table(mults: &[f32]) -> Vec<(i32, i32)> {
mults.iter().map(|&m| quantize_multiplier(m)).collect()
}
let conv1 = Layer::Conv2d {
name: "conv1",
h_in: 28,
w_in: 28,
c_in: 1,
c_out: 8,
kh: 3,
kw: 3,
pad_h: 0,
pad_w: 0,
stride_h: 1,
stride_w: 1,
x_zp: 0,
w_zp: 0,
out_zp: 0,
weight_bits: w::WEIGHT_BITS,
weight_encoding: SignedInt,
requant: requant_table(w::CONV1_MULT),
weights: w::CONV1_W.to_vec(),
bias: Some(w::CONV1_B.to_vec()),
};
let relu1 = Layer::Relu {
name: "relu1",
len: 26 * 26 * 8,
zero_point: 0,
};
let pool1 = Layer::MaxPool2d {
name: "pool1",
h_in: 26,
w_in: 26,
c: 8,
kh: 2,
kw: 2,
stride_h: 2,
stride_w: 2,
};
let conv2 = Layer::Conv2d {
name: "conv2",
h_in: 13,
w_in: 13,
c_in: 8,
c_out: 16,
kh: 3,
kw: 3,
pad_h: 0,
pad_w: 0,
stride_h: 1,
stride_w: 1,
x_zp: 0,
w_zp: 0,
out_zp: 0,
weight_bits: w::WEIGHT_BITS,
weight_encoding: SignedInt,
requant: requant_table(w::CONV2_MULT),
weights: w::CONV2_W.to_vec(),
bias: Some(w::CONV2_B.to_vec()),
};
let relu2 = Layer::Relu {
name: "relu2",
len: 11 * 11 * 16,
zero_point: 0,
};
let pool2 = Layer::MaxPool2d {
name: "pool2",
h_in: 11,
w_in: 11,
c: 16,
kh: 2,
kw: 2,
stride_h: 2,
stride_w: 2,
};
let fc = Layer::Dense {
name: "fc",
in_features: 5 * 5 * 16,
out_features: 10,
x_zp: 0,
w_zp: 0,
out_zp: 0,
weight_bits: w::WEIGHT_BITS,
weight_encoding: SignedInt,
requant: requant_table(w::FC_MULT),
weights: w::FC_W.to_vec(),
bias: Some(w::FC_B.to_vec()),
};
let argmax = Layer::Argmax {
name: "argmax",
len: 10,
};
Model {
name: "tinyconv_mnist".to_string(),
input_len: 28 * 28,
layers: vec![conv1, relu1, pool1, conv2, relu2, pool2, fc, argmax],
extra_outputs: vec![],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tinyconv_shapes_match_cortexm() {
let m = tinyconv_mnist_from_cortexm();
assert_eq!(m.input_len, 28 * 28);
assert_eq!(m.layers.len(), 8);
assert_eq!(m.scratch_len(), 26 * 26 * 8); }
}