1use crate::Engine;
17use cudarc::driver::CudaSlice;
18use memra_gguf::dequant::bf16_to_f32;
19use memra_gguf::safetensors::StShard;
20use std::path::Path;
21
22pub const V_HIDDEN: usize = 1152;
23pub const V_HEADS: usize = 16;
24pub const V_HEAD_DIM: usize = V_HIDDEN / V_HEADS; pub const V_INTER: usize = 4304;
26pub const V_DEPTH: usize = 27;
27pub const V_PATCH: usize = 16;
28pub const V_MERGE: usize = 2;
29pub const V_TEMPORAL: usize = 2;
30pub const V_POS_GRID: usize = 48; pub const V_OUT: usize = 5120;
32pub const V_PATCH_IN: usize = 3 * V_TEMPORAL * V_PATCH * V_PATCH; pub const V_MERGED_IN: usize = V_HIDDEN * V_MERGE * V_MERGE; const LN_EPS: f32 = 1e-6;
35
36pub struct EmbedOverlay {
42 pub rows: CudaSlice<f32>,
43 pub spans: Vec<(usize, usize, usize)>,
44}
45
46impl EmbedOverlay {
47 pub fn window(&self, off: usize, len: usize) -> Option<EmbedOverlay> {
52 let spans: Vec<(usize, usize, usize)> = self
53 .spans
54 .iter()
55 .filter_map(|&(pos, row_off, n_rows)| {
56 let lo = pos.max(off);
57 let hi = (pos + n_rows).min(off + len);
58 (lo < hi).then(|| (lo - off, row_off + (lo - pos), hi - lo))
59 })
60 .collect();
61 (!spans.is_empty()).then(|| EmbedOverlay {
62 rows: self.rows.clone(),
63 spans,
64 })
65 }
66}
67
68struct Lin {
69 w: CudaSlice<f32>,
70 b: CudaSlice<f32>,
71 in_f: usize,
72 out_f: usize,
73}
74
75struct VisBlock {
76 norm1_w: CudaSlice<f32>,
77 norm1_b: CudaSlice<f32>,
78 norm2_w: CudaSlice<f32>,
79 norm2_b: CudaSlice<f32>,
80 qkv: Lin,
81 proj: Lin,
82 fc1: Lin,
83 fc2: Lin,
84}
85
86pub struct VisionTower {
87 patch: Lin,
88 pos: Vec<f32>,
90 blocks: Vec<VisBlock>,
91 merger_norm_w: CudaSlice<f32>,
92 merger_norm_b: CudaSlice<f32>,
93 merger_fc1: Lin,
94 merger_fc2: Lin,
95}
96
97fn read_f32(sh: &StShard, name: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
98 let (info, raw) = sh
99 .raw(name)
100 .ok_or_else(|| format!("vision tensor missing: {name}"))?;
101 match info.dtype.as_str() {
102 "BF16" => Ok(raw
103 .chunks_exact(2)
104 .map(|c| bf16_to_f32(u16::from_le_bytes([c[0], c[1]])))
105 .collect()),
106 "F32" => Ok(raw
107 .chunks_exact(4)
108 .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
109 .collect()),
110 other => Err(format!("vision tensor {name}: unsupported dtype {other}").into()),
111 }
112}
113
114fn load_lin(
115 e: &Engine,
116 sh: &StShard,
117 stem: &str,
118 in_f: usize,
119 out_f: usize,
120) -> Result<Lin, Box<dyn std::error::Error>> {
121 let w = read_f32(sh, &format!("{stem}.weight"))?;
122 let b = read_f32(sh, &format!("{stem}.bias"))?;
123 assert_eq!(w.len(), in_f * out_f, "{stem}.weight shape");
124 assert_eq!(b.len(), out_f, "{stem}.bias shape");
125 Ok(Lin {
126 w: e.htod(&w)?,
127 b: e.htod(&b)?,
128 in_f,
129 out_f,
130 })
131}
132
133impl VisionTower {
134 pub fn load(e: &Engine, dir: &Path) -> Result<Self, Box<dyn std::error::Error>> {
136 let sh = StShard::open(dir.join("outside.safetensors"))?;
137 let p = "model.visual";
138 let patch = {
139 let w = read_f32(&sh, &format!("{p}.patch_embed.proj.weight"))?;
143 let b = read_f32(&sh, &format!("{p}.patch_embed.proj.bias"))?;
144 assert_eq!(w.len(), V_HIDDEN * V_PATCH_IN);
145 Lin {
146 w: e.htod(&w)?,
147 b: e.htod(&b)?,
148 in_f: V_PATCH_IN,
149 out_f: V_HIDDEN,
150 }
151 };
152 let pos = read_f32(&sh, &format!("{p}.pos_embed.weight"))?;
153 assert_eq!(pos.len(), V_POS_GRID * V_POS_GRID * V_HIDDEN);
154 let mut blocks = Vec::with_capacity(V_DEPTH);
155 for il in 0..V_DEPTH {
156 let bp = format!("{p}.blocks.{il}");
157 blocks.push(VisBlock {
158 norm1_w: e.htod(&read_f32(&sh, &format!("{bp}.norm1.weight"))?)?,
159 norm1_b: e.htod(&read_f32(&sh, &format!("{bp}.norm1.bias"))?)?,
160 norm2_w: e.htod(&read_f32(&sh, &format!("{bp}.norm2.weight"))?)?,
161 norm2_b: e.htod(&read_f32(&sh, &format!("{bp}.norm2.bias"))?)?,
162 qkv: load_lin(e, &sh, &format!("{bp}.attn.qkv"), V_HIDDEN, 3 * V_HIDDEN)?,
163 proj: load_lin(e, &sh, &format!("{bp}.attn.proj"), V_HIDDEN, V_HIDDEN)?,
164 fc1: load_lin(e, &sh, &format!("{bp}.mlp.linear_fc1"), V_HIDDEN, V_INTER)?,
165 fc2: load_lin(e, &sh, &format!("{bp}.mlp.linear_fc2"), V_INTER, V_HIDDEN)?,
166 });
167 }
168 let merger_norm_w = e.htod(&read_f32(&sh, &format!("{p}.merger.norm.weight"))?)?;
169 let merger_norm_b = e.htod(&read_f32(&sh, &format!("{p}.merger.norm.bias"))?)?;
170 let merger_fc1 = load_lin(
171 e,
172 &sh,
173 &format!("{p}.merger.linear_fc1"),
174 V_MERGED_IN,
175 V_MERGED_IN,
176 )?;
177 let merger_fc2 = load_lin(
178 e,
179 &sh,
180 &format!("{p}.merger.linear_fc2"),
181 V_MERGED_IN,
182 V_OUT,
183 )?;
184 eprintln!(
185 "[vision] tower loaded from {} ({} blocks, f32-resident)",
186 dir.display(),
187 V_DEPTH
188 );
189 Ok(Self {
190 patch,
191 pos,
192 blocks,
193 merger_norm_w,
194 merger_norm_b,
195 merger_fc1,
196 merger_fc2,
197 })
198 }
199
200 fn linear_bias(
201 &self,
202 e: &Engine,
203 x: &CudaSlice<f32>,
204 l: &Lin,
205 m: usize,
206 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
207 let mut y = e.linear(x, &l.w, m, l.in_f, l.out_f)?;
208 for r in 0..m {
211 e.add_row_inplace(&mut y, &l.b, l.out_f, r * l.out_f)?;
212 }
213 Ok(y)
214 }
215
216 fn pos_for_grid(&self, gh: usize, gw: usize) -> Vec<f32> {
219 let g = V_POS_GRID as f32;
220 let mut out = vec![0f32; gh * gw * V_HIDDEN];
221 for y in 0..gh {
222 for x in 0..gw {
223 let sy = if gh > 1 {
225 y as f32 * (g - 1.0) / (gh as f32 - 1.0)
226 } else {
227 0.0
228 };
229 let sx = if gw > 1 {
230 x as f32 * (g - 1.0) / (gw as f32 - 1.0)
231 } else {
232 0.0
233 };
234 let (y0, x0) = (sy.floor() as usize, sx.floor() as usize);
235 let (y1, x1) = ((y0 + 1).min(V_POS_GRID - 1), (x0 + 1).min(V_POS_GRID - 1));
236 let (fy, fx) = (sy - y0 as f32, sx - x0 as f32);
237 let dst = &mut out[(y * gw + x) * V_HIDDEN..(y * gw + x + 1) * V_HIDDEN];
238 for c in 0..V_HIDDEN {
239 let p00 = self.pos[(y0 * V_POS_GRID + x0) * V_HIDDEN + c];
240 let p01 = self.pos[(y0 * V_POS_GRID + x1) * V_HIDDEN + c];
241 let p10 = self.pos[(y1 * V_POS_GRID + x0) * V_HIDDEN + c];
242 let p11 = self.pos[(y1 * V_POS_GRID + x1) * V_HIDDEN + c];
243 dst[c] = p00 * (1.0 - fy) * (1.0 - fx)
244 + p01 * (1.0 - fy) * fx
245 + p10 * fy * (1.0 - fx)
246 + p11 * fy * fx;
247 }
248 }
249 }
250 out
251 }
252
253 pub fn forward(
256 &self,
257 e: &Engine,
258 patches: &[f32],
259 gh: usize,
260 gw: usize,
261 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
262 let n = gh * gw;
263 assert_eq!(patches.len(), n * V_PATCH_IN, "patch buffer shape");
264 let xd = e.htod(patches)?;
265 let mut x = self.linear_bias(e, &xd, &self.patch, n)?;
266 let pos = self.pos_for_grid(gh, gw);
268 let pos_d = e.htod(&pos)?;
269 let mut x2 = e.zeros(n * V_HIDDEN)?;
270 e.add(&x, &pos_d, &mut x2, n * V_HIDDEN)?;
271 x = x2;
272 let dbg = std::env::var("MEMRA_VISION_DEBUG").ok();
274 let dump = |tag: &str, buf: &[f32]| {
275 if let Some(dir) = dbg.as_deref() {
276 let raw: Vec<u8> = buf.iter().flat_map(|v| v.to_le_bytes()).collect();
277 let _ = std::fs::write(format!("{dir}/rust_{tag}.bin"), raw);
278 }
279 };
280 if dbg.is_some() {
281 dump("pre_blocks", &e.dtoh(&x)?);
282 }
283 let scale = 1.0 / (V_HEAD_DIM as f32).sqrt();
284 let half = V_HEAD_DIM / 2; let quarter = half / 2; let inv_freq: Vec<f32> = (0..quarter)
290 .map(|i| 10000f32.powf(-(i as f32) / quarter as f32))
291 .collect();
292 let mut rope_cos = vec![0f32; n * half];
293 let mut rope_sin = vec![0f32; n * half];
294 for t in 0..n {
295 let (y, x) = (t / gw, t % gw);
296 for d in 0..half {
297 let f = if d < quarter {
298 y as f32 * inv_freq[d]
299 } else {
300 x as f32 * inv_freq[d - quarter]
301 };
302 rope_cos[t * half + d] = f.cos();
303 rope_sin[t * half + d] = f.sin();
304 }
305 }
306 for (ib, blk) in self.blocks.iter().enumerate() {
307 let mut h = e.zeros(n * V_HIDDEN)?;
309 e.layer_norm_bias(&x, &blk.norm1_w, &blk.norm1_b, &mut h, V_HIDDEN, n, LN_EPS)?;
310 let qkv = self.linear_bias(e, &h, &blk.qkv, n)?;
311 let qkv_h = e.dtoh(&qkv)?;
315 let mut qh = vec![0f32; n * V_HIDDEN];
316 let mut kh = vec![0f32; n * V_HIDDEN];
317 let mut vh = vec![0f32; n * V_HIDDEN];
318 for t in 0..n {
319 let row = &qkv_h[t * 3 * V_HIDDEN..(t + 1) * 3 * V_HIDDEN];
320 let dst = t * V_HIDDEN;
321 vh[dst..dst + V_HIDDEN].copy_from_slice(&row[2 * V_HIDDEN..3 * V_HIDDEN]);
322 for hd in 0..V_HEADS {
323 let o = hd * V_HEAD_DIM;
324 for d in 0..half {
326 let (c, sn) = (rope_cos[t * half + d], rope_sin[t * half + d]);
327 let (qa, qb) = (row[o + d], row[o + d + half]);
328 qh[dst + o + d] = qa * c - qb * sn;
329 qh[dst + o + d + half] = qb * c + qa * sn;
330 let (ka, kb) = (row[V_HIDDEN + o + d], row[V_HIDDEN + o + d + half]);
331 kh[dst + o + d] = ka * c - kb * sn;
332 kh[dst + o + d + half] = kb * c + ka * sn;
333 }
334 }
335 }
336 let (qd, kd, vd) = (e.htod(&qh)?, e.htod(&kh)?, e.htod(&vh)?);
337 let mut od = e.zeros(n * V_HIDDEN)?;
338 e.sdpa_naive(
339 &qd, &kd, &vd, &mut od, V_HEAD_DIM, V_HEADS, V_HEADS, n, n, scale, false,
340 )?;
341 let attn = self.linear_bias(e, &od, &blk.proj, n)?;
342 let mut xr = e.zeros(n * V_HIDDEN)?;
343 e.add(&x, &attn, &mut xr, n * V_HIDDEN)?;
344 let mut h2 = e.zeros(n * V_HIDDEN)?;
346 e.layer_norm_bias(
347 &xr,
348 &blk.norm2_w,
349 &blk.norm2_b,
350 &mut h2,
351 V_HIDDEN,
352 n,
353 LN_EPS,
354 )?;
355 let f1 = self.linear_bias(e, &h2, &blk.fc1, n)?;
356 let mut g = e.zeros(n * V_INTER)?;
357 e.gelu_tanh(&f1, &mut g, n * V_INTER)?;
358 let f2 = self.linear_bias(e, &g, &blk.fc2, n)?;
359 let mut xn = e.zeros(n * V_HIDDEN)?;
360 e.add(&xr, &f2, &mut xn, n * V_HIDDEN)?;
361 x = xn;
362 if dbg.is_some() && ib == 0 {
363 dump("blk0", &e.dtoh(&x)?);
364 }
365 }
366 if dbg.is_some() {
367 dump("post_blocks", &e.dtoh(&x)?);
368 }
369 let mut ln = e.zeros(n * V_HIDDEN)?;
371 e.layer_norm_bias(
372 &x,
373 &self.merger_norm_w,
374 &self.merger_norm_b,
375 &mut ln,
376 V_HIDDEN,
377 n,
378 LN_EPS,
379 )?;
380 let lh = e.dtoh(&ln)?;
381 let (mh, mw) = (gh / V_MERGE, gw / V_MERGE);
382 let nm = mh * mw;
383 let mut merged = vec![0f32; nm * V_MERGED_IN];
384 for my in 0..mh {
385 for mx in 0..mw {
386 let dst =
387 &mut merged[(my * mw + mx) * V_MERGED_IN..(my * mw + mx + 1) * V_MERGED_IN];
388 for sy in 0..V_MERGE {
389 for sx in 0..V_MERGE {
390 let t = (my * V_MERGE + sy) * gw + (mx * V_MERGE + sx);
391 let seg = (sy * V_MERGE + sx) * V_HIDDEN;
392 dst[seg..seg + V_HIDDEN]
393 .copy_from_slice(&lh[t * V_HIDDEN..(t + 1) * V_HIDDEN]);
394 }
395 }
396 }
397 }
398 let md = e.htod(&merged)?;
399 let f1 = self.linear_bias(e, &md, &self.merger_fc1, nm)?;
400 let mut g = e.zeros(nm * V_MERGED_IN)?;
401 e.gelu_tanh(&f1, &mut g, nm * V_MERGED_IN)?;
402 self.linear_bias(e, &g, &self.merger_fc2, nm)
403 }
404}