use crate::model::{Lin, linear, linear_multi};
use crate::safetensors::ModelFiles;
use crate::sd::ops::{attention, gelu_erf};
use anyhow::{Result, bail};
pub(crate) struct Dense<'a> {
pub w: Lin<'a>,
pub b: Vec<f32>,
pub n_out: usize,
pub n_in: usize,
}
impl<'a> Dense<'a> {
pub fn load(files: &'a ModelFiles, p: &str, n_out: usize, n_in: usize) -> Result<Self> {
let w = files.bf16(&format!("{p}.weight"))?;
let b = files.f32(&format!("{p}.bias"))?;
if w.len() != n_out * n_in || b.len() != n_out {
bail!("{p}: {} weights / {} biases, expected {n_out}x{n_in}", w.len(), b.len());
}
Ok(Self {
w: Lin::bf16(w),
b,
n_out,
n_in,
})
}
pub fn load_quant(files: &'a ModelFiles, p: &str, n_out: usize, n_in: usize, quant: crate::model::Quant) -> Result<Self> {
let b = files.f32(&format!("{p}.bias"))?;
if b.len() != n_out {
bail!("{p}: {} biases, expected {n_out}", b.len());
}
Ok(Self {
w: crate::model::load_lin(files, &format!("{p}.weight"), n_out, n_in, quant)?,
b,
n_out,
n_in,
})
}
fn slice(files: &'a ModelFiles, p: &str, n_in: usize, r0: usize, r1: usize) -> Result<Self> {
let w = files.bf16(&format!("{p}.weight"))?;
let b = files.f32(&format!("{p}.bias"))?;
if w.len() < r1 * n_in || b.len() < r1 {
bail!("{p}: too small for rows {r0}..{r1}");
}
Ok(Self {
w: Lin::bf16(&w[r0 * n_in..r1 * n_in]),
b: b[r0..r1].to_vec(),
n_out: r1 - r0,
n_in,
})
}
pub fn add_bias(&self, y: &mut [f32]) {
crate::kernels::for_chunks(y, self.n_out, y.len() > 4 * self.n_out, |_, r| {
for (v, &b) in r.iter_mut().zip(&self.b) {
*v += b;
}
});
}
pub fn forward(&self, x: &[f32], rows: usize) -> Vec<f32> {
let mut y = vec![0f32; rows * self.n_out];
linear(&self.w, self.n_out, self.n_in, x, rows, &mut y);
self.add_bias(&mut y);
y
}
}
pub(crate) fn gelu_inplace(x: &mut [f32]) {
crate::kernels::for_chunks(x, 4096, x.len() > 65536, |_, c| c.iter_mut().for_each(|v| *v = gelu_erf(*v)));
}
pub(crate) struct Mlp<'a> {
pub l0: Dense<'a>,
pub l1: Dense<'a>,
}
impl<'a> Mlp<'a> {
pub fn load(files: &'a ModelFiles, p: &str, n_in: usize, n_embed: usize) -> Result<Self> {
Ok(Self {
l0: Dense::load(files, &format!("{p}.layers.0"), n_embed, n_in)?,
l1: Dense::load(files, &format!("{p}.layers.2"), n_embed, n_embed)?,
})
}
pub fn forward(&self, x: &[f32], rows: usize) -> Vec<f32> {
let mut y = self.l0.forward(x, rows);
gelu_inplace(&mut y);
self.l1.forward(&y, rows)
}
}
struct LayerNorm {
w: Vec<f32>,
b: Vec<f32>,
}
impl LayerNorm {
fn load(files: &ModelFiles, p: &str, n: usize) -> Result<Self> {
let (w, b) = (files.f32(&format!("{p}.weight"))?, files.f32(&format!("{p}.bias"))?);
if w.len() != n || b.len() != n {
bail!("{p}: expected {n} channels");
}
Ok(Self { w, b })
}
fn forward(&self, x: &[f32]) -> Vec<f32> {
let n = self.w.len();
let mut y = vec![0f32; x.len()];
crate::kernels::for_chunks(&mut y, n, x.len() > 4 * n, |i, r| {
let xr = &x[i * n..(i + 1) * n];
let mean = xr.iter().map(|&v| v as f64).sum::<f64>() / n as f64;
let var = xr.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n as f64;
let inv = 1.0 / (var + 1e-6).sqrt();
for j in 0..n {
r[j] = ((xr[j] as f64 - mean) * inv) as f32 * self.w[j] + self.b[j];
}
});
y
}
}
struct Block<'a> {
norm1: LayerNorm,
q: Dense<'a>,
k: Dense<'a>,
v: Dense<'a>,
proj: Dense<'a>,
norm2: LayerNorm,
fc1: Dense<'a>,
fc2: Dense<'a>,
}
pub struct VisionEncoder<'a> {
pub image_size: usize,
pub patch: usize,
width: usize,
heads: usize,
patch_embed: Dense<'a>,
pos_embed: Vec<f32>,
blocks: Vec<Block<'a>>,
norm: LayerNorm,
aligner: Mlp<'a>,
pub mean: [f32; 3],
pub std: [f32; 3],
pub background: [u8; 3],
}
impl<'a> VisionEncoder<'a> {
#[allow(clippy::too_many_arguments)]
pub fn load(
files: &'a ModelFiles,
image_size: usize,
layers: usize,
n_embed: usize,
mean: [f32; 3],
std: [f32; 3],
background: [u8; 3],
) -> Result<Self> {
let (width, heads, patch) = (1024, 16, 16);
let p = "vision_model.vision_tower";
let grid = image_size / patch;
let pos_embed = files.f32(&format!("{p}.pos_embed"))?;
if pos_embed.len() != grid * grid * width {
bail!(
"{p}.pos_embed: {} values for a {grid}x{grid} grid of width {width}",
pos_embed.len()
);
}
let mut blocks = Vec::with_capacity(layers);
for i in 0..layers {
let b = format!("{p}.blocks.{i}");
let qkv = format!("{b}.attn.qkv");
blocks.push(Block {
norm1: LayerNorm::load(files, &format!("{b}.norm1"), width)?,
q: Dense::slice(files, &qkv, width, 0, width)?,
k: Dense::slice(files, &qkv, width, width, 2 * width)?,
v: Dense::slice(files, &qkv, width, 2 * width, 3 * width)?,
proj: Dense::load(files, &format!("{b}.attn.proj"), width, width)?,
norm2: LayerNorm::load(files, &format!("{b}.norm2"), width)?,
fc1: Dense::load(files, &format!("{b}.mlp.fc1"), 4 * width, width)?,
fc2: Dense::load(files, &format!("{b}.mlp.fc2"), width, 4 * width)?,
});
}
Ok(Self {
image_size,
patch,
width,
heads,
patch_embed: Dense::load(files, &format!("{p}.patch_embed.proj"), width, 3 * patch * patch)?,
pos_embed,
blocks,
norm: LayerNorm::load(files, &format!("{p}.norm"), width)?,
aligner: Mlp::load(files, "aligner", width, n_embed)?,
mean,
std,
background,
})
}
pub fn preprocess(&self, rgb: &[u8], w: usize, h: usize) -> Result<Vec<f32>> {
if w == 0 || h == 0 || rgb.len() != w * h * 3 {
bail!("image: {} bytes for {w}x{h} RGB", rgb.len());
}
let s = self.image_size;
let m = w.max(h);
let th = ((h as f64 / m as f64 * s as f64) as usize).max(14);
let tw = ((w as f64 / m as f64 * s as f64) as usize).max(14);
let img = crate::resample::resize_bicubic_rgb8(rgb, w, h, tw, th);
let side = tw.max(th);
let (ox, oy) = ((side - tw) / 2, (side - th) / 2);
let mut sq = Vec::with_capacity(side * side * 3);
for y in 0..side {
for x in 0..side {
if x >= ox && x < ox + tw && y >= oy && y < oy + th {
let o = ((y - oy) * tw + (x - ox)) * 3;
sq.extend_from_slice(&img[o..o + 3]);
} else {
sq.extend_from_slice(&self.background);
}
}
}
if side != s {
bail!("image: resized to {side}x{side}, expected {s}x{s}");
}
let mut px = vec![0f32; 3 * s * s];
for c in 0..3 {
for i in 0..s * s {
let v = (sq[i * 3 + c] as f64 * (1.0 / 255.0)) as f32;
px[c * s * s + i] = (v - self.mean[c]) / self.std[c];
}
}
Ok(px)
}
pub fn encode(&self, px: &[f32], features: Option<&mut Vec<f32>>) -> Vec<f32> {
let (s, p, d) = (self.image_size, self.patch, self.width);
let g = s / p;
let n = g * g;
let mut patches = vec![0f32; n * 3 * p * p];
crate::kernels::for_chunks(&mut patches, 3 * p * p, true, |i, r| {
let (gy, gx) = (i / g, i % g);
for c in 0..3 {
for y in 0..p {
let src = c * s * s + (gy * p + y) * s + gx * p;
r[(c * p + y) * p..(c * p + y + 1) * p].copy_from_slice(&px[src..src + p]);
}
}
});
let mut x = self.patch_embed.forward(&patches, n);
crate::kernels::add_inplace(&mut x, &self.pos_embed);
let hd = d / self.heads;
for b in &self.blocks {
let hn = b.norm1.forward(&x);
let (mut q, mut k, mut v) = (vec![0f32; n * d], vec![0f32; n * d], vec![0f32; n * d]);
linear_multi(
&mut [(&b.q.w, d, &mut q[..]), (&b.k.w, d, &mut k[..]), (&b.v.w, d, &mut v[..])],
d,
&hn,
n,
);
b.q.add_bias(&mut q);
b.k.add_bias(&mut k);
b.v.add_bias(&mut v);
let a = attention(&q, &k, &v, n, n, self.heads, hd, false);
crate::kernels::add_inplace(&mut x, &b.proj.forward(&a, n));
let hn = b.norm2.forward(&x);
let mut m = b.fc1.forward(&hn, n);
gelu_inplace(&mut m);
crate::kernels::add_inplace(&mut x, &b.fc2.forward(&m, n));
}
let x = self.norm.forward(&x);
if let Some(f) = features {
f.clone_from(&x);
}
self.aligner.forward(&x, n)
}
pub fn tokens(&self) -> usize {
(self.image_size / self.patch).pow(2)
}
}