1use crate::Engine;
19use cudarc::driver::CudaSlice;
20use memra_gguf::dequant::bf16_to_f32;
21use memra_gguf::safetensors::StShard;
22use std::path::Path;
23
24pub const V_HIDDEN: usize = 1152;
25pub const V_HEADS: usize = 16;
26pub const V_HEAD_DIM: usize = V_HIDDEN / V_HEADS; pub const V_INTER: usize = 4304;
28pub const V_DEPTH: usize = 27;
29pub const V_PATCH: usize = 16;
30pub const V_MERGE: usize = 2;
31pub const V_TEMPORAL: usize = 2;
32pub const V_POS_GRID: usize = 48; pub 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;
36
37pub struct EmbedOverlay {
43 pub rows: CudaSlice<f32>,
44 pub spans: Vec<(usize, usize, usize)>,
45}
46
47impl EmbedOverlay {
48 pub fn window(&self, off: usize, len: usize) -> Option<EmbedOverlay> {
53 let spans: Vec<(usize, usize, usize)> = self
54 .spans
55 .iter()
56 .filter_map(|&(pos, row_off, n_rows)| {
57 let lo = pos.max(off);
58 let hi = (pos + n_rows).min(off + len);
59 (lo < hi).then(|| (lo - off, row_off + (lo - pos), hi - lo))
60 })
61 .collect();
62 (!spans.is_empty()).then(|| EmbedOverlay {
63 rows: self.rows.clone(),
64 spans,
65 })
66 }
67}
68
69struct Lin {
70 w: CudaSlice<f32>,
71 b: CudaSlice<f32>,
72 in_f: usize,
73 out_f: usize,
74}
75
76struct VisBlock {
77 norm1_w: CudaSlice<f32>,
78 norm1_b: CudaSlice<f32>,
79 norm2_w: CudaSlice<f32>,
80 norm2_b: CudaSlice<f32>,
81 qkv: Lin,
82 proj: Lin,
83 fc1: Lin,
84 fc2: Lin,
85}
86
87pub struct VisionTower {
88 patch: Lin,
89 pos: Vec<f32>,
91 blocks: Vec<VisBlock>,
92 merger_norm_w: CudaSlice<f32>,
93 merger_norm_b: CudaSlice<f32>,
94 merger_fc1: Lin,
95 merger_fc2: Lin,
96}
97
98fn read_f32(sh: &StShard, name: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
99 let (info, raw) = sh
100 .raw(name)
101 .ok_or_else(|| format!("vision tensor missing: {name}"))?;
102 match info.dtype.as_str() {
103 "BF16" => Ok(raw
104 .chunks_exact(2)
105 .map(|c| bf16_to_f32(u16::from_le_bytes([c[0], c[1]])))
106 .collect()),
107 "F32" => Ok(raw
108 .chunks_exact(4)
109 .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
110 .collect()),
111 other => Err(format!("vision tensor {name}: unsupported dtype {other}").into()),
112 }
113}
114
115fn load_lin(
116 e: &Engine,
117 sh: &StShard,
118 stem: &str,
119 in_f: usize,
120 out_f: usize,
121) -> Result<Lin, Box<dyn std::error::Error>> {
122 let w = read_f32(sh, &format!("{stem}.weight"))?;
123 let b = read_f32(sh, &format!("{stem}.bias"))?;
124 assert_eq!(w.len(), in_f * out_f, "{stem}.weight shape");
125 assert_eq!(b.len(), out_f, "{stem}.bias shape");
126 Ok(Lin {
127 w: e.htod(&w)?,
128 b: e.htod(&b)?,
129 in_f,
130 out_f,
131 })
132}
133
134impl VisionTower {
135 pub fn load(e: &Engine, dir: &Path) -> Result<Self, Box<dyn std::error::Error>> {
137 let sh = StShard::open(dir.join("outside.safetensors"))?;
138 let p = "model.visual";
139 let patch = {
140 let w = read_f32(&sh, &format!("{p}.patch_embed.proj.weight"))?;
144 let b = read_f32(&sh, &format!("{p}.patch_embed.proj.bias"))?;
145 assert_eq!(w.len(), V_HIDDEN * V_PATCH_IN);
146 Lin {
147 w: e.htod(&w)?,
148 b: e.htod(&b)?,
149 in_f: V_PATCH_IN,
150 out_f: V_HIDDEN,
151 }
152 };
153 let pos = read_f32(&sh, &format!("{p}.pos_embed.weight"))?;
154 assert_eq!(pos.len(), V_POS_GRID * V_POS_GRID * V_HIDDEN);
155 let mut blocks = Vec::with_capacity(V_DEPTH);
156 for il in 0..V_DEPTH {
157 let bp = format!("{p}.blocks.{il}");
158 blocks.push(VisBlock {
159 norm1_w: e.htod(&read_f32(&sh, &format!("{bp}.norm1.weight"))?)?,
160 norm1_b: e.htod(&read_f32(&sh, &format!("{bp}.norm1.bias"))?)?,
161 norm2_w: e.htod(&read_f32(&sh, &format!("{bp}.norm2.weight"))?)?,
162 norm2_b: e.htod(&read_f32(&sh, &format!("{bp}.norm2.bias"))?)?,
163 qkv: load_lin(e, &sh, &format!("{bp}.attn.qkv"), V_HIDDEN, 3 * V_HIDDEN)?,
164 proj: load_lin(e, &sh, &format!("{bp}.attn.proj"), V_HIDDEN, V_HIDDEN)?,
165 fc1: load_lin(e, &sh, &format!("{bp}.mlp.linear_fc1"), V_HIDDEN, V_INTER)?,
166 fc2: load_lin(e, &sh, &format!("{bp}.mlp.linear_fc2"), V_INTER, V_HIDDEN)?,
167 });
168 }
169 let merger_norm_w = e.htod(&read_f32(&sh, &format!("{p}.merger.norm.weight"))?)?;
170 let merger_norm_b = e.htod(&read_f32(&sh, &format!("{p}.merger.norm.bias"))?)?;
171 let merger_fc1 = load_lin(
172 e,
173 &sh,
174 &format!("{p}.merger.linear_fc1"),
175 V_MERGED_IN,
176 V_MERGED_IN,
177 )?;
178 let merger_fc2 = {
179 let w = read_f32(&sh, &format!("{p}.merger.linear_fc2.weight"))?;
183 let b = read_f32(&sh, &format!("{p}.merger.linear_fc2.bias"))?;
184 assert_eq!(w.len() % V_MERGED_IN, 0, "merger.linear_fc2.weight shape");
185 let out_f = w.len() / V_MERGED_IN;
186 assert_eq!(b.len(), out_f, "merger.linear_fc2.bias shape");
187 Lin {
188 w: e.htod(&w)?,
189 b: e.htod(&b)?,
190 in_f: V_MERGED_IN,
191 out_f,
192 }
193 };
194 eprintln!(
195 "[vision] tower loaded from {} ({} blocks, out_width {}, f32-resident)",
196 dir.display(),
197 V_DEPTH,
198 merger_fc2.out_f
199 );
200 Ok(Self {
201 patch,
202 pos,
203 blocks,
204 merger_norm_w,
205 merger_norm_b,
206 merger_fc1,
207 merger_fc2,
208 })
209 }
210
211 pub fn out_width(&self) -> usize {
214 self.merger_fc2.out_f
215 }
216
217 fn linear_bias(
218 &self,
219 e: &Engine,
220 x: &CudaSlice<f32>,
221 l: &Lin,
222 m: usize,
223 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
224 let mut y = e.linear(x, &l.w, m, l.in_f, l.out_f)?;
225 for r in 0..m {
228 e.add_row_inplace(&mut y, &l.b, l.out_f, r * l.out_f)?;
229 }
230 Ok(y)
231 }
232
233 fn pos_for_grid(&self, gh: usize, gw: usize) -> Vec<f32> {
236 let g = V_POS_GRID as f32;
237 let mut out = vec![0f32; gh * gw * V_HIDDEN];
238 for y in 0..gh {
239 for x in 0..gw {
240 let sy = if gh > 1 {
242 y as f32 * (g - 1.0) / (gh as f32 - 1.0)
243 } else {
244 0.0
245 };
246 let sx = if gw > 1 {
247 x as f32 * (g - 1.0) / (gw as f32 - 1.0)
248 } else {
249 0.0
250 };
251 let (y0, x0) = (sy.floor() as usize, sx.floor() as usize);
252 let (y1, x1) = ((y0 + 1).min(V_POS_GRID - 1), (x0 + 1).min(V_POS_GRID - 1));
253 let (fy, fx) = (sy - y0 as f32, sx - x0 as f32);
254 let dst = &mut out[(y * gw + x) * V_HIDDEN..(y * gw + x + 1) * V_HIDDEN];
255 for c in 0..V_HIDDEN {
256 let p00 = self.pos[(y0 * V_POS_GRID + x0) * V_HIDDEN + c];
257 let p01 = self.pos[(y0 * V_POS_GRID + x1) * V_HIDDEN + c];
258 let p10 = self.pos[(y1 * V_POS_GRID + x0) * V_HIDDEN + c];
259 let p11 = self.pos[(y1 * V_POS_GRID + x1) * V_HIDDEN + c];
260 dst[c] = p00 * (1.0 - fy) * (1.0 - fx)
261 + p01 * (1.0 - fy) * fx
262 + p10 * fy * (1.0 - fx)
263 + p11 * fy * fx;
264 }
265 }
266 }
267 out
268 }
269
270 pub fn forward(
273 &self,
274 e: &Engine,
275 patches: &[f32],
276 gh: usize,
277 gw: usize,
278 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
279 self.forward_seq(e, patches, 1, gh, gw)
280 }
281
282 pub fn forward_seq(
291 &self,
292 e: &Engine,
293 patches: &[f32],
294 groups: usize,
295 gh: usize,
296 gw: usize,
297 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
298 let n = groups * gh * gw;
299 assert_eq!(patches.len(), n * V_PATCH_IN, "patch buffer shape");
300 if groups > 1 {
301 let frame = gh * gw;
302 let out_per = frame / (V_MERGE * V_MERGE) * self.merger_fc2.out_f;
303 let mut out = e.uninit(groups * out_per)?;
304 for g in 0..groups {
305 let emb = self.forward_one(
306 e,
307 &patches[g * frame * V_PATCH_IN..(g + 1) * frame * V_PATCH_IN],
308 gh,
309 gw,
310 )?;
311 e.dtod_copy_into(&emb, &mut out, g * out_per)?;
312 }
313 return Ok(out);
314 }
315 self.forward_one(e, patches, gh, gw)
316 }
317
318 fn forward_one(
320 &self,
321 e: &Engine,
322 patches: &[f32],
323 gh: usize,
324 gw: usize,
325 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
326 let groups = 1usize;
327 let n = gh * gw;
328 assert_eq!(patches.len(), n * V_PATCH_IN, "patch buffer shape");
329 if n > 12288 {
330 return Err(format!(
331 "vision segment {n} patches exceeds the sdpa shared-memory ceiling (12288); \
332 lower the pixel budget"
333 )
334 .into());
335 }
336 let xd = e.htod(patches)?;
337 let mut x = self.linear_bias(e, &xd, &self.patch, n)?;
338 let pos_one = self.pos_for_grid(gh, gw);
340 let mut pos = Vec::with_capacity(n * V_HIDDEN);
341 for _ in 0..groups {
342 pos.extend_from_slice(&pos_one);
343 }
344 let pos_d = e.htod(&pos)?;
345 let mut x2 = e.zeros(n * V_HIDDEN)?;
346 e.add(&x, &pos_d, &mut x2, n * V_HIDDEN)?;
347 x = x2;
348 let dbg = std::env::var("MEMRA_VISION_DEBUG").ok();
350 let dump = |tag: &str, buf: &[f32]| {
351 if let Some(dir) = dbg.as_deref() {
352 let raw: Vec<u8> = buf.iter().flat_map(|v| v.to_le_bytes()).collect();
353 let _ = std::fs::write(format!("{dir}/rust_{tag}.bin"), raw);
354 }
355 };
356 if dbg.is_some() {
357 dump("pre_blocks", &e.dtoh(&x)?);
358 }
359 let scale = 1.0 / (V_HEAD_DIM as f32).sqrt();
360 let half = V_HEAD_DIM / 2; let quarter = half / 2; let inv_freq: Vec<f32> = (0..quarter)
366 .map(|i| 10000f32.powf(-(i as f32) / quarter as f32))
367 .collect();
368 let mut rope_cos = vec![0f32; n * half];
369 let mut rope_sin = vec![0f32; n * half];
370 for t in 0..n {
371 let f = t % (gh * gw); let (y, x) = (f / gw, f % gw);
373 for d in 0..half {
374 let f = if d < quarter {
375 y as f32 * inv_freq[d]
376 } else {
377 x as f32 * inv_freq[d - quarter]
378 };
379 rope_cos[t * half + d] = f.cos();
380 rope_sin[t * half + d] = f.sin();
381 }
382 }
383 for (ib, blk) in self.blocks.iter().enumerate() {
384 let mut h = e.zeros(n * V_HIDDEN)?;
386 e.layer_norm_bias(&x, &blk.norm1_w, &blk.norm1_b, &mut h, V_HIDDEN, n, LN_EPS)?;
387 let qkv = self.linear_bias(e, &h, &blk.qkv, n)?;
388 let qkv_h = e.dtoh(&qkv)?;
392 let mut qh = vec![0f32; n * V_HIDDEN];
393 let mut kh = vec![0f32; n * V_HIDDEN];
394 let mut vh = vec![0f32; n * V_HIDDEN];
395 for t in 0..n {
396 let row = &qkv_h[t * 3 * V_HIDDEN..(t + 1) * 3 * V_HIDDEN];
397 let dst = t * V_HIDDEN;
398 vh[dst..dst + V_HIDDEN].copy_from_slice(&row[2 * V_HIDDEN..3 * V_HIDDEN]);
399 for hd in 0..V_HEADS {
400 let o = hd * V_HEAD_DIM;
401 for d in 0..half {
403 let (c, sn) = (rope_cos[t * half + d], rope_sin[t * half + d]);
404 let (qa, qb) = (row[o + d], row[o + d + half]);
405 qh[dst + o + d] = qa * c - qb * sn;
406 qh[dst + o + d + half] = qb * c + qa * sn;
407 let (ka, kb) = (row[V_HIDDEN + o + d], row[V_HIDDEN + o + d + half]);
408 kh[dst + o + d] = ka * c - kb * sn;
409 kh[dst + o + d + half] = kb * c + ka * sn;
410 }
411 }
412 }
413 let (qd, kd, vd) = (e.htod(&qh)?, e.htod(&kh)?, e.htod(&vh)?);
414 let mut od = e.zeros(n * V_HIDDEN)?;
415 e.sdpa_naive(
416 &qd, &kd, &vd, &mut od, V_HEAD_DIM, V_HEADS, V_HEADS, n, n, scale, false,
417 )?;
418 let attn = self.linear_bias(e, &od, &blk.proj, n)?;
419 let mut xr = e.zeros(n * V_HIDDEN)?;
420 e.add(&x, &attn, &mut xr, n * V_HIDDEN)?;
421 let mut h2 = e.zeros(n * V_HIDDEN)?;
423 e.layer_norm_bias(
424 &xr,
425 &blk.norm2_w,
426 &blk.norm2_b,
427 &mut h2,
428 V_HIDDEN,
429 n,
430 LN_EPS,
431 )?;
432 let f1 = self.linear_bias(e, &h2, &blk.fc1, n)?;
433 let mut g = e.zeros(n * V_INTER)?;
434 e.gelu_tanh(&f1, &mut g, n * V_INTER)?;
435 let f2 = self.linear_bias(e, &g, &blk.fc2, n)?;
436 let mut xn = e.zeros(n * V_HIDDEN)?;
437 e.add(&xr, &f2, &mut xn, n * V_HIDDEN)?;
438 x = xn;
439 if dbg.is_some() && ib == 0 {
440 dump("blk0", &e.dtoh(&x)?);
441 }
442 }
443 if dbg.is_some() {
444 dump("post_blocks", &e.dtoh(&x)?);
445 }
446 let mut ln = e.zeros(n * V_HIDDEN)?;
448 e.layer_norm_bias(
449 &x,
450 &self.merger_norm_w,
451 &self.merger_norm_b,
452 &mut ln,
453 V_HIDDEN,
454 n,
455 LN_EPS,
456 )?;
457 let lh = e.dtoh(&ln)?;
458 let (mh, mw) = (gh / V_MERGE, gw / V_MERGE);
459 let nm = groups * mh * mw;
460 let mut merged = vec![0f32; nm * V_MERGED_IN];
461 for g in 0..groups {
462 for my in 0..mh {
463 for mx in 0..mw {
464 let out_t = (g * mh + my) * mw + mx;
465 let dst = &mut merged[out_t * V_MERGED_IN..(out_t + 1) * V_MERGED_IN];
466 for sy in 0..V_MERGE {
467 for sx in 0..V_MERGE {
468 let t = g * gh * gw + (my * V_MERGE + sy) * gw + (mx * V_MERGE + sx);
469 let seg = (sy * V_MERGE + sx) * V_HIDDEN;
470 dst[seg..seg + V_HIDDEN]
471 .copy_from_slice(&lh[t * V_HIDDEN..(t + 1) * V_HIDDEN]);
472 }
473 }
474 }
475 }
476 }
477 let md = e.htod(&merged)?;
478 let f1 = self.linear_bias(e, &md, &self.merger_fc1, nm)?;
479 let mut g = e.zeros(nm * V_MERGED_IN)?;
480 e.gelu_tanh(&f1, &mut g, nm * V_MERGED_IN)?;
481 self.linear_bias(e, &g, &self.merger_fc2, nm)
482 }
483}