mod attention;
mod neck;
mod par;
mod preprocess;
mod vit;
use std::path::Path;
use pagesift_infer::{Conv2d, Tensor, Weights, ops};
pub use preprocess::{SIZE, preprocess};
const PREFIX: &str = "model.encoder";
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub image_size: usize,
pub patch_size: usize,
pub hidden_size: usize,
pub layers: usize,
pub heads: usize,
pub window_size: usize,
pub global_attn_indexes: Vec<usize>,
pub layer_norm_eps: f32,
}
impl Config {
pub fn from_json(v: &serde_json::Value) -> Result<Config, String> {
let c = &v["vision_config"];
let int = |k: &str| {
c[k].as_u64()
.map(|x| x as usize)
.ok_or_else(|| format!("vision_config has no integer {k}"))
};
for k in ["use_abs_pos", "use_rel_pos"] {
if c[k].as_bool() != Some(true) {
return Err(format!("only {k} = true is supported"));
}
}
if c["hidden_act"].as_str().unwrap_or("gelu") != "gelu" {
return Err("only hidden_act = gelu is supported".into());
}
let global_attn_indexes = c["global_attn_indexes"]
.as_array()
.ok_or("vision_config has no global_attn_indexes")?
.iter()
.map(|x| {
x.as_u64()
.map(|x| x as usize)
.ok_or("global_attn_indexes must be integers")
})
.collect::<Result<_, _>>()?;
let cfg = Config {
image_size: int("image_size")?,
patch_size: int("patch_size")?,
hidden_size: int("hidden_size")?,
layers: int("num_hidden_layers")?,
heads: int("num_attention_heads")?,
window_size: int("window_size")?,
global_attn_indexes,
layer_norm_eps: c["layer_norm_eps"]
.as_f64()
.ok_or("vision_config has no layer_norm_eps")? as f32,
};
if cfg.image_size != SIZE {
return Err(format!(
"preprocessing always outputs {SIZE}, the config says {}",
cfg.image_size
));
}
if cfg.patch_size == 0 || !cfg.image_size.is_multiple_of(cfg.patch_size) {
return Err("image_size is not a multiple of patch_size".into());
}
if cfg.heads == 0 || !cfg.hidden_size.is_multiple_of(cfg.heads) {
return Err("hidden_size is not divisible by the number of heads".into());
}
Ok(cfg)
}
pub fn grid(&self) -> usize {
self.image_size / self.patch_size
}
}
pub struct Encoder {
cfg: Config,
patch: Conv2d,
pos: Vec<f32>,
layers: Vec<vit::Layer>,
neck: neck::Neck,
proj: neck::Projector,
}
impl Encoder {
pub fn load(dir: &Path) -> Result<Encoder, String> {
let cfg = Config::from_json(&crate::read_json(dir, "config.json")?)?;
Encoder::from_weights(&crate::read_weights(dir)?, cfg)
}
pub fn from_weights(w: &Weights, cfg: Config) -> Result<Encoder, String> {
let (g, c) = (cfg.grid(), cfg.hidden_size);
let ps = cfg.patch_size;
let patch = Conv2d::load(
w,
&format!("{PREFIX}.patch_embed.projection"),
(ps, ps),
(0, 0),
(1, 1),
1,
)?;
let pos = w.get(&format!("{PREFIX}.pos_embed"))?;
if pos.shape != [1, g, g, c] {
return Err(format!(
"pos_embed has shape {:?}, expected [1, {g}, {g}, {c}]",
pos.shape
));
}
let mut layers = Vec::with_capacity(cfg.layers);
for i in 0..cfg.layers {
let window = if cfg.global_attn_indexes.contains(&i) {
0
} else {
cfg.window_size
};
layers.push(vit::Layer::load(
w,
&format!("{PREFIX}.layers.{i}"),
cfg.heads,
window,
g,
cfg.layer_norm_eps,
)?);
}
Ok(Encoder {
patch,
pos: pos.data.clone(),
layers,
neck: neck::Neck::load(w, &format!("{PREFIX}.neck"))?,
proj: neck::Projector::load(w, &format!("{PREFIX}.multi_modal_projector"))?,
cfg,
})
}
pub fn config(&self) -> &Config {
&self.cfg
}
pub fn encode(&self, x: &Tensor) -> Result<Tensor, String> {
self.encode_traced(x, |_, _| {})
}
pub fn encode_traced(
&self,
x: &Tensor,
mut trace: impl FnMut(&str, &Tensor),
) -> Result<Tensor, String> {
let s = self.cfg.image_size;
if x.shape != [3, s, s] {
return Err(format!("input must be [3, {s}, {s}], got {:?}", x.shape));
}
let g = self.cfg.grid();
let mut h = ops::chw_to_seq(&self.patch.forward(x));
for (v, p) in h.data.iter_mut().zip(&self.pos) {
*v += p;
}
trace("patch_embed+pos", &h);
for (i, l) in self.layers.iter().enumerate() {
h = l.forward(h, g);
trace(&format!("layer{i}"), &h);
}
let n = self.neck.forward(&h, g);
trace("neck", &n);
let m = self.proj.forward(&n);
trace("projector", &m);
Ok(m)
}
}