use crate::model::{Layer, Model};
use crate::passes::Hints;
pub fn run(model: &Model, hints: &mut [Hints]) {
debug_assert_eq!(model.layers.len(), hints.len());
let n = model.layers.len();
for i in 0..n.saturating_sub(1) {
let conv_idx = i;
let relu_idx = i + 1;
if matches(&model.layers[conv_idx], &model.layers[relu_idx]) {
hints[conv_idx].fuses_relu = true;
hints[relu_idx].elided = true;
}
}
}
fn matches(a: &Layer, b: &Layer) -> bool {
let conv_ok = matches!(a, Layer::Conv2d { out_zp: 0, .. });
let relu_ok = matches!(b, Layer::Relu { zero_point: 0, len, .. } if *len == a.out_len());
conv_ok && relu_ok
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::tinyconv_mnist_from_cortexm;
use crate::passes::Hints;
#[test]
fn tinyconv_fires_on_both_conv_relu_pairs() {
let m = tinyconv_mnist_from_cortexm();
let mut hints = vec![Hints::default(); m.layers.len()];
run(&m, &mut hints);
assert!(hints[0].fuses_relu, "conv1 should fuse relu1");
assert!(hints[1].elided, "relu1 should be elided");
assert!(hints[3].fuses_relu, "conv2 should fuse relu2");
assert!(hints[4].elided, "relu2 should be elided");
for &i in &[2usize, 5, 6, 7] {
assert!(!hints[i].fuses_relu);
assert!(!hints[i].elided);
}
}
#[test]
fn no_match_when_pattern_breaks() {
let m = Model {
name: "noop".into(),
input_len: 4,
layers: vec![
Layer::Conv2d {
name: "c",
h_in: 2,
w_in: 2,
c_in: 1,
c_out: 1,
kh: 1,
kw: 1,
pad_h: 0,
pad_w: 0,
stride_h: 1,
stride_w: 1,
x_zp: 0,
w_zp: 0,
out_zp: 0,
weight_bits: 8,
weight_encoding: crate::model::WeightEncoding::SignedInt,
requant: vec![(1 << 30, 0)],
weights: vec![1],
bias: None,
},
Layer::MaxPool2d {
name: "p",
h_in: 2,
w_in: 2,
c: 1,
kh: 1,
kw: 1,
stride_h: 1,
stride_w: 1,
},
],
extra_outputs: vec![],
};
let mut hints = vec![Hints::default(); 2];
run(&m, &mut hints);
assert!(!hints[0].fuses_relu);
assert!(!hints[1].elided);
}
}