use pagesift_infer::exec::{Cpu, Exec};
use pagesift_infer::{Activation, BN_EPS, BatchNorm, Conv2d, ConvTranspose2d, Tensor, Weights};
#[derive(Debug, Clone)]
pub struct NeckConfig {
pub in_channels: Vec<usize>,
pub out_channels: usize,
pub kernel_size: usize,
}
pub fn head_kernels(v: &serde_json::Value) -> Result<[usize; 3], String> {
let a = v["kernel_list"].as_array().ok_or("missing kernel_list")?;
let k: Vec<usize> = a
.iter()
.map(|x| {
x.as_u64()
.map(|n| n as usize)
.ok_or("kernel_list must be integers")
})
.collect::<Result<_, _>>()?;
k.try_into()
.map_err(|_| "kernel_list must have 3 numbers".into())
}
impl NeckConfig {
pub fn from_json(v: &serde_json::Value) -> Result<NeckConfig, String> {
let ints = |key: &str| -> Result<Vec<usize>, String> {
v[key]
.as_array()
.ok_or(format!("missing {key}"))?
.iter()
.map(|x| {
x.as_u64()
.map(|n| n as usize)
.ok_or(format!("{key} must be an integer"))
})
.collect()
};
if v["interpolate_mode"].as_str().unwrap_or("nearest") != "nearest" {
return Err("the neck only supports nearest-neighbor upsampling".into());
}
let in_channels = ints("layer_list_out_channels")?;
if in_channels.len() != 4 {
return Err("layer_list_out_channels must have 4 stages".into());
}
Ok(NeckConfig {
in_channels,
out_channels: v["neck_out_channels"].as_u64().unwrap_or(96) as usize,
kernel_size: v["dilated_kernel_size"].as_u64().unwrap_or(7) as usize,
})
}
}
#[derive(Debug, Clone)]
struct SeResidual {
c1: Conv2d,
c2: Conv2d,
}
impl SeResidual {
fn load(w: &Weights, p: &str) -> Result<SeResidual, String> {
let c = |n: &str| Conv2d::load(w, &format!("{p}.{n}"), (1, 1), (0, 0), (1, 1), 1);
Ok(SeResidual {
c1: c("conv1")?,
c2: c("conv2")?,
})
}
fn apply_on<E: Exec>(&self, e: &E, x: &mut E::T) {
let pooled = e.global_avg(x);
let mut s = e.conv(&self.c1, &pooled);
e.act(Activation::Relu, &mut s);
let mut s = e.conv(&self.c2, &s);
e.se_gate(&mut s);
e.scale_channels(x, &s);
}
}
#[derive(Debug, Clone)]
struct InsertConv {
conv: Conv2d,
se: SeResidual,
}
impl InsertConv {
fn forward_on<E: Exec>(&self, e: &E, x: &E::T) -> E::T {
let mut h = e.conv(&self.conv, x);
self.se.apply_on(e, &mut h);
h
}
}
#[derive(Debug, Clone)]
struct InputConv {
depthwise: Conv2d,
pointwise: Conv2d,
se: SeResidual,
}
impl InputConv {
fn forward_on<E: Exec>(&self, e: &E, x: &E::T) -> E::T {
let mut h = e.conv(&self.pointwise, &e.conv(&self.depthwise, x));
self.se.apply_on(e, &mut h);
h
}
}
#[derive(Debug, Clone)]
pub struct Neck {
insert: Vec<InsertConv>,
input: Vec<InputConv>,
}
pub(super) fn upsample_on<E: Exec>(e: &E, x: &E::T, s: usize) -> E::T {
let shape = e.shape(x);
e.upsample(x, shape[1] * s, shape[2] * s)
}
impl Neck {
pub fn load(w: &Weights, p: &str, cfg: &NeckConfig) -> Result<Neck, String> {
let k = cfg.kernel_size;
let c = cfg.out_channels;
let mut insert = Vec::new();
let mut input = Vec::new();
for i in 0..cfg.in_channels.len() {
let q = format!("{p}.insert_conv.{i}");
insert.push(InsertConv {
conv: Conv2d::load(w, &format!("{q}.in_conv"), (1, 1), (0, 0), (1, 1), 1)?,
se: SeResidual::load(w, &format!("{q}.squeeze_excitation_block"))?,
});
let q = format!("{p}.input_conv.{i}");
input.push(InputConv {
depthwise: Conv2d::load(
w,
&format!("{q}.depthwise_convolution"),
(1, 1),
(k / 2, k / 2),
(1, 1),
c,
)?,
pointwise: Conv2d::load(
w,
&format!("{q}.pointwise_convolution"),
(1, 1),
(0, 0),
(1, 1),
1,
)?,
se: SeResidual::load(w, &format!("{q}.squeeze_excitation_module"))?,
});
}
Ok(Neck { insert, input })
}
pub fn forward(&self, feats: &[Tensor]) -> Result<Tensor, String> {
self.forward_on(&Cpu, feats)
}
pub fn forward_on<E: Exec>(&self, e: &E, feats: &[E::T]) -> Result<E::T, String> {
if feats.len() != self.insert.len() {
return Err(format!(
"the backbone gave {} feature stages, the neck needs {}",
feats.len(),
self.insert.len()
));
}
let mut fused: Vec<E::T> = self
.insert
.iter()
.zip(feats)
.map(|(m, f)| m.forward_on(e, f))
.collect();
for i in (0..fused.len() - 1).rev() {
let up = upsample_on(e, &fused[i + 1], 2);
if e.shape(&up) != e.shape(&fused[i]) {
return Err(format!(
"neck upsampling size mismatch: {:?} and {:?}",
e.shape(&up),
e.shape(&fused[i])
));
}
e.add_assign(&mut fused[i], &up);
}
let processed: Vec<E::T> = self
.input
.iter()
.zip(&fused)
.enumerate()
.map(|(i, (m, f))| {
let h = m.forward_on(e, f);
if i == 0 {
h
} else {
upsample_on(e, &h, 1 << i)
}
})
.collect();
let refs: Vec<&E::T> = processed.iter().rev().collect();
Ok(e.concat(&refs))
}
}
#[derive(Debug, Clone)]
pub struct Head {
down: Conv2d,
up: ConvTranspose2d,
fin: ConvTranspose2d,
}
impl Head {
pub fn load(w: &Weights, p: &str, k: [usize; 3]) -> Result<Head, String> {
let mut down = Conv2d::load(
w,
&format!("{p}.conv_down.convolution"),
(1, 1),
(k[0] / 2, k[0] / 2),
(1, 1),
1,
)?;
down.fold(&BatchNorm::load(w, &format!("{p}.conv_down.norm"), BN_EPS)?);
let mut up = ConvTranspose2d::load(w, &format!("{p}.conv_up.convolution"), (2, 2), (0, 0))?;
up.fold(&BatchNorm::load(w, &format!("{p}.conv_up.norm"), BN_EPS)?);
let fin = ConvTranspose2d::load(w, &format!("{p}.conv_final"), (2, 2), (0, 0))?;
for (name, got, want) in [
("conv_up", up.weight.shape[2], k[1]),
("conv_final", fin.weight.shape[2], k[2]),
] {
if got != want {
return Err(format!(
"{name} has kernel size {got}, the config says {want}"
));
}
}
Ok(Head { down, up, fin })
}
pub fn forward(&self, x: &Tensor) -> Tensor {
self.forward_on(&Cpu, x)
}
pub fn forward_on<E: Exec>(&self, e: &E, x: &E::T) -> E::T {
let mut h = e.conv(&self.down, x);
e.act(Activation::Relu, &mut h);
let mut h = e.conv_transpose(&self.up, &h);
e.act(Activation::Relu, &mut h);
let mut h = e.conv_transpose(&self.fin, &h);
e.act(Activation::Sigmoid, &mut h);
h
}
}