1use crate::Engine;
45use cudarc::driver::CudaSlice;
46use memra_gguf::dequant::bf16_to_f32;
47use memra_gguf::safetensors::StModel;
48use std::path::Path;
49
50pub const SV_HIDDEN: usize = 1536;
51pub const SV_HEADS: usize = 16;
52pub const SV_HEAD_DIM: usize = SV_HIDDEN / SV_HEADS; pub const SV_INTER: usize = 8960; pub const SV_DEPTH: usize = 47;
55pub const SV_PATCH: usize = 14;
56pub const SV_POS_GRID: usize = 52; pub const SV_PATCH_IN: usize = 3 * SV_PATCH * SV_PATCH; pub const SV_IMAGE_SIZE: usize = 728;
60pub const SV_TILE_SIZE: usize = 504;
61pub const SV_GRID_MAIN: usize = SV_IMAGE_SIZE / SV_PATCH; pub const SV_GRID_TILE: usize = SV_TILE_SIZE / SV_PATCH; pub const SV_MAIN_ROWS: usize = 169;
65pub const SV_TILE_ROWS: usize = 81;
66pub const SV_MAX_IMAGE_SIZE: usize = 3024;
68const LN_EPS: f32 = 1e-5;
69const ROPE_THETA: f32 = 10000.0;
70const MEAN: [f32; 3] = [0.481_454_66, 0.457_827_5, 0.408_210_73];
72const STD: [f32; 3] = [0.268_629_54, 0.261_302_6, 0.275_777_1];
73
74struct Lin {
75 w: CudaSlice<f32>,
76 b: Option<CudaSlice<f32>>,
77 in_f: usize,
78 out_f: usize,
79}
80
81struct SBlock {
82 ln1_w: CudaSlice<f32>,
83 ln1_b: CudaSlice<f32>,
84 ln2_w: CudaSlice<f32>,
85 ln2_b: CudaSlice<f32>,
86 ls1: Vec<f32>,
87 ls2: Vec<f32>,
88 qkv: Lin,
89 proj: Lin,
90 fc: Lin,
91 cproj: Lin,
92}
93
94struct Conv3x3s2 {
97 w: CudaSlice<f32>, b: CudaSlice<f32>,
99 c_in: usize,
100 c_out: usize,
101}
102
103pub struct StepVisionTower {
104 patch: Lin, pos: Vec<f32>,
107 ln_pre_w: CudaSlice<f32>,
108 ln_pre_b: CudaSlice<f32>,
109 blocks: Vec<SBlock>,
110 down1: Conv3x3s2, down2: Conv3x3s2, proj: Lin, }
114
115fn read_f32(m: &StModel, name: &str) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
116 let (info, raw) = m
117 .raw(name)
118 .ok_or_else(|| format!("step vision tensor missing: {name}"))?;
119 match info.dtype.as_str() {
120 "BF16" => Ok(raw
121 .chunks_exact(2)
122 .map(|c| bf16_to_f32(u16::from_le_bytes([c[0], c[1]])))
123 .collect()),
124 "F32" => Ok(raw
125 .chunks_exact(4)
126 .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
127 .collect()),
128 other => Err(format!("step vision tensor {name}: unsupported dtype {other}").into()),
129 }
130}
131
132fn load_lin(
133 e: &Engine,
134 m: &StModel,
135 stem: &str,
136 in_f: usize,
137 out_f: usize,
138 bias: bool,
139) -> Result<Lin, Box<dyn std::error::Error>> {
140 let w = read_f32(m, &format!("{stem}.weight"))?;
141 assert_eq!(w.len(), in_f * out_f, "{stem}.weight shape");
142 let b = if bias {
143 let b = read_f32(m, &format!("{stem}.bias"))?;
144 assert_eq!(b.len(), out_f, "{stem}.bias shape");
145 Some(e.htod(&b)?)
146 } else {
147 None
148 };
149 Ok(Lin {
150 w: e.htod(&w)?,
151 b,
152 in_f,
153 out_f,
154 })
155}
156
157fn window_size(long: usize, short: usize) -> usize {
161 if long <= SV_IMAGE_SIZE {
162 if long as f64 / short as f64 > 1.5 {
163 short
164 } else {
165 0
166 }
167 } else if long as f64 / short as f64 > 4.0 {
168 short.min(SV_TILE_SIZE)
169 } else {
170 SV_TILE_SIZE
171 }
172}
173
174fn pad_rule(w: usize, h: usize) -> (usize, usize) {
176 let ratio = w as f64 / h as f64;
177 if w.min(h) < 32 && !(0.25..=4.0).contains(&ratio) {
178 let s = w.max(h);
179 (s, s)
180 } else {
181 (w, h)
182 }
183}
184
185fn cap_rule(w: usize, h: usize) -> (usize, usize) {
187 if w.max(h) > SV_MAX_IMAGE_SIZE {
188 let s = SV_MAX_IMAGE_SIZE as f64 / w.max(h) as f64;
189 ((w as f64 * s) as usize, (h as f64 * s) as usize)
190 } else {
191 (w, h)
192 }
193}
194
195fn crop_snap(side: usize, win: usize) -> usize {
197 let ratio = side as f64 / win as f64;
198 if ratio < 1.0 {
199 return side;
200 }
201 let whole = side / win;
202 let n = if ratio - whole as f64 > 0.2 {
203 whole + 1
204 } else {
205 whole
206 };
207 win * n
208}
209
210pub struct StepImagePlan {
215 pub n_tiles: usize,
216 pub newline_mask: Vec<bool>,
217}
218
219impl StepImagePlan {
220 pub fn n_rows(&self) -> usize {
222 self.n_tiles * SV_TILE_ROWS + SV_MAIN_ROWS
223 }
224 pub fn n_prompt_tokens(&self) -> usize {
226 let newlines = self.newline_mask.iter().filter(|&&b| b).count();
227 self.n_tiles * (SV_TILE_ROWS + 2) + newlines + SV_MAIN_ROWS + 2
228 }
229}
230
231fn plan_for_dims(w0: usize, h0: usize) -> StepImagePlan {
232 let (w, h) = pad_rule(w0, h0);
233 let (w, h) = cap_rule(w, h);
234 let win = window_size(w.max(h), w.min(h));
235 if win == 0 {
236 return StepImagePlan {
237 n_tiles: 0,
238 newline_mask: Vec::new(),
239 };
240 }
241 let (cw, ch) = (crop_snap(w, win), crop_snap(h, win));
242 let x_num = (cw / win).max(1);
245 let y_num = (ch / win).max(1);
246 let n = x_num * y_num;
247 let mut mask = vec![false; n];
248 let mut newlines: Vec<usize> = (0..n).filter(|i| (i + 1) % x_num == 0).collect();
249 if newlines.last() == Some(&(n - 1)) {
250 newlines.pop(); }
252 for i in newlines {
253 mask[i] = true;
254 }
255 StepImagePlan {
256 n_tiles: n,
257 newline_mask: mask,
258 }
259}
260
261pub fn step_plan_image(bytes: &[u8]) -> Result<StepImagePlan, String> {
264 let (w, h) = crate::vision_pre::image_header_dims(bytes)?;
265 if w.saturating_mul(h) > crate::vision_pre::IMG_MAX_DECODE_PIXELS {
266 return Err(format!(
267 "image {w}x{h} exceeds the decode budget ({} px) — refused before decode",
268 crate::vision_pre::IMG_MAX_DECODE_PIXELS
269 ));
270 }
271 if w < 2 || h < 2 {
272 return Err(format!("image too small: {w}x{h}"));
273 }
274 Ok(plan_for_dims(w, h))
275}
276
277pub struct StepVisionUnit {
281 pub main: Vec<f32>,
283 pub tiles: Vec<Vec<f32>>,
285 pub newline_mask: Vec<bool>,
286}
287
288impl StepVisionUnit {
289 pub fn n_rows(&self) -> usize {
290 self.tiles.len() * SV_TILE_ROWS + SV_MAIN_ROWS
291 }
292}
293
294fn patchify(img: &image::RgbImage, g: usize) -> Vec<f32> {
296 let mut rows = vec![0f32; g * g * SV_PATCH_IN];
297 for py in 0..g {
298 for px in 0..g {
299 let dst = &mut rows[(py * g + px) * SV_PATCH_IN..(py * g + px + 1) * SV_PATCH_IN];
300 for c in 0..3 {
301 for ky in 0..SV_PATCH {
302 for kx in 0..SV_PATCH {
303 let p =
304 img.get_pixel((px * SV_PATCH + kx) as u32, (py * SV_PATCH + ky) as u32);
305 dst[(c * SV_PATCH + ky) * SV_PATCH + kx] =
306 ((p[c] as f32) / 255.0 - MEAN[c]) / STD[c];
307 }
308 }
309 }
310 }
311 }
312 rows
313}
314
315pub fn step_prep_image(bytes: &[u8]) -> Result<StepVisionUnit, Box<dyn std::error::Error>> {
320 step_plan_image(bytes)?;
321 let (hw, hh) = crate::vision_pre::image_header_dims(bytes)?;
322 let mut reader = image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format()?;
323 let mut limits = image::Limits::default();
324 limits.max_image_width = Some(hw as u32);
325 limits.max_image_height = Some(hh as u32);
326 reader.limits(limits);
327 let mut img = reader.decode()?.to_rgb8();
328 let (w0, h0) = (img.width() as usize, img.height() as usize);
329 let (pw, ph) = pad_rule(w0, h0);
331 if (pw, ph) != (w0, h0) {
332 let mut padded = image::RgbImage::new(pw as u32, ph as u32);
333 image::imageops::replace(&mut padded, &img, 0, 0);
334 img = padded;
335 }
336 let (cw, ch) = cap_rule(img.width() as usize, img.height() as usize);
338 if (cw, ch) != (img.width() as usize, img.height() as usize) {
339 img = image::imageops::resize(
340 &img,
341 cw as u32,
342 ch as u32,
343 image::imageops::FilterType::Triangle,
344 );
345 }
346 let (w, h) = (img.width() as usize, img.height() as usize);
347 let main_img = image::imageops::resize(
349 &img,
350 SV_IMAGE_SIZE as u32,
351 SV_IMAGE_SIZE as u32,
352 image::imageops::FilterType::Triangle,
353 );
354 let main = patchify(&main_img, SV_GRID_MAIN);
355 let win = window_size(w.max(h), w.min(h));
357 let (mut tiles, mut newline_mask) = (Vec::new(), Vec::new());
358 if win > 0 {
359 let (sw, sh) = (crop_snap(w, win), crop_snap(h, win));
360 let snapped = if (sw, sh) != (w, h) {
361 image::imageops::resize(
362 &img,
363 sw as u32,
364 sh as u32,
365 image::imageops::FilterType::Triangle,
366 )
367 } else {
368 img
369 };
370 let x_num = (sw / win).max(1);
371 let y_num = (sh / win).max(1);
372 let n = x_num * y_num;
373 for ty in 0..y_num {
374 for tx in 0..x_num {
375 let crop = image::imageops::crop_imm(
376 &snapped,
377 (tx * win) as u32,
378 (ty * win) as u32,
379 win as u32,
380 win as u32,
381 )
382 .to_image();
383 let tile = image::imageops::resize(
384 &crop,
385 SV_TILE_SIZE as u32,
386 SV_TILE_SIZE as u32,
387 image::imageops::FilterType::Triangle,
388 );
389 tiles.push(patchify(&tile, SV_GRID_TILE));
390 }
391 }
392 let mut newlines: Vec<usize> = (0..n).filter(|i| (i + 1) % x_num == 0).collect();
393 if newlines.last() == Some(&(n - 1)) {
394 newlines.pop();
395 }
396 newline_mask = vec![false; n];
397 for i in newlines {
398 newline_mask[i] = true;
399 }
400 }
401 Ok(StepVisionUnit {
402 main,
403 tiles,
404 newline_mask,
405 })
406}
407
408impl StepVisionTower {
411 pub fn load(e: &Engine, dir: &Path) -> Result<Self, Box<dyn std::error::Error>> {
416 let m = StModel::open(dir)?;
417 let p = "model.vision_model";
418 let patch = {
419 let w = read_f32(&m, &format!("{p}.conv1.weight"))?;
422 assert_eq!(w.len(), SV_HIDDEN * SV_PATCH_IN, "conv1.weight shape");
423 Lin {
424 w: e.htod(&w)?,
425 b: None,
426 in_f: SV_PATCH_IN,
427 out_f: SV_HIDDEN,
428 }
429 };
430 let pos = read_f32(&m, &format!("{p}.positional_embedding"))?;
431 assert_eq!(
432 pos.len(),
433 SV_POS_GRID * SV_POS_GRID * SV_HIDDEN,
434 "positional_embedding shape"
435 );
436 let ln_pre_w = e.htod(&read_f32(&m, &format!("{p}.ln_pre.weight"))?)?;
437 let ln_pre_b = e.htod(&read_f32(&m, &format!("{p}.ln_pre.bias"))?)?;
438 let mut blocks = Vec::with_capacity(SV_DEPTH);
439 for il in 0..SV_DEPTH {
440 let bp = format!("{p}.transformer.resblocks.{il}");
441 let ls1 = read_f32(&m, &format!("{bp}.ls_1.gamma"))?;
442 let ls2 = read_f32(&m, &format!("{bp}.ls_2.gamma"))?;
443 assert_eq!(ls1.len(), SV_HIDDEN, "ls_1.gamma shape");
444 assert_eq!(ls2.len(), SV_HIDDEN, "ls_2.gamma shape");
445 blocks.push(SBlock {
446 ln1_w: e.htod(&read_f32(&m, &format!("{bp}.ln_1.weight"))?)?,
447 ln1_b: e.htod(&read_f32(&m, &format!("{bp}.ln_1.bias"))?)?,
448 ln2_w: e.htod(&read_f32(&m, &format!("{bp}.ln_2.weight"))?)?,
449 ln2_b: e.htod(&read_f32(&m, &format!("{bp}.ln_2.bias"))?)?,
450 ls1,
451 ls2,
452 qkv: {
453 let w = read_f32(&m, &format!("{bp}.attn.in_proj_weight"))?;
455 let b = read_f32(&m, &format!("{bp}.attn.in_proj_bias"))?;
456 assert_eq!(w.len(), 3 * SV_HIDDEN * SV_HIDDEN, "in_proj_weight shape");
457 assert_eq!(b.len(), 3 * SV_HIDDEN, "in_proj_bias shape");
458 Lin {
459 w: e.htod(&w)?,
460 b: Some(e.htod(&b)?),
461 in_f: SV_HIDDEN,
462 out_f: 3 * SV_HIDDEN,
463 }
464 },
465 proj: load_lin(
466 e,
467 &m,
468 &format!("{bp}.attn.out_proj"),
469 SV_HIDDEN,
470 SV_HIDDEN,
471 true,
472 )?,
473 fc: load_lin(e, &m, &format!("{bp}.mlp.c_fc"), SV_HIDDEN, SV_INTER, true)?,
474 cproj: load_lin(
475 e,
476 &m,
477 &format!("{bp}.mlp.c_proj"),
478 SV_INTER,
479 SV_HIDDEN,
480 true,
481 )?,
482 });
483 }
484 let load_conv = |stem: &str,
485 c_in: usize,
486 c_out: usize|
487 -> Result<Conv3x3s2, Box<dyn std::error::Error>> {
488 let w = read_f32(&m, &format!("{stem}.weight"))?;
489 let b = read_f32(&m, &format!("{stem}.bias"))?;
490 assert_eq!(w.len(), c_out * c_in * 9, "{stem}.weight shape");
491 assert_eq!(b.len(), c_out, "{stem}.bias shape");
492 Ok(Conv3x3s2 {
493 w: e.htod(&w)?,
494 b: e.htod(&b)?,
495 c_in,
496 c_out,
497 })
498 };
499 let down1 = load_conv(&format!("{p}.vit_downsampler1"), SV_HIDDEN, 2 * SV_HIDDEN)?;
500 let down2 = load_conv(
501 &format!("{p}.vit_downsampler2"),
502 2 * SV_HIDDEN,
503 4 * SV_HIDDEN,
504 )?;
505 let proj = {
506 let w = read_f32(&m, "model.vit_large_projector.weight")?;
510 assert_eq!(w.len() % (4 * SV_HIDDEN), 0, "vit_large_projector shape");
511 let out_f = w.len() / (4 * SV_HIDDEN);
512 Lin {
513 w: e.htod(&w)?,
514 b: None,
515 in_f: 4 * SV_HIDDEN,
516 out_f,
517 }
518 };
519 eprintln!(
520 "[step-vision] tower loaded from {} ({SV_DEPTH} blocks, out_width {}, f32-resident)",
521 dir.display(),
522 proj.out_f
523 );
524 Ok(Self {
525 patch,
526 pos,
527 ln_pre_w,
528 ln_pre_b,
529 blocks,
530 down1,
531 down2,
532 proj,
533 })
534 }
535
536 pub fn out_width(&self) -> usize {
539 self.proj.out_f
540 }
541
542 fn linear_bias(
543 &self,
544 e: &Engine,
545 x: &CudaSlice<f32>,
546 l: &Lin,
547 m: usize,
548 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
549 let mut y = e.linear(x, &l.w, m, l.in_f, l.out_f)?;
550 if let Some(b) = &l.b {
551 for r in 0..m {
552 e.add_row_inplace(&mut y, b, l.out_f, r * l.out_f)?;
553 }
554 }
555 Ok(y)
556 }
557
558 fn pos_for_grid(&self, g: usize) -> Vec<f32> {
562 if g == SV_POS_GRID {
563 return self.pos.clone();
564 }
565 let scale = SV_POS_GRID as f32 / g as f32;
566 let mut out = vec![0f32; g * g * SV_HIDDEN];
567 for y in 0..g {
568 for x in 0..g {
569 let sy = ((y as f32 + 0.5) * scale - 0.5).clamp(0.0, (SV_POS_GRID - 1) as f32);
570 let sx = ((x as f32 + 0.5) * scale - 0.5).clamp(0.0, (SV_POS_GRID - 1) as f32);
571 let (y0, x0) = (sy.floor() as usize, sx.floor() as usize);
572 let (y1, x1) = ((y0 + 1).min(SV_POS_GRID - 1), (x0 + 1).min(SV_POS_GRID - 1));
573 let (fy, fx) = (sy - y0 as f32, sx - x0 as f32);
574 let dst = &mut out[(y * g + x) * SV_HIDDEN..(y * g + x + 1) * SV_HIDDEN];
575 #[allow(clippy::needless_range_loop)]
576 for c in 0..SV_HIDDEN {
579 let p00 = self.pos[(y0 * SV_POS_GRID + x0) * SV_HIDDEN + c];
580 let p01 = self.pos[(y0 * SV_POS_GRID + x1) * SV_HIDDEN + c];
581 let p10 = self.pos[(y1 * SV_POS_GRID + x0) * SV_HIDDEN + c];
582 let p11 = self.pos[(y1 * SV_POS_GRID + x1) * SV_HIDDEN + c];
583 dst[c] = p00 * (1.0 - fy) * (1.0 - fx)
584 + p01 * (1.0 - fy) * fx
585 + p10 * fy * (1.0 - fx)
586 + p11 * fy * fx;
587 }
588 }
589 }
590 out
591 }
592
593 fn im2col(x: &[f32], g: usize, c_in: usize) -> (Vec<f32>, usize) {
597 let og = (g - 1) / 2 + 1;
598 let mut out = vec![0f32; og * og * c_in * 9];
599 for oy in 0..og {
600 for ox in 0..og {
601 let dst = &mut out[(oy * og + ox) * c_in * 9..(oy * og + ox + 1) * c_in * 9];
602 for ky in 0..3usize {
603 for kx in 0..3usize {
604 let iy = (2 * oy + ky) as isize - 1;
605 let ix = (2 * ox + kx) as isize - 1;
606 if iy < 0 || ix < 0 || iy >= g as isize || ix >= g as isize {
607 continue; }
609 let src = &x[((iy as usize) * g + ix as usize) * c_in..];
610 for c in 0..c_in {
611 dst[c * 9 + ky * 3 + kx] = src[c];
612 }
613 }
614 }
615 }
616 }
617 (out, og)
618 }
619
620 pub fn forward(
624 &self,
625 e: &Engine,
626 patches: &[f32],
627 g: usize,
628 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
629 let n = g * g;
630 assert_eq!(patches.len(), n * SV_PATCH_IN, "patch buffer shape");
631 if n > 12288 {
632 return Err(format!(
633 "step vision segment {n} patches exceeds the sdpa shared-memory ceiling (12288)"
634 )
635 .into());
636 }
637 let dbg = std::env::var("MEMRA_VISION_DEBUG").ok();
638 let dump = |tag: &str, buf: &[f32]| {
639 if let Some(dir) = dbg.as_deref() {
640 let raw: Vec<u8> = buf.iter().flat_map(|v| v.to_le_bytes()).collect();
641 let _ = std::fs::write(format!("{dir}/rust_{tag}.bin"), raw);
642 }
643 };
644 let xd = e.htod(patches)?;
646 let embedded = self.linear_bias(e, &xd, &self.patch, n)?;
647 let pos = self.pos_for_grid(g);
648 let pos_d = e.htod(&pos)?;
649 let mut summed = e.zeros(n * SV_HIDDEN)?;
650 e.add(&embedded, &pos_d, &mut summed, n * SV_HIDDEN)?;
651 let mut x = e.zeros(n * SV_HIDDEN)?;
652 e.layer_norm_bias(
653 &summed,
654 &self.ln_pre_w,
655 &self.ln_pre_b,
656 &mut x,
657 SV_HIDDEN,
658 n,
659 LN_EPS,
660 )?;
661 if dbg.is_some() {
662 dump("pre_blocks", &e.dtoh(&x)?);
663 }
664 let half = SV_HEAD_DIM / 2; let quarter = half / 2; let inv_freq: Vec<f32> = (0..quarter)
671 .map(|i| ROPE_THETA.powf(-2.0 * (i as f32) / half as f32))
672 .collect();
673 let mut cos_t = vec![0f32; n * half]; let mut sin_t = vec![0f32; n * half];
675 for t in 0..n {
676 let (row, col) = (t / g, t % g);
677 for i in 0..quarter {
678 let ac = col as f32 * inv_freq[i];
679 let ar = row as f32 * inv_freq[i];
680 cos_t[t * half + i] = ac.cos();
681 sin_t[t * half + i] = ac.sin();
682 cos_t[t * half + quarter + i] = ar.cos();
683 sin_t[t * half + quarter + i] = ar.sin();
684 }
685 }
686 let scale = 1.0 / (SV_HEAD_DIM as f32).sqrt();
687 for (ib, blk) in self.blocks.iter().enumerate() {
688 let mut h = e.zeros(n * SV_HIDDEN)?;
691 e.layer_norm_bias(&x, &blk.ln1_w, &blk.ln1_b, &mut h, SV_HIDDEN, n, LN_EPS)?;
692 let qkv = self.linear_bias(e, &h, &blk.qkv, n)?;
693 let qkv_h = e.dtoh(&qkv)?;
694 let mut qh = vec![0f32; n * SV_HIDDEN];
695 let mut kh = vec![0f32; n * SV_HIDDEN];
696 let mut vh = vec![0f32; n * SV_HIDDEN];
697 for t in 0..n {
698 let row = &qkv_h[t * 3 * SV_HIDDEN..(t + 1) * 3 * SV_HIDDEN];
699 let dst = t * SV_HIDDEN;
700 vh[dst..dst + SV_HIDDEN].copy_from_slice(&row[2 * SV_HIDDEN..3 * SV_HIDDEN]);
701 for hd in 0..SV_HEADS {
702 let o = hd * SV_HEAD_DIM;
703 for hf in 0..2usize {
707 for i in 0..quarter {
708 let (c, s) = (
709 cos_t[t * half + hf * quarter + i],
710 sin_t[t * half + hf * quarter + i],
711 );
712 let d = hf * half + 2 * i;
713 let (qa, qb) = (row[o + d], row[o + d + 1]);
714 qh[dst + o + d] = qa * c - qb * s;
715 qh[dst + o + d + 1] = qb * c + qa * s;
716 let (ka, kb) = (row[SV_HIDDEN + o + d], row[SV_HIDDEN + o + d + 1]);
717 kh[dst + o + d] = ka * c - kb * s;
718 kh[dst + o + d + 1] = kb * c + ka * s;
719 }
720 }
721 }
722 }
723 let (qd, kd, vd) = (e.htod(&qh)?, e.htod(&kh)?, e.htod(&vh)?);
724 let mut od = e.zeros(n * SV_HIDDEN)?;
725 e.sdpa_naive(
726 &qd,
727 &kd,
728 &vd,
729 &mut od,
730 SV_HEAD_DIM,
731 SV_HEADS,
732 SV_HEADS,
733 n,
734 n,
735 scale,
736 false,
737 )?;
738 let attn = self.linear_bias(e, &od, &blk.proj, n)?;
739 let mut ah = e.dtoh(&attn)?;
741 for t in 0..n {
742 for c in 0..SV_HIDDEN {
743 ah[t * SV_HIDDEN + c] *= blk.ls1[c];
744 }
745 }
746 let ad = e.htod(&ah)?;
747 let mut xr = e.zeros(n * SV_HIDDEN)?;
748 e.add(&x, &ad, &mut xr, n * SV_HIDDEN)?;
749 let mut h2 = e.zeros(n * SV_HIDDEN)?;
751 e.layer_norm_bias(&xr, &blk.ln2_w, &blk.ln2_b, &mut h2, SV_HIDDEN, n, LN_EPS)?;
752 let f1 = self.linear_bias(e, &h2, &blk.fc, n)?;
753 let mut fh = e.dtoh(&f1)?;
754 for v in fh.iter_mut() {
755 *v = *v / (1.0 + (-1.702 * *v).exp());
757 }
758 let fd = e.htod(&fh)?;
759 let f2 = self.linear_bias(e, &fd, &blk.cproj, n)?;
760 let mut mh = e.dtoh(&f2)?;
761 for t in 0..n {
762 for c in 0..SV_HIDDEN {
763 mh[t * SV_HIDDEN + c] *= blk.ls2[c];
764 }
765 }
766 let md = e.htod(&mh)?;
767 let mut xn = e.zeros(n * SV_HIDDEN)?;
768 e.add(&xr, &md, &mut xn, n * SV_HIDDEN)?;
769 x = xn;
770 if dbg.is_some() && ib == 0 {
771 dump("blk0", &e.dtoh(&x)?);
772 }
773 }
774 if dbg.is_some() {
776 dump("post_blocks", &e.dtoh(&x)?);
777 }
778 let xh = e.dtoh(&x)?;
781 let (col1, g1) = Self::im2col(&xh, g, SV_HIDDEN);
782 let c1 = e.htod(&col1)?;
783 let mut y1 = e.linear(
784 &c1,
785 &self.down1.w,
786 g1 * g1,
787 self.down1.c_in * 9,
788 self.down1.c_out,
789 )?;
790 for r in 0..g1 * g1 {
791 e.add_row_inplace(
792 &mut y1,
793 &self.down1.b,
794 self.down1.c_out,
795 r * self.down1.c_out,
796 )?;
797 }
798 let y1h = e.dtoh(&y1)?;
799 let (col2, g2) = Self::im2col(&y1h, g1, self.down2.c_in);
800 let c2 = e.htod(&col2)?;
801 let mut y2 = e.linear(
802 &c2,
803 &self.down2.w,
804 g2 * g2,
805 self.down2.c_in * 9,
806 self.down2.c_out,
807 )?;
808 for r in 0..g2 * g2 {
809 e.add_row_inplace(
810 &mut y2,
811 &self.down2.b,
812 self.down2.c_out,
813 r * self.down2.c_out,
814 )?;
815 }
816 if dbg.is_some() {
817 dump("downsampled", &e.dtoh(&y2)?);
818 }
819 let out = self.linear_bias(e, &y2, &self.proj, g2 * g2)?;
820 if dbg.is_some() {
821 dump("projected", &e.dtoh(&out)?);
822 }
823 Ok(out)
824 }
825
826 pub fn forward_unit(
829 &self,
830 e: &Engine,
831 unit: &StepVisionUnit,
832 rows: &mut CudaSlice<f32>,
833 row_off: usize,
834 ) -> Result<usize, Box<dyn std::error::Error>> {
835 let w = self.out_width();
836 let mut off = row_off;
837 for tile in &unit.tiles {
838 let emb = self.forward(e, tile, SV_GRID_TILE)?;
839 e.dtod_copy_into(&emb, rows, off * w)?;
840 off += SV_TILE_ROWS;
841 }
842 let emb = self.forward(e, &unit.main, SV_GRID_MAIN)?;
843 e.dtod_copy_into(&emb, rows, off * w)?;
844 off += SV_MAIN_ROWS;
845 Ok(off - row_off)
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[test]
856 fn tiling_plan_cells() {
857 let p = plan_for_dims(600, 400);
859 assert_eq!((p.n_tiles, p.n_prompt_tokens()), (0, 171));
860 let p = plan_for_dims(728, 728);
861 assert_eq!(p.n_tiles, 0);
862 let p = plan_for_dims(700, 300);
866 assert_eq!(p.n_tiles, 3);
867 assert_eq!(p.newline_mask, vec![false, false, false]);
868 let p = plan_for_dims(1600, 900);
872 assert_eq!(p.n_tiles, 6);
873 assert_eq!(
874 p.newline_mask,
875 vec![false, false, true, false, false, false]
876 );
877 assert_eq!(p.n_prompt_tokens(), 670);
879 let p = plan_for_dims(200, 20);
881 assert_eq!(p.n_tiles, 0);
882 let p = plan_for_dims(4000, 1000);
885 assert_eq!(p.n_tiles, 12);
886 assert_eq!(p.n_rows(), 12 * 81 + 169);
887 }
888
889 #[test]
891 fn downsampler_geometry() {
892 let x = vec![0f32; 52 * 52 * 4];
893 let (_, og) = StepVisionTower::im2col(&x, 52, 4);
894 assert_eq!(og, 26);
895 let x = vec![0f32; 26 * 26 * 4];
896 let (_, og) = StepVisionTower::im2col(&x, 26, 4);
897 assert_eq!(og, 13);
898 let x = vec![0f32; 36 * 36 * 4];
899 let (_, og) = StepVisionTower::im2col(&x, 36, 4);
900 assert_eq!(og, 18);
901 let x = vec![0f32; 18 * 18 * 4];
902 let (_, og) = StepVisionTower::im2col(&x, 18, 4);
903 assert_eq!(og, 9);
904 }
905
906 #[test]
908 fn im2col_values() {
909 let x: Vec<f32> = (1..=9).map(|v| v as f32).collect();
912 let (col, og) = StepVisionTower::im2col(&x, 3, 1);
913 assert_eq!(og, 2);
914 assert_eq!(&col[0..9], &[0., 0., 0., 0., 1., 2., 0., 4., 5.]);
915 assert_eq!(&col[27..36], &[5., 6., 0., 8., 9., 0., 0., 0., 0.]);
917 }
918
919 fn png_bytes(w: u32, h: u32) -> Vec<u8> {
920 let img = image::RgbImage::from_fn(w, h, |x, y| {
921 image::Rgb([(x % 251) as u8, (y % 241) as u8, ((x + y) % 253) as u8])
922 });
923 let mut buf = std::io::Cursor::new(Vec::new());
924 img.write_to(&mut buf, image::ImageFormat::Png).unwrap();
925 buf.into_inner()
926 }
927
928 #[test]
931 fn prep_matches_plan() {
932 for (w, h) in [(64u32, 64u32), (1600, 900), (700, 300), (900, 3000)] {
933 let bytes = png_bytes(w, h);
934 let plan = step_plan_image(&bytes).unwrap();
935 let prep = step_prep_image(&bytes).unwrap();
936 assert_eq!(prep.tiles.len(), plan.n_tiles, "{w}x{h} tile count");
937 assert_eq!(prep.newline_mask, plan.newline_mask, "{w}x{h} newline mask");
938 assert_eq!(prep.main.len(), SV_GRID_MAIN * SV_GRID_MAIN * SV_PATCH_IN);
939 for t in &prep.tiles {
940 assert_eq!(t.len(), SV_GRID_TILE * SV_GRID_TILE * SV_PATCH_IN);
941 }
942 assert_eq!(prep.n_rows(), plan.n_rows());
943 }
944 }
945
946 #[test]
949 fn patchify_normalization() {
950 let img = image::RgbImage::from_pixel(
951 SV_IMAGE_SIZE as u32,
952 SV_IMAGE_SIZE as u32,
953 image::Rgb([128, 128, 128]),
954 );
955 let rows = patchify(&img, SV_GRID_MAIN);
956 let want: Vec<f32> = (0..3).map(|c| (128.0 / 255.0 - MEAN[c]) / STD[c]).collect();
957 let r0 = &rows[..SV_PATCH_IN];
958 for c in 0..3 {
959 for i in 0..SV_PATCH * SV_PATCH {
960 assert!((r0[c * SV_PATCH * SV_PATCH + i] - want[c]).abs() < 1e-6);
961 }
962 }
963 }
964}