use anyhow::{Context, Result};
use rayon::prelude::*;
use std::path::Path;
use std::sync::OnceLock;
use crate::cpu_gemm::PackedWeight;
use crate::encoder_weights::Act;
use crate::weights::LazySt;
#[derive(Clone, Debug)]
pub struct VisionConfig {
pub depth: usize,
pub hidden: usize,
pub heads: usize,
pub intermediate: usize,
pub out_hidden: usize,
pub patch: usize,
pub merge: usize,
pub temporal: usize,
pub in_channels: usize,
pub grid_side: usize,
pub eps: f32,
pub act: Act,
pub rope_theta: f32,
pub min_pixels: usize,
pub max_pixels: usize,
}
impl VisionConfig {
pub fn head_dim(&self) -> usize {
self.hidden / self.heads
}
pub fn merge_unit(&self) -> usize {
self.merge * self.merge
}
pub fn patch_dim(&self) -> usize {
self.in_channels * self.temporal * self.patch * self.patch
}
pub fn from_config(v: &serde_json::Value) -> Result<Self> {
let c = v
.get("vision_config")
.context("config.json vision_config")?;
let usize_at = |k: &str| -> Result<usize> {
c.get(k)
.and_then(|x| x.as_u64())
.map(|x| x as usize)
.with_context(|| format!("vision_config.{k}"))
};
let num_pos = usize_at("num_position_embeddings")?;
let grid_side = (num_pos as f64).sqrt().round() as usize;
anyhow::ensure!(
grid_side * grid_side == num_pos,
"num_position_embeddings ({num_pos}) must be a perfect square; the pos table is a \
square grid the interpolation walks"
);
let act = match c.get("hidden_act").and_then(|x| x.as_str()) {
Some("gelu_pytorch_tanh") | Some("gelu_new") => Act::GeluTanh,
Some("gelu") => Act::GeluErf,
Some("silu") => Act::Silu,
other => anyhow::bail!("unsupported vision hidden_act {other:?}"),
};
Ok(Self {
depth: usize_at("depth")?,
hidden: usize_at("hidden_size")?,
heads: usize_at("num_heads")?,
intermediate: usize_at("intermediate_size")?,
out_hidden: usize_at("out_hidden_size")?,
patch: usize_at("patch_size")?,
merge: usize_at("spatial_merge_size")?,
temporal: usize_at("temporal_patch_size")?,
in_channels: c.get("in_channels").and_then(|x| x.as_u64()).unwrap_or(3) as usize,
grid_side,
eps: 1e-6,
act,
rope_theta: 10_000.0,
min_pixels: 65536,
max_pixels: 16_777_216,
})
}
pub fn with_pixel_bounds(mut self, min_pixels: usize, max_pixels: usize) -> Self {
self.min_pixels = min_pixels;
self.max_pixels = max_pixels;
self
}
}
#[derive(Clone, Debug)]
pub struct ImagePatches {
pub patches: Vec<f32>,
pub grid: [u32; 3],
}
impl ImagePatches {
pub fn num_patches(&self) -> usize {
(self.grid[0] * self.grid[1] * self.grid[2]) as usize
}
pub fn num_tokens(&self, cfg: &VisionConfig) -> usize {
self.num_patches() / cfg.merge_unit()
}
}
pub fn smart_resize(
height: usize,
width: usize,
factor: usize,
min_pixels: usize,
max_pixels: usize,
) -> Result<(usize, usize)> {
let (hf, wf) = (height as f64, width as f64);
let ratio = hf.max(wf) / hf.min(wf);
anyhow::ensure!(
ratio <= 200.0,
"absolute aspect ratio must be < 200, got {ratio:.1}"
);
let f = factor as f64;
let round_half_even = |x: f64| -> f64 {
let lo = x.floor();
match (x - lo).partial_cmp(&0.5).expect("finite") {
std::cmp::Ordering::Greater => lo + 1.0,
std::cmp::Ordering::Less => lo,
std::cmp::Ordering::Equal if (lo as i64) % 2 == 0 => lo,
std::cmp::Ordering::Equal => lo + 1.0,
}
};
let round_by = |x: f64| -> usize { (round_half_even(x / f) * f) as usize };
let (mut h, mut w) = (round_by(hf), round_by(wf));
h = h.max(factor);
w = w.max(factor);
if h * w > max_pixels {
let beta = ((hf * wf) / max_pixels as f64).sqrt();
h = (((hf / beta) / f).floor() as usize * factor).max(factor);
w = (((wf / beta) / f).floor() as usize * factor).max(factor);
} else if h * w < min_pixels {
let beta = (min_pixels as f64 / (hf * wf)).sqrt();
h = ((hf * beta) / f).ceil() as usize * factor;
w = ((wf * beta) / f).ceil() as usize * factor;
}
Ok((h, w))
}
fn bicubic(x: f64) -> f64 {
const A: f64 = -0.5;
let x = x.abs();
if x < 1.0 {
((A + 2.0) * x - (A + 3.0)) * x * x + 1.0
} else if x < 2.0 {
(((x - 5.0) * x + 8.0) * x - 4.0) * A
} else {
0.0
}
}
fn triangle(x: f64) -> f64 {
let x = x.abs();
if x < 1.0 { 1.0 - x } else { 0.0 }
}
struct Taps {
precision: u32,
rows: Vec<(usize, Vec<i32>)>,
}
fn resample_taps(in_size: usize, out_size: usize) -> Taps {
resample_taps_with(in_size, out_size, bicubic, 2.0, None)
}
fn resample_taps_with(
in_size: usize,
out_size: usize,
kernel: fn(f64) -> f64,
base_support: f64,
fixed_precision: Option<u32>,
) -> Taps {
let scale = in_size as f64 / out_size as f64;
let filter_scale = scale.max(1.0);
let support = base_support * filter_scale;
let inv = 1.0 / filter_scale;
let float_rows: Vec<(usize, Vec<f64>)> = (0..out_size)
.map(|i| {
let center = (i as f64 + 0.5) * scale;
let lo = ((center - support + 0.5).floor() as isize).max(0) as usize;
let hi = ((center + support + 0.5).floor() as isize).min(in_size as isize) as usize;
let w: Vec<f64> = (lo..hi)
.map(|j| kernel((j as f64 + 0.5 - center) * inv))
.collect();
let sum: f64 = w.iter().sum();
let w = if sum != 0.0 {
w.into_iter().map(|v| v / sum).collect()
} else {
w
};
(lo, w)
})
.collect();
let precision = fixed_precision.unwrap_or_else(|| {
let w_max = float_rows
.iter()
.flat_map(|(_, w)| w.iter())
.fold(0f64, |a, &b| a.max(b));
let mut p = 0u32;
while p < 22 && ((0.5 + w_max * (1i64 << (p + 1)) as f64) as i64) < (1 << 15) {
p += 1;
}
p
});
let one = (1i64 << precision) as f64;
let rows = float_rows
.into_iter()
.map(|(lo, w)| {
let q = w
.iter()
.map(|v| {
let x = v * one;
(if x < 0.0 { x - 0.5 } else { x + 0.5 }) as i32
})
.collect();
(lo, q)
})
.collect();
Taps { precision, rows }
}
#[inline]
fn tap_u8(acc: i32, precision: u32) -> u8 {
let v = (acc + (1 << (precision - 1))) >> precision;
v.clamp(0, 255) as u8
}
pub(crate) fn resize_rgb8(src: &[u8], sw: usize, sh: usize, dw: usize, dh: usize) -> Vec<u8> {
resize_rgb8_taps(
src,
sw,
sh,
dw,
resample_taps(sw, dw),
resample_taps(sh, dh),
)
}
pub fn resize_rgb8_bilinear(src: &[u8], sw: usize, sh: usize, dw: usize, dh: usize) -> Vec<u8> {
resize_rgb8_taps(
src,
sw,
sh,
dw,
resample_taps_with(sw, dw, triangle, 1.0, Some(22)),
resample_taps_with(sh, dh, triangle, 1.0, Some(22)),
)
}
pub fn resize_rgb8_bicubic(src: &[u8], sw: usize, sh: usize, dw: usize, dh: usize) -> Vec<u8> {
resize_rgb8_taps(
src,
sw,
sh,
dw,
resample_taps_with(sw, dw, bicubic, 2.0, Some(22)),
resample_taps_with(sh, dh, bicubic, 2.0, Some(22)),
)
}
pub fn decode_rgb8(bytes: &[u8]) -> Result<(Vec<u8>, usize, usize)> {
let img = image::load_from_memory(bytes).context("decode image")?;
let rgb = img.to_rgb8();
let (w, h) = (rgb.width() as usize, rgb.height() as usize);
Ok((rgb.into_raw(), w, h))
}
fn resize_rgb8_taps(src: &[u8], sw: usize, sh: usize, dw: usize, hx: Taps, hy: Taps) -> Vec<u8> {
const C: usize = 3;
let dh = hy.rows.len();
let mut tmp = vec![0u8; sh * dw * C];
tmp.par_chunks_mut(dw * C).enumerate().for_each(|(y, row)| {
let srow = &src[y * sw * C..(y + 1) * sw * C];
for (x, (lo, w)) in hx.rows.iter().enumerate() {
let mut acc = [0i32; C];
for (t, &wt) in w.iter().enumerate() {
let p = &srow[(lo + t) * C..(lo + t) * C + C];
for c in 0..C {
acc[c] += wt * p[c] as i32;
}
}
for c in 0..C {
row[x * C + c] = tap_u8(acc[c], hx.precision);
}
}
});
let mut out = vec![0u8; dh * dw * C];
out.par_chunks_mut(dw * C).enumerate().for_each(|(y, row)| {
let (lo, w) = &hy.rows[y];
for x in 0..dw {
let mut acc = [0i32; C];
for (t, &wt) in w.iter().enumerate() {
let p = &tmp[((lo + t) * dw + x) * C..((lo + t) * dw + x) * C + C];
for c in 0..C {
acc[c] += wt * p[c] as i32;
}
}
for c in 0..C {
row[x * C + c] = tap_u8(acc[c], hy.precision);
}
}
});
out
}
pub(crate) fn patchify(img: &[u8], h: usize, w: usize, cfg: &VisionConfig) -> ImagePatches {
patchify_with(img, h, w, cfg, [0.5; 3], [0.5; 3])
}
pub(crate) fn patchify_with(
img: &[u8],
h: usize,
w: usize,
cfg: &VisionConfig,
mean: [f32; 3],
std: [f32; 3],
) -> ImagePatches {
let (p, m, c, tp) = (cfg.patch, cfg.merge, cfg.in_channels, cfg.temporal);
let (gh, gw) = (h / p, w / p);
debug_assert_eq!(gh % m, 0, "grid height must be a multiple of merge");
debug_assert_eq!(gw % m, 0, "grid width must be a multiple of merge");
let pd = cfg.patch_dim();
let mut out = vec![0f32; gh * gw * pd];
out.par_chunks_mut(pd).enumerate().for_each(|(tok, dst)| {
let per_block = m * m;
let block = tok / per_block;
let within = tok % per_block;
let blocks_w = gw / m;
let (br, bc) = (block / blocks_w, block % blocks_w);
let (ir, ic) = (within / m, within % m);
let (prow, pcol) = (br * m + ir, bc * m + ic);
for ch in 0..c {
for t in 0..tp {
for py in 0..p {
let sy = prow * p + py;
for px in 0..p {
let sx = pcol * p + px;
let v = img[(sy * w + sx) * c + ch] as f32;
let idx = ((ch * tp + t) * p + py) * p + px;
dst[idx] = if mean[ch] == 0.5 && std[ch] == 0.5 {
v / 127.5 - 1.0
} else {
(v / 255.0 - mean[ch]) / std[ch]
};
}
}
}
}
});
ImagePatches {
patches: out,
grid: [1, gh as u32, gw as u32],
}
}
pub fn preprocess_rgb8(
rgb: &[u8],
width: usize,
height: usize,
cfg: &VisionConfig,
) -> Result<ImagePatches> {
anyhow::ensure!(
rgb.len() == width * height * cfg.in_channels,
"rgb buffer is {} bytes, expected {}×{}×{}",
rgb.len(),
width,
height,
cfg.in_channels
);
let factor = cfg.patch * cfg.merge;
let (rh, rw) = smart_resize(height, width, factor, cfg.min_pixels, cfg.max_pixels)?;
let resized = if (rh, rw) == (height, width) {
rgb.to_vec()
} else {
resize_rgb8(rgb, width, height, rw, rh)
};
Ok(patchify(&resized, rh, rw, cfg))
}
pub fn preprocess_bytes(bytes: &[u8], cfg: &VisionConfig) -> Result<ImagePatches> {
let img = image::load_from_memory(bytes).context("decode image")?;
let rgb = img.to_rgb8();
let (w, h) = (rgb.width() as usize, rgb.height() as usize);
preprocess_rgb8(rgb.as_raw(), w, h, cfg)
}
pub fn mrope_positions(
tokens: &[u32],
image_token_id: u32,
grids: &[[u32; 3]],
merge: usize,
) -> Result<(Vec<[u32; 3]>, u32)> {
let m = merge as u32;
let mut pos = Vec::with_capacity(tokens.len());
let mut cur = 0u32;
let mut next_grid = 0usize;
let mut i = 0usize;
while i < tokens.len() {
if tokens[i] != image_token_id {
pos.push([cur, cur, cur]);
cur += 1;
i += 1;
continue;
}
let grid = *grids.get(next_grid).with_context(|| {
format!(
"prompt has more image placeholder runs than images ({} given)",
grids.len()
)
})?;
next_grid += 1;
let (gt, lh, lw) = (grid[0], grid[1] / m, grid[2] / m);
let want = (gt * lh * lw) as usize;
let run = tokens[i..]
.iter()
.take_while(|&&t| t == image_token_id)
.count();
anyhow::ensure!(
run == want,
"image placeholder run is {run} tokens but grid {grid:?} (merge {merge}) needs {want}"
);
for t in 0..gt {
for r in 0..lh {
for c in 0..lw {
pos.push([cur + t, cur + r, cur + c]);
}
}
}
cur += lh.max(lw);
i += run;
}
anyhow::ensure!(
next_grid == grids.len(),
"{} images supplied but the prompt has {next_grid} placeholder runs",
grids.len()
);
let next = pos
.iter()
.flat_map(|p| p.iter())
.copied()
.max()
.map_or(0, |m| m + 1);
Ok((pos, next))
}
pub fn prepare_prompt_gpu(
ctx: &crate::GpuCtx,
tower: &crate::vision_gpu::VisionGpu,
tokens: &[u32],
image_token_id: u32,
images: &[ImagePatches],
) -> Result<crate::server::VisionPrompt> {
let cfg = tower.config();
let grids: Vec<[u32; 3]> = images.iter().map(|i| i.grid).collect();
let (mpos, next_pos) = mrope_positions(tokens, image_token_id, &grids, cfg.merge)?;
let embeds = tower.forward(ctx, images)?;
place_image_rows(
tokens,
image_token_id,
embeds,
cfg.out_hidden,
mpos,
next_pos,
)
}
fn place_image_rows(
tokens: &[u32],
image_token_id: u32,
embeds: Vec<f32>,
out_hidden: usize,
mpos: Vec<[u32; 3]>,
next_pos: u32,
) -> Result<crate::server::VisionPrompt> {
let rows = embeds.len() / out_hidden;
let mut embed_index = vec![-1i32; tokens.len()];
let mut next_row = 0i32;
for (i, &t) in tokens.iter().enumerate() {
if t == image_token_id {
embed_index[i] = next_row;
next_row += 1;
}
}
anyhow::ensure!(
next_row as usize == rows,
"the prompt has {next_row} image placeholders but the tower produced {rows} rows"
);
Ok(crate::server::VisionPrompt {
embeds,
mpos,
embed_index,
next_pos,
})
}
pub fn prepare_prompt(
tower: &VisionTower,
tokens: &[u32],
image_token_id: u32,
images: &[ImagePatches],
) -> Result<crate::server::VisionPrompt> {
let cfg = tower.config();
let grids: Vec<[u32; 3]> = images.iter().map(|i| i.grid).collect();
let (mpos, next_pos) = mrope_positions(tokens, image_token_id, &grids, cfg.merge)?;
let embeds = tower.forward(images)?;
let rows = embeds.len() / cfg.out_hidden;
let want: usize = images.iter().map(|i| i.num_tokens(cfg)).sum();
anyhow::ensure!(
rows == want,
"the tower produced {rows} tokens but the images need {want}"
);
let mut embed_index = vec![-1i32; tokens.len()];
let mut next_row = 0i32;
for (i, &t) in tokens.iter().enumerate() {
if t == image_token_id {
embed_index[i] = next_row;
next_row += 1;
}
}
anyhow::ensure!(
next_row as usize == rows,
"the prompt has {next_row} image placeholders but the tower produced {rows} rows"
);
Ok(crate::server::VisionPrompt {
embeds,
mpos,
embed_index,
next_pos,
})
}
pub(crate) struct Linear {
pub(crate) w: Vec<f32>,
pub(crate) b: Option<Vec<f32>>,
pub(crate) n: usize,
pub(crate) k: usize,
packed: OnceLock<PackedWeight>,
}
impl Linear {
pub(crate) fn load(st: &LazySt, prefix: &str) -> Result<Self> {
let w = st.tensor_f32(&format!("{prefix}.weight"))?;
let b = st.tensor_f32(&format!("{prefix}.bias")).ok();
let n = b.as_ref().map(Vec::len).unwrap_or(0);
anyhow::ensure!(n > 0, "{prefix}.bias is required by this tower");
let k = w.len() / n;
Ok(Self {
w,
b,
n,
k,
packed: OnceLock::new(),
})
}
pub(crate) fn load_shaped(st: &LazySt, prefix: &str, n: usize) -> Result<Self> {
let w = st.tensor_f32(&format!("{prefix}.weight"))?;
let b = st.tensor_f32(&format!("{prefix}.bias")).ok();
anyhow::ensure!(w.len() % n == 0, "{prefix}.weight not divisible by n={n}");
let k = w.len() / n;
Ok(Self {
w,
b,
n,
k,
packed: OnceLock::new(),
})
}
pub(crate) fn from_parts(w: Vec<f32>, b: Option<Vec<f32>>, n: usize, k: usize) -> Self {
Self {
w,
b,
n,
k,
packed: OnceLock::new(),
}
}
pub(crate) fn forward(&self, y: &mut [f32], x: &[f32]) {
let m = x.len() / self.k;
debug_assert_eq!(x.len(), m * self.k);
let packed = self
.packed
.get_or_init(|| PackedWeight::new(&self.w, self.n, self.k));
crate::cpu_gemm::gemm_packed(&mut y[..m * self.n], x, packed, m, self.b.as_deref());
}
}
pub(crate) struct Norm {
pub(crate) w: Vec<f32>,
pub(crate) b: Vec<f32>,
}
impl Norm {
pub(crate) fn load(st: &LazySt, prefix: &str) -> Result<Self> {
Ok(Self {
w: st.tensor_f32(&format!("{prefix}.weight"))?,
b: st.tensor_f32(&format!("{prefix}.bias"))?,
})
}
}
pub(crate) fn layer_norm(x: &mut [f32], h: usize, n: &Norm, eps: f32) {
x.par_chunks_mut(h).for_each(|row| {
let mean = row.iter().sum::<f32>() / h as f32;
let var = row.iter().map(|v| (v - mean) * (v - mean)).sum::<f32>() / h as f32;
let inv = 1.0 / (var + eps).sqrt();
for (j, v) in row.iter_mut().enumerate() {
*v = (*v - mean) * inv * n.w[j] + n.b[j];
}
});
}
pub(crate) fn act_inplace(x: &mut [f32], act: Act) {
let c = (2.0 / std::f32::consts::PI).sqrt();
x.par_chunks_mut(4096).for_each(|chunk| {
for v in chunk {
*v = match act {
Act::GeluErf => 0.5 * *v * (1.0 + libm::erff(*v * std::f32::consts::FRAC_1_SQRT_2)),
Act::GeluTanh => 0.5 * *v * (1.0 + (c * (*v + 0.044715 * *v * *v * *v)).tanh()),
Act::Silu => *v / (1.0 + (-*v).exp()),
Act::Tanh => v.tanh(),
Act::Relu => v.max(0.0),
};
}
});
}
pub(crate) struct Block {
pub(crate) norm1: Norm,
pub(crate) qkv: Linear,
pub(crate) proj: Linear,
pub(crate) norm2: Norm,
pub(crate) fc1: Linear,
pub(crate) fc2: Linear,
}
pub struct VisionTower {
pub(crate) cfg: VisionConfig,
pub(crate) patch: Linear,
pub(crate) pos_embed: Vec<f32>,
pub(crate) blocks: Vec<Block>,
pub(crate) merger_norm: Norm,
pub(crate) merger_fc1: Linear,
pub(crate) merger_fc2: Linear,
}
impl VisionTower {
pub fn load(dir: &Path) -> Result<Self> {
let cfg_json: serde_json::Value = serde_json::from_slice(
&std::fs::read(dir.join("config.json"))
.with_context(|| format!("read {}", dir.join("config.json").display()))?,
)?;
let mut cfg = VisionConfig::from_config(&cfg_json)?;
let pcfg = dir.join("processor_config.json");
if pcfg.exists() {
let p: serde_json::Value = serde_json::from_slice(&std::fs::read(&pcfg)?)?;
if let Some(size) = p.pointer("/image_processor/size") {
let get = |k: &str| size.get(k).and_then(|x| x.as_u64()).map(|x| x as usize);
if let (Some(lo), Some(hi)) = (get("shortest_edge"), get("longest_edge")) {
cfg = cfg.with_pixel_bounds(lo, hi);
}
}
}
let st = LazySt::open(dir)?;
Self::from_st(&st, cfg)
}
fn from_st(st: &LazySt, cfg: VisionConfig) -> Result<Self> {
let p = if st.has("model.visual.pos_embed.weight") {
"model.visual"
} else {
"visual"
};
anyhow::ensure!(
st.has(&format!("{p}.pos_embed.weight")),
"no vision tower in this checkpoint (looked for {p}.pos_embed.weight)"
);
let blocks = (0..cfg.depth)
.map(|i| {
let b = format!("{p}.blocks.{i}");
Ok(Block {
norm1: Norm::load(st, &format!("{b}.norm1"))?,
qkv: Linear::load(st, &format!("{b}.attn.qkv"))?,
proj: Linear::load(st, &format!("{b}.attn.proj"))?,
norm2: Norm::load(st, &format!("{b}.norm2"))?,
fc1: Linear::load(st, &format!("{b}.mlp.linear_fc1"))?,
fc2: Linear::load(st, &format!("{b}.mlp.linear_fc2"))?,
})
})
.collect::<Result<Vec<_>>>()?;
let patch = Linear::load(st, &format!("{p}.patch_embed.proj"))?;
anyhow::ensure!(
patch.k == cfg.patch_dim() && patch.n == cfg.hidden,
"patch_embed is [{}, {}], expected [{}, {}]",
patch.n,
patch.k,
cfg.hidden,
cfg.patch_dim()
);
Ok(Self {
pos_embed: st.tensor_f32(&format!("{p}.pos_embed.weight"))?,
patch,
merger_norm: Norm::load(st, &format!("{p}.merger.norm"))?,
merger_fc1: Linear::load(st, &format!("{p}.merger.linear_fc1"))?,
merger_fc2: Linear::load(st, &format!("{p}.merger.linear_fc2"))?,
blocks,
cfg,
})
}
pub fn config(&self) -> &VisionConfig {
&self.cfg
}
pub(crate) fn interpolate_pos(&self, grid: [u32; 3]) -> Vec<f32> {
let (t, h, w) = (grid[0] as usize, grid[1] as usize, grid[2] as usize);
let (side, hid, m) = (self.cfg.grid_side, self.cfg.hidden, self.cfg.merge);
let coord = |n: usize| -> Vec<f32> {
if n == 1 {
vec![0.0]
} else {
let step = (side - 1) as f32 / (n - 1) as f32;
(0..n).map(|i| i as f32 * step).collect()
}
};
let (hc, wc) = (coord(h), coord(w));
let mut out = vec![0f32; t * h * w * hid];
out.par_chunks_mut(hid).enumerate().for_each(|(tok, dst)| {
let hw = h * w;
let within_frame = tok % hw;
let per_block = m * m;
let block = within_frame / per_block;
let within = within_frame % per_block;
let blocks_w = w / m;
let (br, bc) = (block / blocks_w, block % blocks_w);
let (r, c) = (br * m + within / m, bc * m + within % m);
let (hy, wx) = (hc[r], wc[c]);
let (h0, w0) = (hy as usize, wx as usize);
let (h1, w1) = ((h0 + 1).min(side - 1), (w0 + 1).min(side - 1));
let (hf, wf) = (hy - h0 as f32, wx - w0 as f32);
let corners = [
((h0 * side + w0), (1.0 - hf) * (1.0 - wf)),
((h0 * side + w1), (1.0 - hf) * wf),
((h1 * side + w0), hf * (1.0 - wf)),
((h1 * side + w1), hf * wf),
];
for (idx, wt) in corners {
let row = &self.pos_embed[idx * hid..(idx + 1) * hid];
for (d, v) in dst.iter_mut().zip(row) {
*d += wt * v;
}
}
});
out
}
pub(crate) fn rope_tables(&self, grid: [u32; 3]) -> (Vec<f32>, Vec<f32>) {
rope_tables_2d(&self.cfg, grid)
}
}
pub(crate) fn rope_tables_2d(cfg: &VisionConfig, grid: [u32; 3]) -> (Vec<f32>, Vec<f32>) {
{
let (t, h, w) = (grid[0] as usize, grid[1] as usize, grid[2] as usize);
let (hd, m) = (cfg.head_dim(), cfg.merge);
let half = hd / 2; let nf = half / 2; let inv: Vec<f32> = (0..nf)
.map(|i| 1.0 / cfg.rope_theta.powf((2 * i) as f32 / half as f32))
.collect();
let n = t * h * w;
let mut cos = vec![0f32; n * hd];
let mut sin = vec![0f32; n * hd];
cos.par_chunks_mut(hd)
.zip(sin.par_chunks_mut(hd))
.enumerate()
.for_each(|(tok, (co, si))| {
let hw = h * w;
let within_frame = tok % hw;
let per_block = m * m;
let block = within_frame / per_block;
let within = within_frame % per_block;
let blocks_w = w / m;
let (br, bc) = (block / blocks_w, block % blocks_w);
let (r, c) = (br * m + within / m, bc * m + within % m);
for i in 0..nf {
let (fr, fc) = (r as f32 * inv[i], c as f32 * inv[i]);
for (off, f) in [(i, fr), (nf + i, fc)] {
let (cv, sv) = (f.cos(), f.sin());
co[off] = cv;
co[off + half] = cv;
si[off] = sv;
si[off + half] = sv;
}
}
});
(cos, sin)
}
}
impl VisionTower {
pub fn forward(&self, images: &[ImagePatches]) -> Result<Vec<f32>> {
let mut out = Vec::new();
for img in images {
out.extend(self.run(img, false)?.1);
}
Ok(out)
}
pub fn debug_forward(&self, img: &ImagePatches) -> Result<(Vec<Vec<f32>>, Vec<f32>)> {
self.run(img, true)
}
fn run(&self, img: &ImagePatches, capture: bool) -> Result<(Vec<Vec<f32>>, Vec<f32>)> {
let cfg = &self.cfg;
let (hid, heads, hd) = (cfg.hidden, cfg.heads, cfg.head_dim());
let n = img.num_patches();
anyhow::ensure!(
img.patches.len() == n * cfg.patch_dim(),
"patch buffer {} does not match grid {:?}",
img.patches.len(),
img.grid
);
let mut x = vec![0f32; n * hid];
self.patch.forward(&mut x, &img.patches);
let pos = self.interpolate_pos(img.grid);
x.par_iter_mut()
.zip(pos.par_iter())
.for_each(|(a, b)| *a += b);
let (cos, sin) = self.rope_tables(img.grid);
let mut qkv = vec![0f32; n * 3 * hid];
let mut attn = vec![0f32; n * hid];
let mut mid = vec![0f32; n * cfg.intermediate];
let mut normed = vec![0f32; n * hid];
let mut states: Vec<Vec<f32>> = Vec::new();
if capture {
states.push(x.clone());
}
for blk in &self.blocks {
normed.copy_from_slice(&x);
layer_norm(&mut normed, hid, &blk.norm1, cfg.eps);
blk.qkv.forward(&mut qkv, &normed);
self.attention(&mut attn, &qkv, &cos, &sin, n, heads, hd);
blk.proj.forward(&mut normed, &attn);
x.par_iter_mut()
.zip(normed.par_iter())
.for_each(|(a, b)| *a += b);
normed.copy_from_slice(&x);
layer_norm(&mut normed, hid, &blk.norm2, cfg.eps);
blk.fc1.forward(&mut mid, &normed);
act_inplace(&mut mid, cfg.act);
blk.fc2.forward(&mut normed, &mid);
x.par_iter_mut()
.zip(normed.par_iter())
.for_each(|(a, b)| *a += b);
if capture {
states.push(x.clone());
}
}
layer_norm(&mut x, hid, &self.merger_norm, cfg.eps);
let unit = cfg.merge_unit();
let tokens = n / unit;
anyhow::ensure!(
n.is_multiple_of(unit),
"patch count {n} is not a multiple of merge² ({unit})"
);
let mut m1 = vec![0f32; tokens * hid * unit];
self.merger_fc1.forward(&mut m1, &x);
act_inplace(&mut m1, Act::GeluErf); let mut m2 = vec![0f32; tokens * cfg.out_hidden];
self.merger_fc2.forward(&mut m2, &m1);
Ok((states, m2))
}
fn attention(
&self,
out: &mut [f32],
qkv: &[f32],
cos: &[f32],
sin: &[f32],
n: usize,
heads: usize,
hd: usize,
) {
let hid = heads * hd;
let scale = 1.0 / (hd as f32).sqrt();
let half = hd / 2;
let rope = |vec: &mut [f32], tok: usize| {
let (c, s) = (
&cos[tok * hd..(tok + 1) * hd],
&sin[tok * hd..(tok + 1) * hd],
);
for h in 0..heads {
let v = &mut vec[h * hd..(h + 1) * hd];
let orig: Vec<f32> = v.to_vec();
for j in 0..hd {
let rot = if j < half {
-orig[j + half]
} else {
orig[j - half]
};
v[j] = orig[j] * c[j] + rot * s[j];
}
}
};
let mut q = vec![0f32; n * hid];
let mut k = vec![0f32; n * hid];
let mut v = vec![0f32; n * hid];
q.par_chunks_mut(hid)
.zip(k.par_chunks_mut(hid))
.zip(v.par_chunks_mut(hid))
.enumerate()
.for_each(|(tok, ((qr, kr), vr))| {
let row = &qkv[tok * 3 * hid..(tok + 1) * 3 * hid];
qr.copy_from_slice(&row[..hid]);
kr.copy_from_slice(&row[hid..2 * hid]);
vr.copy_from_slice(&row[2 * hid..]);
rope(qr, tok);
rope(kr, tok);
});
let mut heads_out: Vec<Vec<f32>> = vec![Vec::new(); heads];
heads_out.par_iter_mut().enumerate().for_each(|(h, slot)| {
let mut scores = vec![0f32; n * n];
unsafe {
gemm::gemm(
n,
n,
hd,
scores.as_mut_ptr(),
1,
n as isize,
false,
q.as_ptr().add(h * hd),
1,
hid as isize,
k.as_ptr().add(h * hd),
hid as isize,
1,
0.0,
scale,
false,
false,
false,
gemm::Parallelism::None,
);
}
for row in scores.chunks_mut(n) {
let max = row.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0.0;
for s in row.iter_mut() {
*s = (*s - max).exp();
sum += *s;
}
let inv = 1.0 / sum;
for s in row.iter_mut() {
*s *= inv;
}
}
let mut ctx = vec![0f32; n * hd];
unsafe {
gemm::gemm(
n,
hd,
n,
ctx.as_mut_ptr(),
1,
hd as isize,
false,
scores.as_ptr(),
1,
n as isize,
v.as_ptr().add(h * hd),
1,
hid as isize,
0.0,
1.0,
false,
false,
false,
gemm::Parallelism::None,
);
}
*slot = ctx;
});
for (h, ctx) in heads_out.iter().enumerate() {
for tok in 0..n {
out[tok * hid + h * hd..tok * hid + (h + 1) * hd]
.copy_from_slice(&ctx[tok * hd..(tok + 1) * hd]);
}
}
}
}