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 pub fn splice_into(
75 &self,
76 e: &Engine,
77 embedded: &mut CudaSlice<f32>,
78 chunk_off: usize,
79 t: usize,
80 n_embd: usize,
81 ) -> Result<(), Box<dyn std::error::Error>> {
82 for &(pos, row_off, n_rows) in &self.spans {
83 let lo = pos.max(chunk_off);
84 let hi = (pos + n_rows).min(chunk_off + t);
85 if lo < hi {
86 let src_row = row_off + (lo - pos);
87 let view = self
88 .rows
89 .slice(src_row * n_embd..(src_row + (hi - lo)) * n_embd);
90 e.copy_view_into(
91 embedded,
92 (lo - chunk_off) * n_embd,
93 &view,
94 (hi - lo) * n_embd,
95 )?;
96 }
97 }
98 Ok(())
99 }
100}
101
102struct Lin {
103 w: CudaSlice<f32>,
104 b: CudaSlice<f32>,
105 in_f: usize,
106 out_f: usize,
107}
108
109struct VisBlock {
110 norm1_w: CudaSlice<f32>,
111 norm1_b: CudaSlice<f32>,
112 norm2_w: CudaSlice<f32>,
113 norm2_b: CudaSlice<f32>,
114 qkv: Lin,
115 proj: Lin,
116 fc1: Lin,
117 fc2: Lin,
118}
119
120pub struct VisionTower {
121 patch: Lin,
122 pos: Vec<f32>,
124 blocks: Vec<VisBlock>,
125 merger_norm_w: CudaSlice<f32>,
126 merger_norm_b: CudaSlice<f32>,
127 merger_fc1: Lin,
128 merger_fc2: Lin,
129}
130
131fn read_f32(sh: &StShard, name: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
132 let (info, raw) = sh
133 .raw(name)
134 .ok_or_else(|| format!("vision tensor missing: {name}"))?;
135 match info.dtype.as_str() {
136 "BF16" => Ok(raw
137 .chunks_exact(2)
138 .map(|c| bf16_to_f32(u16::from_le_bytes([c[0], c[1]])))
139 .collect()),
140 "F32" => Ok(raw
141 .chunks_exact(4)
142 .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
143 .collect()),
144 other => Err(format!("vision tensor {name}: unsupported dtype {other}").into()),
145 }
146}
147
148fn load_lin(
149 e: &Engine,
150 sh: &StShard,
151 stem: &str,
152 in_f: usize,
153 out_f: usize,
154) -> Result<Lin, Box<dyn std::error::Error>> {
155 let w = read_f32(sh, &format!("{stem}.weight"))?;
156 let b = read_f32(sh, &format!("{stem}.bias"))?;
157 assert_eq!(w.len(), in_f * out_f, "{stem}.weight shape");
158 assert_eq!(b.len(), out_f, "{stem}.bias shape");
159 Ok(Lin {
160 w: e.htod(&w)?,
161 b: e.htod(&b)?,
162 in_f,
163 out_f,
164 })
165}
166
167impl VisionTower {
168 pub fn load(e: &Engine, dir: &Path) -> Result<Self, Box<dyn std::error::Error>> {
170 let sh = StShard::open(dir.join("outside.safetensors"))?;
171 let p = "model.visual";
172 let patch = {
173 let w = read_f32(&sh, &format!("{p}.patch_embed.proj.weight"))?;
177 let b = read_f32(&sh, &format!("{p}.patch_embed.proj.bias"))?;
178 assert_eq!(w.len(), V_HIDDEN * V_PATCH_IN);
179 Lin {
180 w: e.htod(&w)?,
181 b: e.htod(&b)?,
182 in_f: V_PATCH_IN,
183 out_f: V_HIDDEN,
184 }
185 };
186 let pos = read_f32(&sh, &format!("{p}.pos_embed.weight"))?;
187 assert_eq!(pos.len(), V_POS_GRID * V_POS_GRID * V_HIDDEN);
188 let mut blocks = Vec::with_capacity(V_DEPTH);
189 for il in 0..V_DEPTH {
190 let bp = format!("{p}.blocks.{il}");
191 blocks.push(VisBlock {
192 norm1_w: e.htod(&read_f32(&sh, &format!("{bp}.norm1.weight"))?)?,
193 norm1_b: e.htod(&read_f32(&sh, &format!("{bp}.norm1.bias"))?)?,
194 norm2_w: e.htod(&read_f32(&sh, &format!("{bp}.norm2.weight"))?)?,
195 norm2_b: e.htod(&read_f32(&sh, &format!("{bp}.norm2.bias"))?)?,
196 qkv: load_lin(e, &sh, &format!("{bp}.attn.qkv"), V_HIDDEN, 3 * V_HIDDEN)?,
197 proj: load_lin(e, &sh, &format!("{bp}.attn.proj"), V_HIDDEN, V_HIDDEN)?,
198 fc1: load_lin(e, &sh, &format!("{bp}.mlp.linear_fc1"), V_HIDDEN, V_INTER)?,
199 fc2: load_lin(e, &sh, &format!("{bp}.mlp.linear_fc2"), V_INTER, V_HIDDEN)?,
200 });
201 }
202 let merger_norm_w = e.htod(&read_f32(&sh, &format!("{p}.merger.norm.weight"))?)?;
203 let merger_norm_b = e.htod(&read_f32(&sh, &format!("{p}.merger.norm.bias"))?)?;
204 let merger_fc1 = load_lin(
205 e,
206 &sh,
207 &format!("{p}.merger.linear_fc1"),
208 V_MERGED_IN,
209 V_MERGED_IN,
210 )?;
211 let merger_fc2 = {
212 let w = read_f32(&sh, &format!("{p}.merger.linear_fc2.weight"))?;
216 let b = read_f32(&sh, &format!("{p}.merger.linear_fc2.bias"))?;
217 assert_eq!(w.len() % V_MERGED_IN, 0, "merger.linear_fc2.weight shape");
218 let out_f = w.len() / V_MERGED_IN;
219 assert_eq!(b.len(), out_f, "merger.linear_fc2.bias shape");
220 Lin {
221 w: e.htod(&w)?,
222 b: e.htod(&b)?,
223 in_f: V_MERGED_IN,
224 out_f,
225 }
226 };
227 eprintln!(
228 "[vision] tower loaded from {} ({} blocks, out_width {}, f32-resident)",
229 dir.display(),
230 V_DEPTH,
231 merger_fc2.out_f
232 );
233 Ok(Self {
234 patch,
235 pos,
236 blocks,
237 merger_norm_w,
238 merger_norm_b,
239 merger_fc1,
240 merger_fc2,
241 })
242 }
243
244 pub fn out_width(&self) -> usize {
247 self.merger_fc2.out_f
248 }
249
250 fn linear_bias(
251 &self,
252 e: &Engine,
253 x: &CudaSlice<f32>,
254 l: &Lin,
255 m: usize,
256 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
257 let mut y = e.linear(x, &l.w, m, l.in_f, l.out_f)?;
258 for r in 0..m {
261 e.add_row_inplace(&mut y, &l.b, l.out_f, r * l.out_f)?;
262 }
263 Ok(y)
264 }
265
266 fn pos_for_grid(&self, gh: usize, gw: usize) -> Vec<f32> {
269 let g = V_POS_GRID as f32;
270 let mut out = vec![0f32; gh * gw * V_HIDDEN];
271 for y in 0..gh {
272 for x in 0..gw {
273 let sy = if gh > 1 {
275 y as f32 * (g - 1.0) / (gh as f32 - 1.0)
276 } else {
277 0.0
278 };
279 let sx = if gw > 1 {
280 x as f32 * (g - 1.0) / (gw as f32 - 1.0)
281 } else {
282 0.0
283 };
284 let (y0, x0) = (sy.floor() as usize, sx.floor() as usize);
285 let (y1, x1) = ((y0 + 1).min(V_POS_GRID - 1), (x0 + 1).min(V_POS_GRID - 1));
286 let (fy, fx) = (sy - y0 as f32, sx - x0 as f32);
287 let dst = &mut out[(y * gw + x) * V_HIDDEN..(y * gw + x + 1) * V_HIDDEN];
288 #[allow(clippy::needless_range_loop)]
289 for c in 0..V_HIDDEN {
291 let p00 = self.pos[(y0 * V_POS_GRID + x0) * V_HIDDEN + c];
292 let p01 = self.pos[(y0 * V_POS_GRID + x1) * V_HIDDEN + c];
293 let p10 = self.pos[(y1 * V_POS_GRID + x0) * V_HIDDEN + c];
294 let p11 = self.pos[(y1 * V_POS_GRID + x1) * V_HIDDEN + c];
295 dst[c] = p00 * (1.0 - fy) * (1.0 - fx)
296 + p01 * (1.0 - fy) * fx
297 + p10 * fy * (1.0 - fx)
298 + p11 * fy * fx;
299 }
300 }
301 }
302 out
303 }
304
305 pub fn forward(
308 &self,
309 e: &Engine,
310 patches: &[f32],
311 gh: usize,
312 gw: usize,
313 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
314 self.forward_seq(e, patches, 1, gh, gw)
315 }
316
317 pub fn forward_seq(
326 &self,
327 e: &Engine,
328 patches: &[f32],
329 groups: usize,
330 gh: usize,
331 gw: usize,
332 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
333 let n = groups * gh * gw;
334 assert_eq!(patches.len(), n * V_PATCH_IN, "patch buffer shape");
335 if groups > 1 {
336 let frame = gh * gw;
337 let out_per = frame / (V_MERGE * V_MERGE) * self.merger_fc2.out_f;
338 let mut out = e.uninit(groups * out_per)?;
339 for g in 0..groups {
340 let emb = self.forward_one(
341 e,
342 &patches[g * frame * V_PATCH_IN..(g + 1) * frame * V_PATCH_IN],
343 gh,
344 gw,
345 )?;
346 e.dtod_copy_into(&emb, &mut out, g * out_per)?;
347 }
348 return Ok(out);
349 }
350 self.forward_one(e, patches, gh, gw)
351 }
352
353 fn forward_one(
355 &self,
356 e: &Engine,
357 patches: &[f32],
358 gh: usize,
359 gw: usize,
360 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
361 let groups = 1usize;
362 let n = gh * gw;
363 assert_eq!(patches.len(), n * V_PATCH_IN, "patch buffer shape");
364 if n > 12288 {
365 return Err(format!(
366 "vision segment {n} patches exceeds the sdpa shared-memory ceiling (12288); \
367 lower the pixel budget"
368 )
369 .into());
370 }
371 let xd = e.htod(patches)?;
372 let mut x = self.linear_bias(e, &xd, &self.patch, n)?;
373 let pos_one = self.pos_for_grid(gh, gw);
375 let mut pos = Vec::with_capacity(n * V_HIDDEN);
376 for _ in 0..groups {
377 pos.extend_from_slice(&pos_one);
378 }
379 let pos_d = e.htod(&pos)?;
380 let mut x2 = e.zeros(n * V_HIDDEN)?;
381 e.add(&x, &pos_d, &mut x2, n * V_HIDDEN)?;
382 x = x2;
383 let dbg = std::env::var("MEMRA_VISION_DEBUG").ok();
385 let dump = |tag: &str, buf: &[f32]| {
386 if let Some(dir) = dbg.as_deref() {
387 let raw: Vec<u8> = buf.iter().flat_map(|v| v.to_le_bytes()).collect();
388 let _ = std::fs::write(format!("{dir}/rust_{tag}.bin"), raw);
389 }
390 };
391 if dbg.is_some() {
392 dump("pre_blocks", &e.dtoh(&x)?);
393 }
394 let scale = 1.0 / (V_HEAD_DIM as f32).sqrt();
395 let half = V_HEAD_DIM / 2; let quarter = half / 2; let inv_freq: Vec<f32> = (0..quarter)
401 .map(|i| 10000f32.powf(-(i as f32) / quarter as f32))
402 .collect();
403 let mut rope_cos = vec![0f32; n * half];
404 let mut rope_sin = vec![0f32; n * half];
405 for t in 0..n {
406 let f = t % (gh * gw); let (y, x) = (f / gw, f % gw);
408 for d in 0..half {
409 let f = if d < quarter {
410 y as f32 * inv_freq[d]
411 } else {
412 x as f32 * inv_freq[d - quarter]
413 };
414 rope_cos[t * half + d] = f.cos();
415 rope_sin[t * half + d] = f.sin();
416 }
417 }
418 for (ib, blk) in self.blocks.iter().enumerate() {
419 let mut h = e.zeros(n * V_HIDDEN)?;
421 e.layer_norm_bias(&x, &blk.norm1_w, &blk.norm1_b, &mut h, V_HIDDEN, n, LN_EPS)?;
422 let qkv = self.linear_bias(e, &h, &blk.qkv, n)?;
423 let qkv_h = e.dtoh(&qkv)?;
427 let mut qh = vec![0f32; n * V_HIDDEN];
428 let mut kh = vec![0f32; n * V_HIDDEN];
429 let mut vh = vec![0f32; n * V_HIDDEN];
430 for t in 0..n {
431 let row = &qkv_h[t * 3 * V_HIDDEN..(t + 1) * 3 * V_HIDDEN];
432 let dst = t * V_HIDDEN;
433 vh[dst..dst + V_HIDDEN].copy_from_slice(&row[2 * V_HIDDEN..3 * V_HIDDEN]);
434 for hd in 0..V_HEADS {
435 let o = hd * V_HEAD_DIM;
436 for d in 0..half {
438 let (c, sn) = (rope_cos[t * half + d], rope_sin[t * half + d]);
439 let (qa, qb) = (row[o + d], row[o + d + half]);
440 qh[dst + o + d] = qa * c - qb * sn;
441 qh[dst + o + d + half] = qb * c + qa * sn;
442 let (ka, kb) = (row[V_HIDDEN + o + d], row[V_HIDDEN + o + d + half]);
443 kh[dst + o + d] = ka * c - kb * sn;
444 kh[dst + o + d + half] = kb * c + ka * sn;
445 }
446 }
447 }
448 let (qd, kd, vd) = (e.htod(&qh)?, e.htod(&kh)?, e.htod(&vh)?);
449 let mut od = e.zeros(n * V_HIDDEN)?;
450 e.sdpa_naive(
451 &qd, &kd, &vd, &mut od, V_HEAD_DIM, V_HEADS, V_HEADS, n, n, scale, false,
452 )?;
453 let attn = self.linear_bias(e, &od, &blk.proj, n)?;
454 let mut xr = e.zeros(n * V_HIDDEN)?;
455 e.add(&x, &attn, &mut xr, n * V_HIDDEN)?;
456 let mut h2 = e.zeros(n * V_HIDDEN)?;
458 e.layer_norm_bias(
459 &xr,
460 &blk.norm2_w,
461 &blk.norm2_b,
462 &mut h2,
463 V_HIDDEN,
464 n,
465 LN_EPS,
466 )?;
467 let f1 = self.linear_bias(e, &h2, &blk.fc1, n)?;
468 let mut g = e.zeros(n * V_INTER)?;
469 e.gelu_tanh(&f1, &mut g, n * V_INTER)?;
470 let f2 = self.linear_bias(e, &g, &blk.fc2, n)?;
471 let mut xn = e.zeros(n * V_HIDDEN)?;
472 e.add(&xr, &f2, &mut xn, n * V_HIDDEN)?;
473 x = xn;
474 if dbg.is_some() && ib == 0 {
475 dump("blk0", &e.dtoh(&x)?);
476 }
477 }
478 if dbg.is_some() {
479 dump("post_blocks", &e.dtoh(&x)?);
480 }
481 let mut ln = e.zeros(n * V_HIDDEN)?;
483 e.layer_norm_bias(
484 &x,
485 &self.merger_norm_w,
486 &self.merger_norm_b,
487 &mut ln,
488 V_HIDDEN,
489 n,
490 LN_EPS,
491 )?;
492 let lh = e.dtoh(&ln)?;
493 let (mh, mw) = (gh / V_MERGE, gw / V_MERGE);
494 let nm = groups * mh * mw;
495 let mut merged = vec![0f32; nm * V_MERGED_IN];
496 for g in 0..groups {
497 for my in 0..mh {
498 for mx in 0..mw {
499 let out_t = (g * mh + my) * mw + mx;
500 let dst = &mut merged[out_t * V_MERGED_IN..(out_t + 1) * V_MERGED_IN];
501 for sy in 0..V_MERGE {
502 for sx in 0..V_MERGE {
503 let t = g * gh * gw + (my * V_MERGE + sy) * gw + (mx * V_MERGE + sx);
504 let seg = (sy * V_MERGE + sx) * V_HIDDEN;
505 dst[seg..seg + V_HIDDEN]
506 .copy_from_slice(&lh[t * V_HIDDEN..(t + 1) * V_HIDDEN]);
507 }
508 }
509 }
510 }
511 }
512 let md = e.htod(&merged)?;
513 let f1 = self.linear_bias(e, &md, &self.merger_fc1, nm)?;
514 let mut g = e.zeros(nm * V_MERGED_IN)?;
515 e.gelu_tanh(&f1, &mut g, nm * V_MERGED_IN)?;
516 self.linear_bias(e, &g, &self.merger_fc2, nm)
517 }
518}