use pagesift_infer::exec::{Cpu, Exec};
use pagesift_infer::{Activation, BN_EPS, BatchNorm, Conv2d, Tensor, Weights};
pub type BlockConfig = (usize, usize, usize, (usize, usize), bool);
#[derive(Debug, Clone)]
pub struct Config {
pub stem_channels: [usize; 3],
pub blocks: Vec<Vec<BlockConfig>>,
}
impl Config {
pub fn from_json(v: &serde_json::Value) -> Result<Config, String> {
let b = &v["backbone_config"];
if b["stem_type"].as_str().unwrap_or("large") != "large" {
return Err("only the large stem is supported".into());
}
if b["use_learnable_affine_block"].as_bool().unwrap_or(false) {
return Err("learnable affine blocks are not supported".into());
}
let int = |x: &serde_json::Value| {
x.as_u64()
.map(|v| v as usize)
.ok_or("expected an integer in the config")
};
let sc = b["stem_channels"]
.as_array()
.ok_or("missing stem_channels")?;
let stem_channels = [int(&sc[0])?, int(&sc[1])?, int(&sc[2])?];
let mut blocks = Vec::new();
for stage in b["block_configs"]
.as_array()
.ok_or("missing block_configs")?
{
let mut s = Vec::new();
for blk in stage.as_array().ok_or("malformed block_configs")? {
let a = blk.as_array().ok_or("malformed block_configs")?;
let stride = match a[3].as_array() {
Some(p) => (int(&p[0])?, int(&p[1])?),
None => (int(&a[3])?, int(&a[3])?),
};
s.push((
int(&a[0])?,
int(&a[1])?,
int(&a[2])?,
stride,
a[4].as_bool().unwrap_or(false),
));
}
blocks.push(s);
}
Ok(Config {
stem_channels,
blocks,
})
}
}
#[derive(Debug, Clone)]
pub struct ConvBn {
pub conv: Conv2d,
pub act: Activation,
}
impl ConvBn {
pub fn load(
w: &Weights,
p: &str,
stride: (usize, usize),
groups: usize,
act: Activation,
) -> Result<ConvBn, String> {
let k = w.get(&format!("{p}.convolution.weight"))?.shape[2];
let pad = (k - 1) / 2;
let mut conv = Conv2d::load(
w,
&format!("{p}.convolution"),
stride,
(pad, pad),
(1, 1),
groups,
)?;
conv.fold(&BatchNorm::load(w, &format!("{p}.normalization"), BN_EPS)?);
Ok(ConvBn { conv, act })
}
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 y = e.conv(&self.conv, x);
e.act(self.act, &mut y);
y
}
}
#[derive(Debug, Clone)]
struct Se {
c1: Conv2d,
c2: Conv2d,
}
impl Se {
fn load(w: &Weights, p: &str) -> Result<Se, String> {
let c = |i: usize| {
Conv2d::load(
w,
&format!("{p}.convolutions.{i}"),
(1, 1),
(0, 0),
(1, 1),
1,
)
};
Ok(Se {
c1: c(0)?,
c2: c(2)?,
})
}
fn forward_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.act(Activation::HardSigmoid, &mut s);
e.scale_channels(x, &s);
}
}
#[derive(Debug, Clone)]
struct Block {
token: Conv2d,
se: Option<Se>,
ch1: ConvBn,
ch2: ConvBn,
residual: bool,
}
impl Block {
fn load(w: &Weights, p: &str, cfg: &BlockConfig) -> Result<Block, String> {
let &(k, cin, cout, stride, use_se) = cfg;
let same = cin == cout && stride == (1, 1);
let token = if same {
Conv2d::load(
w,
&format!("{p}.token_conv"),
stride,
(k / 2, k / 2),
(1, 1),
cin,
)?
} else {
ConvBn::load(
w,
&format!("{p}.token_conv"),
stride,
cin,
Activation::Identity,
)?
.conv
};
let se = if use_se {
Some(Se::load(w, &format!("{p}.token_squeeze_excitation"))?)
} else {
None
};
Ok(Block {
token,
se,
ch1: ConvBn::load(
w,
&format!("{p}.channel_conv1"),
(1, 1),
1,
Activation::Gelu,
)?,
ch2: ConvBn::load(
w,
&format!("{p}.channel_conv2"),
(1, 1),
1,
Activation::Identity,
)?,
residual: same,
})
}
fn forward_on<E: Exec>(&self, e: &E, x: &E::T) -> E::T {
let mut t = e.conv(&self.token, x);
if let Some(se) = &self.se {
se.forward_on(e, &mut t);
}
let y = self.ch2.forward_on(e, &self.ch1.forward_on(e, &t));
if self.residual { e.add(&t, &y) } else { y }
}
}
#[derive(Debug, Clone)]
pub struct Backbone {
stem1: ConvBn,
stem2a: ConvBn,
stem2b: ConvBn,
stem3: ConvBn,
stem4: ConvBn,
stages: Vec<Vec<Block>>,
}
impl Backbone {
pub fn load(w: &Weights, p: &str, cfg: &Config) -> Result<Backbone, String> {
let s = |name: &str, stride: usize| {
ConvBn::load(
w,
&format!("{p}.convolution.{name}"),
(stride, stride),
1,
Activation::Relu,
)
};
let mut stages = Vec::new();
for (i, stage) in cfg.blocks.iter().enumerate() {
let mut v = Vec::new();
for (j, b) in stage.iter().enumerate() {
v.push(Block::load(w, &format!("{p}.blocks.{i}.blocks.{j}"), b)?);
}
stages.push(v);
}
Ok(Backbone {
stem1: s("stem1", 2)?,
stem2a: s("stem2a", 1)?,
stem2b: s("stem2b", 1)?,
stem3: s("stem3", 2)?,
stem4: s("stem4", 1)?,
stages,
})
}
pub fn forward(&self, x: &Tensor) -> Vec<Tensor> {
self.forward_on(&Cpu, x)
}
pub fn forward_on<E: Exec>(&self, ex: &E, x: &E::T) -> Vec<E::T> {
let stem = self.stem_on(ex, x);
let mut outs: Vec<E::T> = Vec::with_capacity(self.stages.len());
for stage in &self.stages {
let h = self.stage_on(ex, stage, outs.last().unwrap_or(&stem));
outs.push(h);
}
outs
}
pub fn forward_last_on<E: Exec>(&self, ex: &E, x: &E::T) -> E::T {
let mut h = self.stem_on(ex, x);
for stage in &self.stages {
h = self.stage_on(ex, stage, &h);
}
h
}
fn stem_on<E: Exec>(&self, ex: &E, x: &E::T) -> E::T {
let e = ex.pad_right_bottom(&self.stem1.forward_on(ex, x), 1, 1);
let a = ex.pad_right_bottom(&self.stem2a.forward_on(ex, &e), 1, 1);
let a = self.stem2b.forward_on(ex, &a);
let pooled = ex.max_pool2d(&e, (2, 2), (1, 1), true);
let cat = ex.concat(&[&pooled, &a]);
self.stem4.forward_on(ex, &self.stem3.forward_on(ex, &cat))
}
fn stage_on<E: Exec>(&self, ex: &E, stage: &[Block], x: &E::T) -> E::T {
let mut h = stage[0].forward_on(ex, x);
for b in &stage[1..] {
h = b.forward_on(ex, &h);
}
h
}
}