1use std::sync::atomic::{AtomicU64, Ordering};
13use std::sync::Arc;
14
15use anyhow::{anyhow, Context, Result};
16use cortiq_core::quant::dequant_tensor;
17use cortiq_core::{CmfModel, TensorDtype};
18
19use crate::inference::gelu_erf;
20use crate::pool::Pool;
21use crate::qtensor::QTensor;
22
23const NEG: f32 = -1e30;
25
26pub fn preprocess(frame: &crate::media::RgbFrame) -> Vec<f32> {
31 use image::imageops::{self, FilterType};
32 let img = image::RgbImage::from_raw(
33 frame.width as u32,
34 frame.height as u32,
35 frame.data.clone(),
36 )
37 .expect("RgbFrame buffer matches its own dimensions");
38 let rs = imageops::resize(&img, PIX as u32, PIX as u32, FilterType::Triangle);
39 let raw = rs.into_raw();
40 let mut out = vec![0f32; 3 * PIX * PIX];
41 for i in 0..PIX * PIX {
42 for c in 0..3 {
43 out[c * PIX * PIX + i] = (raw[i * 3 + c] as f32 / 255.0 - 0.5) / 0.5;
44 }
45 }
46 out
47}
48
49const PIX: usize = 1008;
50const PATCH: usize = 14;
51const GRID: usize = PIX / PATCH; const WIN: usize = 24;
53const TD: usize = 1024;
54const TH: usize = 16;
55const TL: usize = 32;
56const TI: usize = 4736;
57const T_EPS: f32 = 1e-6;
58const GLOBAL: [usize; 4] = [7, 15, 23, 31];
59
60const SS: usize = 32;
61const SD: usize = 1024;
62const SL: usize = 24;
63const SI: usize = 4096;
64const S_EPS: f32 = 1e-5;
65const PROJ: usize = 512;
66
67const FD: usize = 256;
68const FI: usize = 2048;
69const FH: usize = 8;
70const D_EPS: f32 = 1e-6;
71const NL: usize = 6;
72const NQ: usize = 200;
73const MH: usize = 288;
74
75pub struct Sam3Out {
77 pub text_embeds: Vec<f32>,
79 pub text_feats: Vec<f32>,
81 pub trunk_feats: Vec<f32>,
83 pub fpn: Vec<Vec<f32>>,
85 pub enc_hidden: Vec<f32>,
87 pub dec_hidden: Vec<f32>,
89 pub pred_boxes: Vec<f32>,
91 pub pred_masks: Vec<f32>,
93 pub pred_logits: Vec<f32>,
99 pub presence_logit: f32,
102}
103
104#[derive(Clone, Copy)]
106pub(crate) enum Mask<'a> {
107 None,
108 Key(&'a [f32]),
110 Add(&'a [f32]),
112 Head(&'a [f32]),
114}
115
116static ATT_PROJ_NS: AtomicU64 = AtomicU64::new(0);
119static ATT_CORE_NS: AtomicU64 = AtomicU64::new(0);
120
121struct StageTimer {
126 on: bool,
127 main: bool,
128 t0: std::time::Instant,
129 acc: Vec<(&'static str, f64)>,
130}
131
132impl StageTimer {
133 fn new() -> Self {
134 Self {
135 on: std::env::var("SAM3_STAGES").is_ok_and(|v| v != "0"),
136 main: false,
137 t0: std::time::Instant::now(),
138 acc: Vec::new(),
139 }
140 }
141
142 fn new_session() -> Self {
145 ATT_PROJ_NS.store(0, Ordering::Relaxed);
146 ATT_CORE_NS.store(0, Ordering::Relaxed);
147 Self {
148 on: std::env::var("SAM3_STAGES").is_ok_and(|v| v != "0"),
149 main: true,
150 t0: std::time::Instant::now(),
151 acc: Vec::new(),
152 }
153 }
154
155 fn lap(&mut self, name: &'static str) {
156 if self.on {
157 self.acc.push((name, self.t0.elapsed().as_secs_f64() * 1e3));
158 }
159 self.t0 = std::time::Instant::now();
160 }
161}
162
163impl Drop for StageTimer {
164 fn drop(&mut self) {
165 if !self.on || self.acc.is_empty() {
166 return;
167 }
168 let mut merged: Vec<(&'static str, f64)> = Vec::new();
169 for (n, ms) in &self.acc {
170 match merged.iter_mut().find(|(m, _)| m == n) {
171 Some((_, t)) => *t += *ms,
172 None => merged.push((*n, *ms)),
173 }
174 }
175 let total: f64 = merged.iter().map(|(_, t)| t).sum();
176 for (n, ms) in &merged {
177 eprintln!("[sam3-prof] {n:>14} {ms:>10.1} ms ({:.1}%)", ms / total * 100.0);
178 }
179 eprintln!("[sam3-prof] {:>14} {total:>10.1} ms", "TOTAL");
180 if self.main {
181 eprintln!(
182 "[sam3-prof] {:>14} {:>10.1} ms",
183 "attn_proj",
184 ATT_PROJ_NS.load(Ordering::Relaxed) as f64 / 1e6
185 );
186 eprintln!(
187 "[sam3-prof] {:>14} {:>10.1} ms",
188 "attn_core",
189 ATT_CORE_NS.load(Ordering::Relaxed) as f64 / 1e6
190 );
191 eprintln!(
192 "[sam3-prof] {:>14} hit/gate={}/{}",
193 "matmat_gpu",
194 crate::qtensor::MATMAT_GPU_HIT.load(Ordering::Relaxed),
195 crate::qtensor::MATMAT_GATE.load(Ordering::Relaxed)
196 );
197 #[cfg(feature = "gpu")]
198 eprintln!(
199 "[sam3-prof] {:>14} noctx/shape/entry/bounds/disp={}/{}/{}/{}/{}",
200 "m2f_refuse",
201 crate::gpu_wgpu::M2F_NO_CTX.load(Ordering::Relaxed),
202 crate::gpu_wgpu::M2F_SHAPE.load(Ordering::Relaxed),
203 crate::gpu_wgpu::M2F_ENTRY.load(Ordering::Relaxed),
204 crate::gpu_wgpu::M2F_BOUNDS.load(Ordering::Relaxed),
205 crate::gpu_wgpu::M2F_DISPATCH.load(Ordering::Relaxed),
206 );
207 }
208 }
209}
210
211pub struct Sam3 {
212 model: Arc<CmfModel>,
213 pool: Option<Arc<Pool>>,
214 fpn_prefix: &'static str,
216 pos_skip_cls: bool,
219}
220
221impl Sam3 {
222 pub fn open(path: &str) -> Result<Self> {
223 let model = CmfModel::open(path)
224 .with_context(|| format!("open {path}"))?
225 .into();
226 let fpn_prefix = match std::env::var("SAM3_FPN").as_deref() {
227 Ok("sam2_convs") => "fpn.sam2_convs",
228 _ => "fpn.convs",
229 };
230 let pos_skip_cls = std::env::var("SAM3_POS").as_deref() != Ok("first");
231 Ok(Self { model, pool: Pool::from_env(), fpn_prefix, pos_skip_cls })
232 }
233
234 pub(crate) fn cmf(&self) -> &Arc<CmfModel> { &self.model }
237
238 pub(crate) fn pool_handle(&self) -> Option<&Arc<Pool>> { self.pool.as_ref() }
240
241 pub fn directory(&self) -> Vec<String> {
244 self.model
245 .tensors
246 .iter()
247 .map(|t| format!("{} {:?} {:?}", t.name, t.dtype, t.shape))
248 .collect()
249 }
250
251 pub fn raw_tensor(&self, name: &str) -> Option<Vec<u8>> {
253 let e = self.model.tensor(name)?;
254 Some(self.model.entry_bytes(e).to_vec())
255 }
256
257 pub fn bad_scales(&self) -> Vec<String> {
260 let f16_bad = |h: u16| h & 0x7C00 == 0x7C00;
261 let mut out = Vec::new();
262 for t in self.model.tensors.iter() {
263 let bytes = self.model.entry_bytes(t);
264 match t.dtype {
265 TensorDtype::Q8Row if t.shape.len() == 2 => {
266 let (rows, cols) = (t.shape[0], t.shape[1]);
267 let off = rows * cols;
268 if bytes.len() < off + rows * 2 {
269 out.push(format!("{}@payload short", t.name));
270 continue;
271 }
272 for r in 0..rows {
273 let h = u16::from_le_bytes([bytes[off + r * 2], bytes[off + r * 2 + 1]]);
274 if f16_bad(h) {
275 out.push(format!("{}@row{r} scale_f16=0x{h:04x}", t.name));
276 }
277 }
278 }
279 TensorDtype::F16 => {
280 for (i, c) in bytes.chunks_exact(2).enumerate() {
281 let h = u16::from_le_bytes([c[0], c[1]]);
282 if f16_bad(h) {
283 out.push(format!("{}@{i} val_f16=0x{h:04x}", t.name));
284 if out.len() > 200 { return out; }
285 }
286 }
287 }
288 _ => {}
289 }
290 }
291 out
292 }
293
294 pub fn forward(&mut self, pixels: &[f32], ids: &[u32], attn: &[u8]) -> Result<Sam3Out> {
304 let _prof = crate::cpuprof::spans::dump_on_drop("image forward");
305 if pixels.len() != 3 * PIX * PIX {
306 return Err(anyhow!("pixels {} != 3*{}*{}", pixels.len(), PIX, PIX));
307 }
308 if ids.len() != SS || attn.len() != SS {
309 return Err(anyhow!("ids/attn must be {SS} long"));
310 }
311 let only = std::env::var("SAM3_ONLY").ok();
312 let want = |k: &str| {
313 only
314 .as_deref()
315 .map_or(true, |o| o.split(',').any(|s| s.trim() == k))
316 };
317 let pool = self.pool.as_deref();
318 let mut stg = StageTimer::new_session();
319 let _p = crate::cpuprof::spans::span("text");
320 let (text_embeds, text_feats) = self.text(ids, attn, pool)?;
321 drop(_p);
322 stg.lap("text");
323 let blank = |te: Vec<f32>, tf: Vec<f32>| Sam3Out {
324 text_embeds: te,
325 text_feats: tf,
326 trunk_feats: Vec::new(),
327 fpn: Vec::new(),
328 enc_hidden: Vec::new(),
329 dec_hidden: Vec::new(),
330 pred_boxes: Vec::new(),
331 pred_masks: Vec::new(),
332 pred_logits: Vec::new(),
333 presence_logit: 0.0,
334 };
335 if !want("trunk") {
336 return Ok(blank(text_embeds, text_feats));
337 }
338 let trunk_feats = {
339 let _p = crate::cpuprof::spans::span("trunk");
340 self.trunk(pixels, pool)?
341 };
342 stg.lap("trunk");
343 if !want("fpn") {
344 let mut o = blank(text_embeds, text_feats);
345 o.trunk_feats = trunk_feats;
346 return Ok(o);
347 }
348 let fpn = self.fpn(&trunk_feats, pool)?;
349 stg.lap("fpn");
350 let pos = sine_pos(GRID, GRID, FD / 2); let vision = chw_to_rows(&fpn[2], GRID, GRID);
352 let enc_hidden = {
353 let _p = crate::cpuprof::spans::span("detr_enc");
354 self.detr_encoder(&vision, &pos, &text_feats, attn, pool)?
355 };
356 stg.lap("enc");
357 if !want("dec") {
358 let mut o = blank(text_embeds, text_feats);
359 o.trunk_feats = trunk_feats;
360 o.fpn = fpn;
361 o.enc_hidden = enc_hidden;
362 return Ok(o);
363 }
364 let (dec_hidden, final_refs, presence_logit) = {
365 let _p = crate::cpuprof::spans::span("detr_dec");
366 self.detr_decoder(&enc_hidden, &pos, &text_feats, attn, pool)?
367 };
368 stg.lap("dec");
369 let pred_logits = {
374 let _p = crate::cpuprof::spans::span("score");
375 self.score(
376 &text_feats,
377 attn,
378 &dec_hidden[(NL - 1) * NQ * FD..],
379 pool,
380 )?
381 };
382 stg.lap("score");
383 if !want("masks") {
384 let mut o = blank(text_embeds, text_feats);
385 o.trunk_feats = trunk_feats;
386 o.fpn = fpn;
387 o.enc_hidden = enc_hidden;
388 o.dec_hidden = dec_hidden.clone();
389 o.pred_boxes = xyxy(&final_refs);
390 o.pred_logits = pred_logits;
391 o.presence_logit = presence_logit;
392 return Ok(o);
393 }
394 let pred_boxes = xyxy(&final_refs);
395 let pred_masks = {
396 let _p = crate::cpuprof::spans::span("mask_dec");
397 self.mask_decoder(
398 &dec_hidden[(NL - 1) * NQ * FD..],
399 &enc_hidden,
400 &fpn,
401 &text_feats,
402 attn,
403 pool,
404 )?
405 };
406 stg.lap("masks");
407 Ok(Sam3Out {
408 text_embeds,
409 text_feats,
410 trunk_feats,
411 fpn,
412 enc_hidden,
413 dec_hidden,
414 pred_boxes,
415 pred_masks,
416 pred_logits,
417 presence_logit,
418 })
419 }
420
421 fn text(&self, ids: &[u32], attn: &[u8], pool: Option<&Pool>) -> Result<(Vec<f32>, Vec<f32>)> {
424 let m = &self.model;
425 let tok = lin(m, "text.encoder.token_embedding.weight")?;
426 let pos = load_vec(m, "text.encoder.positional_embedding")?;
427 let mut x = vec![0f32; SS * SD];
428 let mut row = vec![0f32; SD];
429 for (i, &id) in ids.iter().enumerate() {
430 tok.row_f32(id as usize, &mut row);
431 let r = &mut x[i * SD..(i + 1) * SD];
432 r.copy_from_slice(&row);
433 for (v, p) in r.iter_mut().zip(&pos[i * SD..(i + 1) * SD]) {
434 *v += *p;
435 }
436 }
437 dbg_stats("text.emb", &x);
438 let mut cm = vec![0f32; SS * SS];
440 for q in 0..SS {
441 for k in 0..=q {
442 if attn[k] == 0 {
443 cm[q * SS + k] = NEG;
444 }
445 }
446 for k in (q + 1)..SS {
447 cm[q * SS + k] = NEG;
448 }
449 }
450 for l in 0..SL {
451 let b = format!("text.encoder.transformer.resblocks.{l}.");
452 let (lw, lb) = (load_vec(m, &format!("{b}ln_1.weight"))?, load_vec(m, &format!("{b}ln_1.bias"))?);
453 let mut h = x.clone();
455 layer_norm_rows(&mut h, &lw, &lb, S_EPS);
456 let qkv = lin(m, &format!("{b}attn.in_proj_weight"))?;
457 let ib = load_vec(m, &format!("{b}attn.in_proj_bias"))?;
458 let op = lin(m, &format!("{b}attn.out_proj.weight"))?;
459 let ob = load_vec(m, &format!("{b}attn.out_proj.bias"))?;
460 let mut ao = vec![0f32; SS * SD];
461 run_attn(
462 &qkv, &ib, &op, &ob,
463 &h, &h, &h, SS, SS, SD, 16, None, Mask::Add(&cm), pool, &mut ao,
464 );
465 for (a, c) in x.iter_mut().zip(&ao) { *a += *c; }
466 let (lw, lb) = (load_vec(m, &format!("{b}ln_2.weight"))?, load_vec(m, &format!("{b}ln_2.bias"))?);
467 let mut h = x.clone();
468 layer_norm_rows(&mut h, &lw, &lb, S_EPS);
469 let f1 = lin(m, &format!("{b}mlp.c_fc.weight"))?;
470 let f1b = load_vec(m, &format!("{b}mlp.c_fc.bias"))?;
471 let f2 = lin(m, &format!("{b}mlp.c_proj.weight"))?;
472 let f2b = load_vec(m, &format!("{b}mlp.c_proj.bias"))?;
473 let mut ff = vec![0f32; SS * SI];
474 f1.matmat(&h, SS, &mut ff, pool);
475 add_bias(&mut ff, SI, &f1b);
476 ff.iter_mut().for_each(|v| *v = gelu_erf(*v));
477 let mut fo = vec![0f32; SS * SD];
478 f2.matmat(&ff, SS, &mut fo, pool);
479 add_bias(&mut fo, SD, &f2b);
480 for (a, c) in x.iter_mut().zip(&fo) { *a += *c; }
481 dbg_stats(&format!("text.l{l}"), &x);
482 }
483 let (lw, lb) = (load_vec(m, "text.encoder.ln_final.weight")?, load_vec(m, "text.encoder.ln_final.bias")?);
484 layer_norm_rows(&mut x, &lw, &lb, S_EPS);
485 dbg_stats("text.ln_final", &x);
486 if let Some(d) = std::env::var("SAM3_DUMP_DIR").ok() {
487 dump_f32(&d, "text_lh", &x);
488 }
489 let p = ids.iter().position(|&v| v == 49407).ok_or_else(|| anyhow!("no eos token"))?;
491 let pooled = &x[p * SD..(p + 1) * SD];
492 let proj = load_vec(m, "text.encoder.text_projection")?;
494 dbg_stats("text.proj", &proj);
495 let mut embeds = vec![0f32; PROJ];
496 for j in 0..PROJ {
497 let mut acc = 0f32;
498 for i in 0..SD { acc += pooled[i] * proj[i * PROJ + j]; }
499 embeds[j] = acc;
500 }
501 dbg_stats("text.pooled", pooled);
502 dbg_stats("text.embeds", &embeds);
503 let rz = lin(m, "text.resizer.weight")?;
505 let rb = load_vec(m, "text.resizer.bias")?;
506 let mut feats = vec![0f32; SS * FD];
507 rz.matmat(&x, SS, &mut feats, pool);
508 add_bias(&mut feats, FD, &rb);
509 dbg_stats("text.feats", &feats);
510 Ok((embeds, feats))
511 }
512
513 fn trunk(&self, pixels: &[f32], pool: Option<&Pool>) -> Result<Vec<f32>> {
516 let _patch = crate::cpuprof::spans::span("trunk.patch");
517 let m = &self.model;
518 let np = GRID * GRID;
519 let kc = 3 * PATCH * PATCH;
520 let dump = std::env::var("SAM3_DUMP_DIR").ok();
521 let dlayers: Option<Vec<usize>> = std::env::var("SAM3_DUMP_LAYERS")
522 .ok()
523 .map(|s| s.split(',').filter_map(|t| t.trim().parse().ok()).collect());
524 let w = load_vec(m, "trunk.patch_embed.proj.weight")?; let mut pv = vec![0f32; np * kc];
526 let pvb = pv.as_mut_ptr() as usize;
527 let fill = |s: usize, e: usize| {
528 let pv = unsafe { std::slice::from_raw_parts_mut(pvb as *mut f32, np * kc) };
529 for pt in s..e {
530 let (py, px) = (pt / GRID, pt % GRID);
531 let d = &mut pv[pt * kc..(pt + 1) * kc];
532 let mut o = 0;
533 for c in 0..3 {
534 for ky in 0..PATCH {
535 let y = py * PATCH + ky;
536 for kx in 0..PATCH {
537 d[o] = pixels[c * PIX * PIX + y * PIX + px * PATCH + kx];
538 o += 1;
539 }
540 }
541 }
542 }
543 };
544 match pool { Some(p) => p.run_rows(np, &fill), None => fill(0, np) };
545 let mut x = vec![0f32; np * TD];
546 let xb = x.as_mut_ptr() as usize;
547 let gemm = |s: usize, e: usize| {
548 let x = unsafe { std::slice::from_raw_parts_mut(xb as *mut f32, np * TD) };
549 for ch in s..e {
550 let wr = &w[ch * kc..(ch + 1) * kc];
551 for pt in 0..np {
552 let xv = &pv[pt * kc..(pt + 1) * kc];
553 let mut acc = 0f32;
554 for k in 0..kc { acc += wr[k] * xv[k]; }
555 x[pt * TD + ch] = acc;
556 }
557 }
558 };
559 match pool { Some(p) => p.run_rows(TD, &gemm), None => gemm(0, TD) };
560 drop(_patch);
561 if let Some(d) = &dump { dump_f32(d, "trunk_patch", &x); }
562 let pos = load_vec(m, "trunk.pos_embed")?;
564 let base = if self.pos_skip_cls { 1 } else { 0 };
565 if pos.len() < (base + 576) * TD { return Err(anyhow!("pos_embed too small")); }
566 for pt in 0..np {
567 let (py, px) = (pt / GRID, pt % GRID);
568 let src = (base + (py % 24) * 24 + px % 24) * TD;
569 for d in 0..TD { x[pt * TD + d] += pos[src + d]; }
570 }
571 let (lw, lb) = (load_vec(m, "trunk.ln_pre.weight")?, load_vec(m, "trunk.ln_pre.bias")?);
572 layer_norm_rows(&mut x, &lw, &lb, T_EPS);
573 if let Some(d) = &dump { dump_f32(d, "trunk_pre", &x); }
574
575 let mut tst = StageTimer::new();
576 for l in 0..TL {
577 let b = format!("trunk.blocks.{l}.");
578 let _attn = crate::cpuprof::spans::span("trunk.attn");
579 let (nw, nb) = (load_vec(m, &format!("{b}norm1.weight"))?, load_vec(m, &format!("{b}norm1.bias"))?);
580 let mut h = x.clone();
581 layer_norm_rows(&mut h, &nw, &nb, T_EPS);
582 let dbg0 = dump.is_some() && l == 0;
583 if let (Some(d), true) = (&dump, dbg0) { dump_f32(d, "b0_ln1", &h); }
584 drop(_attn);
585 let qkv = lin(m, &format!("{b}attn.qkv.weight"))?;
586 let qb = load_vec(m, &format!("{b}attn.qkv.bias"))?;
587 let proj = lin(m, &format!("{b}attn.proj.weight"))?;
588 let pb = load_vec(m, &format!("{b}attn.proj.bias"))?;
589 let fc = load_vec(m, &format!("{b}attn.freqs_cis"))?;
590 tst.lap("trk_attn_ln");
591 let attn_out = if GLOBAL.contains(&l) {
592 let mut o = vec![0f32; np * TD];
593 let _pa = crate::cpuprof::spans::span("trunk.attn.run");
594 run_attn(&qkv, &qb, &proj, &pb, &h, &h, &h, np, np, TD, TH, Some(&fc), Mask::None, pool, &mut o);
595 drop(_pa);
596 o
597 } else {
598 let _pa = crate::cpuprof::spans::span("trunk.attn.run");
599 let o = self.vit_windows(&h, &qkv, &qb, &proj, &pb, &fc, pool)?;
600 drop(_pa);
601 o
602 };
603 if let (Some(d), true) = (&dump, dbg0) { dump_f32(d, "b0_attn", &attn_out); }
604 for (a, c) in x.iter_mut().zip(&attn_out) { *a += *c; }
605 tst.lap("trk_attn_run");
606 if let (Some(d), true) = (&dump, dbg0) { dump_f32(d, "b0_x2", &x); }
607 let (nw, nb) = (load_vec(m, &format!("{b}norm2.weight"))?, load_vec(m, &format!("{b}norm2.bias"))?);
611 let mut h = x.clone();
612 layer_norm_rows(&mut h, &nw, &nb, T_EPS);
613 if let (Some(d), true) = (&dump, dbg0) { dump_f32(d, "b0_ln2", &h); }
614 let f1 = lin(m, &format!("{b}mlp.fc1.weight"))?;
615 let f1b = load_vec(m, &format!("{b}mlp.fc1.bias"))?;
616 let f2 = lin(m, &format!("{b}mlp.fc2.weight"))?;
617 let f2b = load_vec(m, &format!("{b}mlp.fc2.bias"))?;
618 let mut ff = vec![0f32; np * TI];
619 f1.matmat(&h, np, &mut ff, pool);
620 add_bias(&mut ff, TI, &f1b);
621 gelu_slice(&mut ff);
622 let mut fo = vec![0f32; np * TD];
623 f2.matmat(&ff, np, &mut fo, pool);
624 add_bias(&mut fo, TD, &f2b);
625 if let (Some(d), true) = (&dump, dbg0) { dump_f32(d, "b0_mlp", &fo); }
626 for (a, c) in x.iter_mut().zip(&fo) { *a += *c; }
627 tst.lap("trk_mlp");
628 if let (Some(d), true) = (&dump, dlayers.as_ref().map_or(true, |ls| ls.contains(&l))) {
629 dump_f32(d, &format!("trunk_b{l}"), &x);
630 }
631 }
632 Ok(x)
633 }
634
635 fn vit_windows(
637 &self,
638 h: &[f32],
639 qkv: &QTensor,
640 qb: &[f32],
641 proj: &QTensor,
642 pb: &[f32],
643 fc: &[f32],
644 pool: Option<&Pool>,
645 ) -> Result<Vec<f32>> {
646 let np = GRID * GRID;
647 let nw = GRID / WIN;
648 let wl = WIN * WIN;
649 let mut wh = vec![0f32; wl * TD];
650 let mut ao = vec![0f32; wl * TD];
651 let mut out = vec![0f32; np * TD];
652 for wy in 0..nw {
653 for wx in 0..nw {
654 for y in 0..WIN {
655 for x2 in 0..WIN {
656 let s = ((wy * WIN + y) * GRID + wx * WIN + x2) * TD;
657 let d = (y * WIN + x2) * TD;
658 wh[d..d + TD].copy_from_slice(&h[s..s + TD]);
659 }
660 }
661 run_attn(qkv, qb, proj, pb, &wh, &wh, &wh, wl, wl, TD, TH, Some(fc), Mask::None, pool, &mut ao);
662 for y in 0..WIN {
663 for x2 in 0..WIN {
664 let d = ((wy * WIN + y) * GRID + wx * WIN + x2) * TD;
665 let s = (y * WIN + x2) * TD;
666 out[d..d + TD].copy_from_slice(&ao[s..s + TD]);
667 }
668 }
669 }
670 }
671 Ok(out)
672 }
673
674 fn fpn(&self, trunk: &[f32], pool: Option<&Pool>) -> Result<Vec<Vec<f32>>> {
679 fpn_levels(&self.model, self.fpn_prefix, trunk, pool, 4)
680 }
681
682 #[allow(clippy::too_many_arguments)]
685 fn detr_encoder(
686 &self,
687 vision: &[f32],
688 pos: &[f32],
689 text: &[f32],
690 am: &[u8],
691 pool: Option<&Pool>,
692 ) -> Result<Vec<f32>> {
693 let m = &self.model;
694 let n = vision.len() / FD;
695 let mut xs = vision.to_vec();
696 let mut km = vec![0f32; SS];
697 for (k, &v) in am.iter().enumerate() { km[k] = if v == 0 { NEG } else { 0.0 }; }
698 let mut tse = StageTimer::new();
699 for l in 0..NL {
700 let b = format!("det.transformer.encoder.layers.{l}.");
701 let (nw, nb) = (load_vec(m, &format!("{b}norm1.weight"))?, load_vec(m, &format!("{b}norm1.bias"))?);
703 let mut h = xs.clone();
704 layer_norm_rows(&mut h, &nw, &nb, D_EPS);
705 let hp: Vec<f32> = h
706 .chunks_exact(FD)
707 .zip(pos.chunks_exact(FD))
708 .flat_map(|(a, c)| a.iter().zip(c).map(|(x, y)| x + y))
709 .collect();
710 let qkv = lin(m, &format!("{b}self_attn.in_proj_weight"))?;
711 let ib = load_vec(m, &format!("{b}self_attn.in_proj_bias"))?;
712 let op = lin(m, &format!("{b}self_attn.out_proj.weight"))?;
713 let ob = load_vec(m, &format!("{b}self_attn.out_proj.bias"))?;
714 let mut ao = vec![0f32; n * FD];
715 run_attn(&qkv, &ib, &op, &ob, &hp, &hp, &h, n, n, FD, FH, None, Mask::None, pool, &mut ao);
716 for (a, c) in xs.iter_mut().zip(&ao) { *a += *c; }
717 tse.lap("enc_self_attn");
718 let (nw, nb) = (load_vec(m, &format!("{b}norm2.weight"))?, load_vec(m, &format!("{b}norm2.bias"))?);
720 let mut h = xs.clone();
721 layer_norm_rows(&mut h, &nw, &nb, D_EPS);
722 let qkv = lin(m, &format!("{b}cross_attn_image.in_proj_weight"))?;
723 let ib = load_vec(m, &format!("{b}cross_attn_image.in_proj_bias"))?;
724 let op = lin(m, &format!("{b}cross_attn_image.out_proj.weight"))?;
725 let ob = load_vec(m, &format!("{b}cross_attn_image.out_proj.bias"))?;
726 let mut ao = vec![0f32; n * FD];
727 run_attn(&qkv, &ib, &op, &ob, &h, text, text, n, SS, FD, FH, None, Mask::Key(&km), pool, &mut ao);
728 for (a, c) in xs.iter_mut().zip(&ao) { *a += *c; }
729 tse.lap("enc_cross_text");
730 let (nw, nb) = (load_vec(m, &format!("{b}norm3.weight"))?, load_vec(m, &format!("{b}norm3.bias"))?);
732 let mut h = xs.clone();
733 layer_norm_rows(&mut h, &nw, &nb, D_EPS);
734 let l1 = lin(m, &format!("{b}linear1.weight"))?;
735 let l1b = load_vec(m, &format!("{b}linear1.bias"))?;
736 let l2 = lin(m, &format!("{b}linear2.weight"))?;
737 let l2b = load_vec(m, &format!("{b}linear2.bias"))?;
738 let mut mid = vec![0f32; n * FI];
739 l1.matmat(&h, n, &mut mid, pool);
740 add_bias(&mut mid, FI, &l1b);
741 mid.iter_mut().for_each(|v| *v = v.max(0.0));
742 let mut fo = vec![0f32; n * FD];
743 l2.matmat(&mid, n, &mut fo, pool);
744 add_bias(&mut fo, FD, &l2b);
745 for (a, c) in xs.iter_mut().zip(&fo) { *a += *c; }
746 tse.lap("enc_ffn");
747 }
748 Ok(xs)
749 }
750
751 fn detr_decoder(
760 &self,
761 enc: &[f32],
762 pos: &[f32],
763 text: &[f32],
764 am: &[u8],
765 pool: Option<&Pool>,
766 ) -> Result<(Vec<f32>, Vec<[f32; 4]>, f32)> {
767 let m = &self.model;
768 let n = enc.len() / FD;
769 let db = "det.transformer.decoder.";
770 let query = load_vec(m, &format!("{db}query_embed.weight"))?;
771 let presence = load_vec(m, &format!("{db}presence_token.weight"))?;
772 let ref0 = load_vec(m, &format!("{db}reference_points.weight"))?;
773 let mut hidden = vec![0f32; (NQ + 1) * FD];
774 hidden[..FD].copy_from_slice(&presence);
775 hidden[FD..].copy_from_slice(&query);
776 let mut refs: Vec<[f32; 4]> = ref0
777 .chunks_exact(4)
778 .map(|c| [sigm(c[0]), sigm(c[1]), sigm(c[2]), sigm(c[3])])
779 .collect();
780 let mut km = vec![0f32; SS];
781 for (k, &v) in am.iter().enumerate() { km[k] = if v == 0 { NEG } else { 0.0 }; }
782 let mut tsd = StageTimer::new();
783 let rh0 = lin(m, &format!("{db}ref_point_head.layers.0.weight"))?;
785 let rh0b = load_vec(m, &format!("{db}ref_point_head.layers.0.bias"))?;
786 let rh1 = lin(m, &format!("{db}ref_point_head.layers.1.weight"))?;
787 let rh1b = load_vec(m, &format!("{db}ref_point_head.layers.1.bias"))?;
788 let rx = DecoderMlp::load(m, &format!("{db}boxRPB_embed_x."))?;
789 let ry = DecoderMlp::load(m, &format!("{db}boxRPB_embed_y."))?;
790 let bbox = DecoderMlp3::load3(m, &format!("{db}bbox_embed."))?;
791 let olnw = load_vec(m, &format!("{db}norm.weight"))?;
792 let olnb = load_vec(m, &format!("{db}norm.bias"))?;
793 let pres_w = load_vec(m, &format!("{db}presence_token_out_norm.weight"))?;
794 let pres_b = load_vec(m, &format!("{db}presence_token_out_norm.bias"))?;
795 let pres_head = DecoderMlp3::load3(m, &format!("{db}presence_token_head."))?;
796 let key = add_rows(enc, pos); let mut inter: Vec<Vec<f32>> = Vec::with_capacity(NL);
798 for l in 0..NL {
799 let b = format!("{db}layers.{l}.");
800 let sine = sine_boxes(&refs);
802 let mut qp = vec![0f32; NQ * FD];
803 rh0.matmat(&sine, NQ, &mut qp, pool);
804 add_bias(&mut qp, FD, &rh0b);
805 qp.iter_mut().for_each(|v| *v = v.max(0.0)); let mut qp2 = vec![0f32; NQ * FD];
807 rh1.matmat(&qp, NQ, &mut qp2, pool);
808 add_bias(&mut qp2, FD, &rh1b);
809 let qp = qp2;
810 let mut qp_full = vec![0f32; (NQ + 1) * FD];
811 qp_full[FD..].copy_from_slice(&qp);
812 let rpb = rpb_bias(&refs, &rx, &ry, n, pool)?;
813 let hqp = add_rows(&hidden, &qp_full);
815 let qkv = lin(m, &format!("{b}self_attn.in_proj_weight"))?;
816 let ib = load_vec(m, &format!("{b}self_attn.in_proj_bias"))?;
817 let op = lin(m, &format!("{b}self_attn.out_proj.weight"))?;
818 let ob = load_vec(m, &format!("{b}self_attn.out_proj.bias"))?;
819 let mut ao = vec![0f32; (NQ + 1) * FD];
820 run_attn(&qkv, &ib, &op, &ob, &hqp, &hqp, &hidden, NQ + 1, NQ + 1, FD, FH, None, Mask::None, pool, &mut ao);
821 for (a, c) in hidden.iter_mut().zip(&ao) { *a += *c; }
822 tsd.lap("dec_self_attn");
823 let (nw, nb) = (load_vec(m, &format!("{b}norm1.weight"))?, load_vec(m, &format!("{b}norm1.bias"))?);
824 layer_norm_rows(&mut hidden, &nw, &nb, D_EPS);
825 let hqp = add_rows(&hidden, &qp_full);
827 let qkv = lin(m, &format!("{b}ca_text.in_proj_weight"))?;
828 let ib = load_vec(m, &format!("{b}ca_text.in_proj_bias"))?;
829 let op = lin(m, &format!("{b}ca_text.out_proj.weight"))?;
830 let ob = load_vec(m, &format!("{b}ca_text.out_proj.bias"))?;
831 let mut ao = vec![0f32; (NQ + 1) * FD];
832 run_attn(&qkv, &ib, &op, &ob, &hqp, text, text, NQ + 1, SS, FD, FH, None, Mask::Key(&km), pool, &mut ao);
833 for (a, c) in hidden.iter_mut().zip(&ao) { *a += *c; }
834 tsd.lap("dec_cross_text");
835 let (nw, nb) = (load_vec(m, &format!("{b}catext_norm.weight"))?, load_vec(m, &format!("{b}catext_norm.bias"))?);
836 layer_norm_rows(&mut hidden, &nw, &nb, D_EPS);
837 let hqp = add_rows(&hidden, &qp_full);
839 let qkv = lin(m, &format!("{b}cross_attn.in_proj_weight"))?;
840 let ib = load_vec(m, &format!("{b}cross_attn.in_proj_bias"))?;
841 let op = lin(m, &format!("{b}cross_attn.out_proj.weight"))?;
842 let ob = load_vec(m, &format!("{b}cross_attn.out_proj.bias"))?;
843 let mut ao = vec![0f32; (NQ + 1) * FD];
844 run_attn(&qkv, &ib, &op, &ob, &hqp, &key, enc, NQ + 1, n, FD, FH, None, Mask::Head(&rpb), pool, &mut ao);
845 for (a, c) in hidden.iter_mut().zip(&ao) { *a += *c; }
846 tsd.lap("dec_cross_enc");
847 let (nw, nb) = (load_vec(m, &format!("{b}norm2.weight"))?, load_vec(m, &format!("{b}norm2.bias"))?);
848 layer_norm_rows(&mut hidden, &nw, &nb, D_EPS);
849 let l1 = lin(m, &format!("{b}linear1.weight"))?;
851 let l1b = load_vec(m, &format!("{b}linear1.bias"))?;
852 let l2 = lin(m, &format!("{b}linear2.weight"))?;
853 let l2b = load_vec(m, &format!("{b}linear2.bias"))?;
854 let mut mid = vec![0f32; (NQ + 1) * FI];
855 l1.matmat(&hidden, NQ + 1, &mut mid, pool);
856 add_bias(&mut mid, FI, &l1b);
857 mid.iter_mut().for_each(|v| *v = v.max(0.0));
858 let mut fo = vec![0f32; (NQ + 1) * FD];
859 l2.matmat(&mid, NQ + 1, &mut fo, pool);
860 add_bias(&mut fo, FD, &l2b);
861 for (a, c) in hidden.iter_mut().zip(&fo) { *a += *c; }
862 tsd.lap("dec_ffn");
863 let (nw, nb) = (load_vec(m, &format!("{b}norm3.weight"))?, load_vec(m, &format!("{b}norm3.bias"))?);
864 layer_norm_rows(&mut hidden, &nw, &nb, D_EPS);
865 let mut qh = hidden[FD..].to_vec();
867 layer_norm_rows(&mut qh, &olnw, &olnb, D_EPS);
868 inter.push(qh.clone());
869 let d = bbox.forward(&qh, NQ, pool);
870 for (r, dd) in refs.iter_mut().zip(d.chunks_exact(4)) {
871 for k in 0..4 { r[k] = sigm(inv_sigmoid(r[k]) + dd[k]); }
872 }
873 tsd.lap("dec_box_ref");
874 }
875 let dec = inter.iter().flat_map(|v| v.iter().copied()).collect();
876 let mut ph = hidden[0..FD].to_vec();
881 layer_norm_rows(&mut ph, &pres_w, &pres_b, D_EPS);
882 let pl = pres_head.forward(&ph, 1, pool);
883 let presence_logit = pl[0].clamp(-10.0, 10.0);
884 Ok((dec, refs, presence_logit))
885 }
886
887 fn score(
896 &self,
897 text: &[f32],
898 am: &[u8],
899 dec: &[f32],
900 pool: Option<&Pool>,
901 ) -> Result<Vec<f32>> {
902 let m = &self.model;
903 let mlp = DecoderMlp::load(m, "det.scoring.prompt_mlp.")?;
904 let mut h = mlp.forward(text, SS, pool, FD);
905 for (a, b) in h.iter_mut().zip(text) {
906 *a += *b;
907 }
908 let (ow, ob) = (
909 load_vec(m, "det.scoring.prompt_mlp.out_norm.weight")?,
910 load_vec(m, "det.scoring.prompt_mlp.out_norm.bias")?,
911 );
912 layer_norm_rows(&mut h, &ow, &ob, D_EPS);
913 let n_valid = am.iter().filter(|&&v| v == 1).count().max(1) as f32;
915 let mut pooled = vec![0f32; FD];
916 for (i, &v) in am.iter().enumerate().take(SS) {
917 if v == 1 {
918 for d in 0..FD {
919 pooled[d] += h[i * FD + d];
920 }
921 }
922 }
923 for v in pooled.iter_mut() {
924 *v /= n_valid;
925 }
926 let pt = lin(m, "det.scoring.prompt_proj.weight")?;
927 let pb = load_vec(m, "det.scoring.prompt_proj.bias")?;
928 let mut tp = vec![0f32; FD];
929 pt.matmat(&pooled, 1, &mut tp, pool);
930 add_bias(&mut tp, FD, &pb);
931 let nq = dec.len() / FD;
932 let hs = lin(m, "det.scoring.hs_proj.weight")?;
933 let hb = load_vec(m, "det.scoring.hs_proj.bias")?;
934 let mut q = vec![0f32; nq * FD];
935 hs.matmat(dec, nq, &mut q, pool);
936 add_bias(&mut q, FD, &hb);
937 let scale = 1.0 / (FD as f32).sqrt();
938 Ok(q
939 .chunks(FD)
940 .map(|r| {
941 let mut acc = 0f32;
942 for d in 0..FD {
943 acc += r[d] * tp[d];
944 }
945 (acc * scale).clamp(-12.0, 12.0)
946 })
947 .collect())
948 }
949
950 #[allow(clippy::too_many_arguments)]
956 fn mask_decoder(
957 &self,
958 queries: &[f32],
959 enc: &[f32],
960 fpn: &[Vec<f32>],
961 text: &[f32],
962 am: &[u8],
963 pool: Option<&Pool>,
964 ) -> Result<Vec<f32>> {
965 let m = &self.model;
966 let db = "det.seghead.";
967 let n = enc.len() / FD;
968 let mut km = vec![0f32; SS];
969 for (k, &v) in am.iter().enumerate() { km[k] = if v == 0 { NEG } else { 0.0 }; }
970 let nw = load_vec(m, &format!("{db}cross_attn_norm.weight"))?;
971 let nb = load_vec(m, &format!("{db}cross_attn_norm.bias"))?;
972 let mut h = enc.to_vec();
973 layer_norm_rows(&mut h, &nw, &nb, D_EPS);
974 let qkv = lin(m, &format!("{db}cross_attend_prompt.in_proj_weight"))?;
975 let ib = load_vec(m, &format!("{db}cross_attend_prompt.in_proj_bias"))?;
976 let op = lin(m, &format!("{db}cross_attend_prompt.out_proj.weight"))?;
977 let ob = load_vec(m, &format!("{db}cross_attend_prompt.out_proj.bias"))?;
978 let mut tsm = StageTimer::new();
979 let mut e = enc.to_vec();
980 let mut ao = vec![0f32; n * FD];
981 run_attn(&qkv, &ib, &op, &ob, &h, text, text, n, SS, FD, FH, None, Mask::Key(&km), pool, &mut ao);
982 for (a, c) in e.iter_mut().zip(&ao) { *a += *c; }
983 tsm.lap("msk_cross_attn");
984 let l72 = rows_to_chw(&e, GRID, GRID);
986 let sizes = [(144usize, 144usize), (288, 288)];
987 let mut prev = l72;
988 for (li, &(sh, sw)) in sizes.iter().enumerate() {
989 let _pp = crate::cpuprof::spans::span("mask.pixdec");
990 prev = upsample_nearest(&prev, FD, sh / 2, sh / 2, sh, sw);
991 let fw = &fpn[1 - li]; for (a, b) in prev.iter_mut().zip(fw.iter()) { *a += *b; }
993 let cw = load_vec(m, &format!("{db}pixel_decoder.conv_layers.{li}.weight"))?;
994 let cb = load_vec(m, &format!("{db}pixel_decoder.conv_layers.{li}.bias"))?;
995 prev = conv2d(&cw, &cb, &prev, FD, sh, sw, 3, 3, 1, 1, pool);
996 let gw = load_vec(m, &format!("{db}pixel_decoder.norms.{li}.weight"))?;
997 let gb = load_vec(m, &format!("{db}pixel_decoder.norms.{li}.bias"))?;
998 group_norm(&mut prev, 8, &gw, &gb, 1e-5);
999 prev.iter_mut().for_each(|v| *v = v.max(0.0));
1000 drop(_pp);
1001 }
1002 tsm.lap("msk_pixel_dec");
1003 let iw = load_vec(m, &format!("{db}instance_seg_head.weight"))?;
1004 let ib2 = load_vec(m, &format!("{db}instance_seg_head.bias"))?;
1005 let inst = conv2d(&iw, &ib2, &prev, FD, MH, MH, 1, 1, 1, 0, pool);
1006 let mut q = queries.to_vec();
1008 for i in 0..3 {
1009 let w = lin(m, &format!("{db}mask_predictor.mask_embed.layers.{i}.weight"))?;
1010 let b = load_vec(m, &format!("{db}mask_predictor.mask_embed.layers.{i}.bias"))?;
1011 let mut o = vec![0f32; NQ * FD];
1012 w.matmat(&q, NQ, &mut o, pool);
1013 add_bias(&mut o, FD, &b);
1014 if i < 2 { o.iter_mut().for_each(|v| *v = v.max(0.0)); }
1015 q = o;
1016 }
1017 tsm.lap("msk_inst_head");
1018 let hw = MH * MH;
1019 let mut out = vec![0f32; NQ * hw];
1020 let ob2 = out.as_mut_ptr() as usize;
1021 let _pe = crate::cpuprof::spans::span("mask.einsum");
1022 let run = |s: usize, e: usize| {
1023 let o = unsafe { std::slice::from_raw_parts_mut(ob2 as *mut f32, NQ * hw) };
1024 for qi in s..e {
1025 let qq = &q[qi * FD..(qi + 1) * FD];
1026 let dst = &mut o[qi * hw..(qi + 1) * hw];
1027 for c in 0..FD {
1028 let wgt = qq[c];
1029 if wgt == 0.0 { continue; }
1030 let ic = &inst[c * hw..(c + 1) * hw];
1031 for p in 0..hw { dst[p] += wgt * ic[p]; }
1032 }
1033 }
1034 };
1035 match pool { Some(p) => p.run_rows(NQ, &run), None => run(0, NQ) }
1036 drop(_pe);
1037 tsm.lap("msk_einsum");
1038 Ok(out)
1039 }
1040}
1041
1042pub(crate) fn fpn_levels(
1047 m: &Arc<CmfModel>,
1048 p: &str,
1049 trunk: &[f32],
1050 pool: Option<&Pool>,
1051 n_levels: usize,
1052) -> Result<Vec<Vec<f32>>> {
1053 let hidden = rows_to_chw(trunk, GRID, GRID); let scales = [4.0f32, 2.0, 1.0, 0.5];
1055 let mut levels = Vec::with_capacity(n_levels);
1056 let _p = crate::cpuprof::spans::span("fpn.levels");
1057 for (i, &s) in scales.iter().enumerate().take(n_levels) {
1058 let (mut ci, mut ch, mut cw) = (TD, GRID, GRID);
1059 let mut cur = hidden.clone();
1060 match s {
1061 4.0 => {
1062 let w0 = load_vec(m, &format!("{p}.{i}.dconv_2x2_0.weight"))?;
1063 let b0 = load_vec(m, &format!("{p}.{i}.dconv_2x2_0.bias"))?;
1064 cur = convt2x2(&w0, &b0, &cur, ci, ch, cw, pool);
1065 (ch, cw, ci) = (ch * 2, cw * 2, b0.len());
1066 gelu_slice(&mut cur);
1067 let w1 = load_vec(m, &format!("{p}.{i}.dconv_2x2_1.weight"))?;
1068 let b1 = load_vec(m, &format!("{p}.{i}.dconv_2x2_1.bias"))?;
1069 cur = convt2x2(&w1, &b1, &cur, ci, ch, cw, pool);
1070 (ch, cw, ci) = (ch * 2, cw * 2, b1.len());
1071 }
1072 2.0 => {
1073 let w0 = load_vec(m, &format!("{p}.{i}.dconv_2x2.weight"))?;
1074 let b0 = load_vec(m, &format!("{p}.{i}.dconv_2x2.bias"))?;
1075 cur = convt2x2(&w0, &b0, &cur, ci, ch, cw, pool);
1076 (ch, cw, ci) = (ch * 2, cw * 2, b0.len());
1077 }
1078 1.0 => {}
1079 _ => {
1080 cur = maxpool2(&cur, ci, ch, cw);
1081 (ch, cw) = (ch / 2, cw / 2);
1082 }
1083 }
1084 let w1 = load_vec(m, &format!("{p}.{i}.conv_1x1.weight"))?;
1085 let b1 = load_vec(m, &format!("{p}.{i}.conv_1x1.bias"))?;
1086 cur = conv2d(&w1, &b1, &cur, ci, ch, cw, 1, 1, 1, 0, pool);
1087 ci = b1.len();
1088 let w3 = load_vec(m, &format!("{p}.{i}.conv_3x3.weight"))?;
1089 let b3 = load_vec(m, &format!("{p}.{i}.conv_3x3.bias"))?;
1090 cur = conv2d(&w3, &b3, &cur, ci, ch, cw, 3, 3, 1, 1, pool);
1091 levels.push(cur);
1092 }
1093 Ok(levels)
1094}
1095
1096struct DecoderMlp {
1100 w0: QTensor,
1101 b0: Vec<f32>,
1102 w1: QTensor,
1103 b1: Vec<f32>,
1104}
1105
1106impl DecoderMlp {
1107 fn load(m: &Arc<CmfModel>, prefix: &str) -> Result<Self> {
1108 Ok(Self {
1109 w0: lin(m, &format!("{prefix}layers.0.weight"))?,
1110 b0: load_vec(m, &format!("{prefix}layers.0.bias"))?,
1111 w1: lin(m, &format!("{prefix}layers.1.weight"))?,
1112 b1: load_vec(m, &format!("{prefix}layers.1.bias"))?,
1113 })
1114 }
1115 fn forward(&self, x: &[f32], rows: usize, pool: Option<&Pool>, out_w: usize) -> Vec<f32> {
1116 let mut h = vec![0f32; rows * self.b0.len()];
1117 self.w0.matmat(x, rows, &mut h, pool);
1118 add_bias(&mut h, self.b0.len(), &self.b0);
1119 h.iter_mut().for_each(|v| *v = v.max(0.0));
1120 let mut o = vec![0f32; rows * out_w];
1121 self.w1.matmat(&h, rows, &mut o, pool);
1122 add_bias(&mut o, out_w, &self.b1);
1123 o
1124 }
1125}
1126
1127struct DecoderMlp3 {
1129 w0: QTensor,
1130 b0: Vec<f32>,
1131 w1: QTensor,
1132 b1: Vec<f32>,
1133 w2: QTensor,
1134 b2: Vec<f32>,
1135}
1136
1137impl DecoderMlp3 {
1138 fn load3(m: &Arc<CmfModel>, prefix: &str) -> Result<Self> {
1139 Ok(Self {
1140 w0: lin(m, &format!("{prefix}layers.0.weight"))?,
1141 b0: load_vec(m, &format!("{prefix}layers.0.bias"))?,
1142 w1: lin(m, &format!("{prefix}layers.1.weight"))?,
1143 b1: load_vec(m, &format!("{prefix}layers.1.bias"))?,
1144 w2: lin(m, &format!("{prefix}layers.2.weight"))?,
1145 b2: load_vec(m, &format!("{prefix}layers.2.bias"))?,
1146 })
1147 }
1148 fn forward(&self, x: &[f32], rows: usize, pool: Option<&Pool>) -> Vec<f32> {
1149 let mut d = vec![0f32; rows * self.b0.len()];
1150 self.w0.matmat(x, rows, &mut d, pool);
1151 add_bias(&mut d, self.b0.len(), &self.b0);
1152 d.iter_mut().for_each(|v| *v = v.max(0.0));
1153 let mut d2 = vec![0f32; rows * self.b1.len()];
1154 self.w1.matmat(&d, rows, &mut d2, pool);
1155 add_bias(&mut d2, self.b1.len(), &self.b1);
1156 d2.iter_mut().for_each(|v| *v = v.max(0.0));
1157 let mut o = vec![0f32; rows * self.b2.len()];
1158 self.w2.matmat(&d2, rows, &mut o, pool);
1159 add_bias(&mut o, self.b2.len(), &self.b2);
1160 o
1161 }
1162}
1163
1164pub(crate) fn sigm(x: f32) -> f32 {
1165 1.0 / (1.0 + (-x).exp())
1166}
1167
1168fn inv_sigmoid(x: f32) -> f32 {
1169 let x = x.clamp(0.0, 1.0);
1170 ((x.max(1e-3)) / ((1.0 - x).max(1e-3))).ln()
1171}
1172
1173fn xyxy(boxes: &[[f32; 4]]) -> Vec<f32> {
1174 let mut out = vec![0f32; boxes.len() * 4];
1175 for (i, b) in boxes.iter().enumerate() {
1176 out[i * 4] = b[0] - 0.5 * b[2];
1177 out[i * 4 + 1] = b[1] - 0.5 * b[3];
1178 out[i * 4 + 2] = b[0] + 0.5 * b[2];
1179 out[i * 4 + 3] = b[1] + 0.5 * b[3];
1180 }
1181 out
1182}
1183
1184pub(crate) fn lin(m: &Arc<CmfModel>, name: &str) -> Result<QTensor> {
1185 let _p = crate::cpuprof::spans::span("qtensor.wrap");
1186 QTensor::from_model(m, name).map_err(|e| anyhow!(e))
1187}
1188
1189pub(crate) fn load_vec(m: &Arc<CmfModel>, name: &str) -> Result<Vec<f32>> {
1191 let _p = crate::cpuprof::spans::span("load.decode");
1192 let e = m.tensor(name).ok_or_else(|| anyhow!("tensor '{name}' not found"))?;
1193 if matches!(e.dtype, TensorDtype::U8) { return Err(anyhow!("'{name}' is u8")); }
1194 let bytes = m.entry_bytes(e);
1195 let mut dst = vec![0f32; e.n_elems()];
1196 dequant_tensor(e, bytes, &mut dst).map_err(|er| anyhow!("{name}: {er}"))?;
1197 Ok(dst)
1198}
1199
1200pub(crate) fn gelu_slice(v: &mut [f32]) {
1202 let _p = crate::cpuprof::spans::span("op.gelu");
1203 v.iter_mut().for_each(|x| *x = gelu_erf(*x));
1204}
1205
1206pub(crate) fn add_bias(out: &mut [f32], cols: usize, bias: &[f32]) {
1208 debug_assert_eq!(bias.len(), cols);
1209 for r in out.chunks_exact_mut(cols) {
1210 for (v, b) in r.iter_mut().zip(bias) { *v += *b; }
1211 }
1212}
1213
1214pub(crate) fn layer_norm_rows(x: &mut [f32], w: &[f32], b: &[f32], eps: f32) {
1216 let _p = crate::cpuprof::spans::span("op.layernorm");
1217 let d = w.len();
1218 for r in x.chunks_exact_mut(d) {
1219 let mean = r.iter().sum::<f32>() / d as f32;
1220 let var = r.iter().map(|v| (v - mean) * (v - mean)).sum::<f32>() / d as f32;
1221 let inv = 1.0 / (var + eps).sqrt();
1222 for (v, (&ww, &bb)) in r.iter_mut().zip(w.iter().zip(b.iter())) {
1223 *v = (*v - mean) * inv * ww + bb;
1224 }
1225 }
1226}
1227
1228fn apply_rope(buf: &mut [f32], n: usize, stride: usize, width: usize, fc: &[f32]) {
1234 let npairs = 32;
1235 debug_assert_eq!(fc.len(), n * npairs * 2);
1236 let blocks = width / 64;
1237 for i in 0..n {
1238 let f = &fc[i * npairs * 2..(i + 1) * npairs * 2];
1239 let row = &mut buf[i * stride..i * stride + width];
1240 for b in 0..blocks {
1241 let base = b * 64;
1242 for j in 0..npairs {
1243 let (c, s) = (f[j * 2], f[j * 2 + 1]);
1244 let (a, d) = (row[base + j * 2], row[base + j * 2 + 1]);
1245 row[base + j * 2] = a * c - d * s;
1246 row[base + j * 2 + 1] = d * c + a * s;
1247 }
1248 }
1249 }
1250}
1251
1252struct CoopBypass;
1257
1258impl CoopBypass {
1259 fn new() -> Self {
1260 #[cfg(feature = "gpu")]
1261 crate::gpu_wgpu::COOP_BYPASS.with(|b| b.set(true));
1262 Self
1263 }
1264}
1265
1266impl Drop for CoopBypass {
1267 fn drop(&mut self) {
1268 #[cfg(feature = "gpu")]
1269 crate::gpu_wgpu::COOP_BYPASS.with(|b| b.set(false));
1270 }
1271}
1272
1273#[allow(clippy::too_many_arguments)]
1284fn attn_core_gpu(
1285 qkv_q: &[f32],
1286 k: &[f32],
1287 v: &[f32],
1288 n_q: usize,
1289 n_k: usize,
1290 dim: usize,
1291 heads: usize,
1292 mask: Mask<'_>,
1293 scale: f32,
1294 out: &mut [f32],
1295) -> bool {
1296 #[cfg(feature = "gpu")]
1297 {
1298 if std::env::var("CMF_SAM3_GPU").as_deref() == Ok("0") {
1299 return false;
1300 }
1301 if !crate::gpu::enabled_here() {
1302 return false;
1303 }
1304 let hd = dim / heads;
1305 if hd == 0 || out.len() < n_q * dim {
1306 return false;
1307 }
1308 if n_q.saturating_mul(n_k).saturating_mul(hd) < (1 << 22) {
1311 return false;
1312 }
1313 let h3 = dim * 3;
1314 let _byp = CoopBypass::new();
1317 let mut qpk = vec![0f32; n_q * hd];
1318 let mut kpk = vec![0f32; n_k * hd];
1319 let mut vtp = vec![0f32; hd * n_k];
1320 let mut raw = vec![0f32; n_q * n_k];
1321 let mut outt = vec![0f32; n_q * hd];
1322 let mut acc = vec![0f32; n_q * dim];
1323 let qk_chunk = ((1usize << 22) / n_k.max(1)).max(1);
1328 for t in 0..heads {
1329 let off = t * hd;
1330 for i in 0..n_q {
1331 qpk[i * hd..(i + 1) * hd]
1332 .copy_from_slice(&qkv_q[i * h3 + off..i * h3 + off + hd]);
1333 }
1334 for j in 0..n_k {
1335 kpk[j * hd..(j + 1) * hd]
1336 .copy_from_slice(&k[j * h3 + dim + off..j * h3 + dim + off + hd]);
1337 }
1338 for d in 0..hd {
1339 let rowd = &mut vtp[d * n_k..(d + 1) * n_k];
1340 for (j, rv) in rowd.iter_mut().enumerate() {
1341 *rv = v[j * h3 + 2 * dim + off + d];
1342 }
1343 }
1344 let mut i0 = 0usize;
1346 while i0 < n_q {
1347 let i1 = (i0 + qk_chunk).min(n_q);
1348 if !crate::gpu_wgpu::gemm_nt_f32_transient(
1349 &qpk[i0 * hd..i1 * hd],
1350 &kpk,
1351 &mut raw[i0 * n_k..i1 * n_k],
1352 i1 - i0,
1353 hd,
1354 n_k,
1355 ) {
1356 return false;
1357 }
1358 i0 = i1;
1359 }
1360 let mut rowmax = vec![0f32; n_q];
1362 for i in 0..n_q {
1363 let base = i * n_k;
1364 let mut mx = f32::NEG_INFINITY;
1365 for j in 0..n_k {
1366 let mut val = raw[base + j] * scale;
1367 val += match mask {
1368 Mask::None => 0.0,
1369 Mask::Key(km) => km[j],
1370 Mask::Add(am) => am[i * n_k + j],
1371 Mask::Head(hm) => hm[(t * n_q + i) * n_k + j],
1372 };
1373 raw[base + j] = val;
1374 if val > mx {
1375 mx = val;
1376 }
1377 }
1378 rowmax[i] = mx;
1379 let mut sum = 0.0f32;
1380 for j in 0..n_k {
1381 let e = (raw[base + j] - mx).exp();
1382 raw[base + j] = e;
1383 sum += e;
1384 }
1385 let inv = 1.0 / sum;
1386 for j in 0..n_k {
1387 raw[base + j] *= inv;
1388 }
1389 }
1390 if !crate::gpu_wgpu::gemm_nt_f32_transient(&raw, &vtp, &mut outt, n_q, n_k, hd) {
1392 return false;
1393 }
1394 for i in 0..n_q {
1395 acc[i * dim + off..i * dim + off + hd]
1396 .copy_from_slice(&outt[i * hd..(i + 1) * hd]);
1397 }
1398 }
1399 out.copy_from_slice(&acc);
1400 true
1401 }
1402 #[cfg(not(feature = "gpu"))]
1403 {
1404 let _ = (qkv_q, k, v, n_q, n_k, dim, heads, mask, scale, out);
1405 false
1406 }
1407}
1408
1409#[allow(clippy::too_many_arguments)]
1414pub(crate) fn run_attn(
1415 w_qkv: &QTensor,
1416 qb: &[f32],
1417 w_o: &QTensor,
1418 ob: &[f32],
1419 xs_q: &[f32],
1420 xs_k: &[f32],
1421 xs_v: &[f32],
1422 n_q: usize,
1423 n_k: usize,
1424 dim: usize,
1425 heads: usize,
1426 rope: Option<&[f32]>,
1427 mask: Mask,
1428 pool: Option<&Pool>,
1429 out: &mut [f32],
1430) {
1431 let h3 = dim * 3;
1432 let hd = dim / heads;
1433 let scale = 1.0 / (hd as f32).sqrt();
1434 let _tp = std::time::Instant::now();
1435 let mut qkv_q = vec![0f32; n_q * h3];
1436 let _pg = crate::cpuprof::spans::span("attn.gemm");
1437 w_qkv.matmat(xs_q, n_q, &mut qkv_q, pool);
1438 add_bias(&mut qkv_q, h3, qb);
1439 let same_k = xs_k.as_ptr() == xs_q.as_ptr();
1440 let mut kbuf = Vec::new();
1441 if !same_k {
1442 kbuf = vec![0f32; n_k * h3];
1443 w_qkv.matmat(xs_k, n_k, &mut kbuf, pool);
1444 add_bias(&mut kbuf, h3, qb);
1445 }
1446 let same_v = xs_v.as_ptr() == xs_q.as_ptr();
1447 let mut vbuf = Vec::new();
1448 if !same_v {
1449 vbuf = vec![0f32; n_k * h3];
1450 w_qkv.matmat(xs_v, n_k, &mut vbuf, pool);
1451 add_bias(&mut vbuf, h3, qb);
1452 }
1453 drop(_pg);
1454 if let Some(fc) = rope {
1455 apply_rope(&mut qkv_q, n_q, h3, dim * 2, fc);
1457 if !same_k {
1458 apply_rope(&mut kbuf, n_k, h3, dim * 2, fc);
1459 }
1460 }
1461 ATT_PROJ_NS.fetch_add(_tp.elapsed().as_nanos() as u64, Ordering::Relaxed);
1462 let _tc = std::time::Instant::now();
1463 let qkv_k: &[f32] = if same_k { &qkv_q } else { &kbuf };
1464 let qkv_v: &[f32] = if same_v { &qkv_q } else { &vbuf };
1465 let mut attn = vec![0f32; n_q * dim];
1466 if attn_core_gpu(&qkv_q, qkv_k, qkv_v, n_q, n_k, dim, heads, mask, scale, &mut attn) {
1467 ATT_CORE_NS.fetch_add(_tc.elapsed().as_nanos() as u64, Ordering::Relaxed);
1468 let _tw = std::time::Instant::now();
1469 w_o.matmat(&attn, n_q, out, pool);
1470 add_bias(out, dim, ob);
1471 ATT_PROJ_NS.fetch_add(_tw.elapsed().as_nanos() as u64, Ordering::Relaxed);
1472 return;
1473 }
1474 let ab = attn.as_mut_ptr() as usize;
1475 let _pc = crate::cpuprof::spans::span("attn.core");
1476 let run = |s: usize, e: usize| {
1484 if s >= e {
1485 return;
1486 }
1487 let a = unsafe { std::slice::from_raw_parts_mut(ab as *mut f32, n_q * dim) };
1488 let rows = e - s;
1489 let mut scores = vec![0f32; rows * n_k];
1490 let prof = crate::cpuprof::spans::on();
1491 for t in 0..heads {
1492 let tq = prof.then(std::time::Instant::now);
1493 crate::qtensor::sgemm_public(
1495 rows,
1496 n_k,
1497 hd,
1498 scale,
1499 &qkv_q[s * h3 + t * hd..],
1500 h3,
1501 &qkv_k[dim + t * hd..],
1502 h3,
1503 true,
1504 &mut scores,
1505 n_k,
1506 );
1507 match mask {
1508 Mask::None => {}
1509 Mask::Key(km) => {
1510 for r in 0..rows {
1511 let sr = &mut scores[r * n_k..(r + 1) * n_k];
1512 for (v, &m) in sr.iter_mut().zip(km) {
1513 *v += m;
1514 }
1515 }
1516 }
1517 Mask::Add(am) => {
1518 for (r, i) in (s..e).enumerate() {
1519 let sr = &mut scores[r * n_k..(r + 1) * n_k];
1520 for (v, &m) in sr.iter_mut().zip(&am[i * n_k..(i + 1) * n_k]) {
1521 *v += m;
1522 }
1523 }
1524 }
1525 Mask::Head(hm) => {
1526 for (r, i) in (s..e).enumerate() {
1527 let base = (t * n_q + i) * n_k;
1528 let sr = &mut scores[r * n_k..(r + 1) * n_k];
1529 for (v, &m) in sr.iter_mut().zip(&hm[base..base + n_k]) {
1530 *v += m;
1531 }
1532 }
1533 }
1534 }
1535 let tm = prof.then(std::time::Instant::now);
1536 for r in 0..rows {
1541 let sr = &mut scores[r * n_k..(r + 1) * n_k];
1542 let mut mx = f32::NEG_INFINITY;
1543 for &v in sr.iter() {
1544 if v > mx {
1545 mx = v;
1546 }
1547 }
1548 let mut sum = 0.0f32;
1549 for v in sr.iter_mut() {
1550 *v = (*v - mx).exp();
1551 sum += *v;
1552 }
1553 let inv = 1.0 / sum;
1554 for v in sr.iter_mut() {
1555 *v *= inv;
1556 }
1557 }
1558 let ta = prof.then(std::time::Instant::now);
1559 crate::qtensor::sgemm_public(
1561 rows,
1562 hd,
1563 n_k,
1564 1.0,
1565 &scores,
1566 n_k,
1567 &qkv_v[2 * dim + t * hd..],
1568 h3,
1569 false,
1570 &mut a[s * dim + t * hd..],
1571 dim,
1572 );
1573 if let (Some(t0), Some(t1), Some(t2)) = (tq, tm, ta) {
1574 let t3 = std::time::Instant::now();
1575 crate::cpuprof::spans::add("attn.qk", t1 - t0);
1576 crate::cpuprof::spans::add("attn.sm", t2 - t1);
1577 crate::cpuprof::spans::add("attn.av", t3 - t2);
1578 }
1579 }
1580 };
1581 match pool { Some(p) => p.run_rows(n_q, &run), None => run(0, n_q) }
1582 drop(_pc);
1583 let _pg = crate::cpuprof::spans::span("attn.gemm");
1584 ATT_CORE_NS.fetch_add(_tc.elapsed().as_nanos() as u64, Ordering::Relaxed);
1585 let _tw = std::time::Instant::now();
1586 w_o.matmat(&attn, n_q, out, pool);
1587 add_bias(out, dim, ob);
1588 ATT_PROJ_NS.fetch_add(_tw.elapsed().as_nanos() as u64, Ordering::Relaxed);
1589}
1590
1591pub(crate) fn conv2d(
1594 w: &[f32],
1595 b: &[f32],
1596 x: &[f32],
1597 ci: usize,
1598 h: usize,
1599 wid: usize,
1600 kh: usize,
1601 kw: usize,
1602 stride: usize,
1603 pad: usize,
1604 pool: Option<&Pool>,
1605) -> Vec<f32> {
1606 let co = b.len();
1607 debug_assert_eq!(w.len(), co * ci * kh * kw);
1608 debug_assert_eq!(x.len(), ci * h * wid);
1609 let oh = (h + 2 * pad - kh) / stride + 1;
1610 let ow = (wid + 2 * pad - kw) / stride + 1;
1611 let _p = crate::cpuprof::spans::span("op.conv2d");
1612 let mut out = vec![0f32; co * oh * ow];
1613 let ob = out.as_mut_ptr() as usize;
1614 let run = |s: usize, e: usize| {
1621 let o = unsafe { std::slice::from_raw_parts_mut(ob as *mut f32, co * oh * ow) };
1622 for r in s..e {
1623 let c_o = r / oh;
1624 let oy = r % oh;
1625 let obase = c_o * oh * ow + oy * ow;
1626 let orow = &mut o[obase..obase + ow];
1627 orow.fill(b[c_o]);
1628 for c in 0..ci {
1629 for dy in 0..kh {
1630 let iy = oy * stride + dy;
1631 if iy < pad || iy - pad >= h { continue; }
1632 let xrow = &x[(c * h + iy - pad) * wid..][..wid];
1633 for dx in 0..kw {
1634 let wv = w[((c_o * ci + c) * kh + dy) * kw + dx];
1635 let lo = if pad > dx {
1637 (pad - dx).div_ceil(stride)
1638 } else {
1639 0
1640 };
1641 if lo >= ow { continue; }
1642 let hi_excl = (pad + wid - dx).div_ceil(stride).min(ow);
1643 if lo >= hi_excl { continue; }
1644 if stride == 1 {
1645 let xo = lo + dx - pad;
1647 let hi = hi_excl;
1648 for (a, b) in orow[lo..hi].iter_mut().zip(&xrow[xo..xo + (hi - lo)]) {
1649 *a += wv * *b;
1650 }
1651 } else {
1652 for ox in lo..hi_excl {
1653 orow[ox] += wv * xrow[ox * stride + dx - pad];
1654 }
1655 }
1656 }
1657 }
1658 }
1659 }
1660 };
1661 match pool { Some(p) => p.run_rows(co * oh, &run), None => run(0, co * oh) }
1662 out
1663}
1664
1665pub(crate) fn convt2x2(w: &[f32], b: &[f32], x: &[f32], ci: usize, h: usize, wid: usize, pool: Option<&Pool>) -> Vec<f32> {
1667 let co = b.len();
1668 debug_assert_eq!(w.len(), ci * co * 4);
1669 let (oh, ow) = (h * 2, wid * 2);
1670 let _p = crate::cpuprof::spans::span("op.convt");
1671 let mut out = vec![0f32; co * oh * ow];
1672 let ob = out.as_mut_ptr() as usize;
1673 let run = |s: usize, e: usize| {
1678 let o = unsafe { std::slice::from_raw_parts_mut(ob as *mut f32, co * oh * ow) };
1679 let mut t0 = vec![0f32; wid];
1680 let mut t1 = vec![0f32; wid];
1681 for r in s..e {
1682 let c_o = r / oh;
1683 let oy = r % oh;
1684 let (slot, iy) = (oy & 1, oy >> 1);
1685 t0.fill(b[c_o]);
1686 t1.fill(b[c_o]);
1687 for c in 0..ci {
1688 let xrow = &x[(c * h + iy) * wid..][..wid];
1689 let wb = &w[(c * co + c_o) * 4 + slot * 2..][..2];
1690 let (w0, w1) = (wb[0], wb[1]);
1691 for (t, &xv) in t0.iter_mut().zip(xrow) {
1692 *t += w0 * xv;
1693 }
1694 for (t, &xv) in t1.iter_mut().zip(xrow) {
1695 *t += w1 * xv;
1696 }
1697 }
1698 let obase = c_o * oh * ow + oy * ow;
1699 for j in 0..wid {
1700 o[obase + 2 * j] = t0[j];
1701 o[obase + 2 * j + 1] = t1[j];
1702 }
1703 }
1704 };
1705 match pool { Some(p) => p.run_rows(co * oh, &run), None => run(0, co * oh) }
1706 out
1707}
1708
1709pub(crate) fn maxpool2(x: &[f32], ci: usize, h: usize, wid: usize) -> Vec<f32> {
1710 let (oh, ow) = (h / 2, wid / 2);
1711 let mut out = vec![0f32; ci * oh * ow];
1712 for c in 0..ci {
1713 for oy in 0..oh {
1714 for ox in 0..ow {
1715 let mut mx = f32::NEG_INFINITY;
1716 for dy in 0..2 {
1717 for dx in 0..2 {
1718 let v = x[(c * h + oy * 2 + dy) * wid + ox * 2 + dx];
1719 if v > mx { mx = v; }
1720 }
1721 }
1722 out[(c * oh + oy) * ow + ox] = mx;
1723 }
1724 }
1725 }
1726 out
1727}
1728
1729pub(crate) fn upsample_nearest(x: &[f32], ci: usize, h: usize, wid: usize, sh: usize, sw: usize) -> Vec<f32> {
1731 let mut out = vec![0f32; ci * sh * sw];
1732 for c in 0..ci {
1733 for oy in 0..sh {
1734 let iy = oy * h / sh;
1735 for ox in 0..sw {
1736 let ix = ox * wid / sw;
1737 out[(c * sh + oy) * sw + ox] = x[(c * h + iy) * wid + ix];
1738 }
1739 }
1740 }
1741 out
1742}
1743
1744pub(crate) fn group_norm(x: &mut [f32], groups: usize, w: &[f32], b: &[f32], eps: f32) {
1746 let co = w.len();
1747 let gsz = co / groups;
1748 let rest = x.len() / co;
1749 for g in 0..groups {
1750 let s = g * gsz * rest;
1751 let e = s + gsz * rest;
1752 let mean = x[s..e].iter().sum::<f32>() / (e - s) as f32;
1753 let var = x[s..e].iter().map(|v| (v - mean) * (v - mean)).sum::<f32>() / (e - s) as f32;
1754 let inv = 1.0 / (var + eps).sqrt();
1755 for (i, v) in x[s..e].iter_mut().enumerate() {
1756 let ch = g * gsz + (i / rest);
1757 *v = (*v - mean) * inv * w[ch] + b[ch];
1758 }
1759 }
1760}
1761
1762pub(crate) fn rows_to_chw(rows: &[f32], h: usize, w: usize) -> Vec<f32> {
1765 let _p = crate::cpuprof::spans::span("op.layout");
1766 let n = h * w;
1767 let c = rows.len() / n;
1768 let mut out = vec![0f32; c * h * w];
1769 for ch in 0..c {
1770 for y in 0..h {
1771 for x in 0..w { out[(ch * h + y) * w + x] = rows[(y * w + x) * c + ch]; }
1772 }
1773 }
1774 out
1775}
1776
1777pub(crate) fn chw_to_rows(chw: &[f32], h: usize, w: usize) -> Vec<f32> {
1778 let _p = crate::cpuprof::spans::span("op.layout");
1779 let c = chw.len() / (h * w);
1780 let mut out = vec![0f32; h * w * c];
1781 for ch in 0..c {
1782 for y in 0..h {
1783 for x in 0..w { out[(y * w + x) * c + ch] = chw[(ch * h + y) * w + x]; }
1784 }
1785 }
1786 out
1787}
1788
1789pub(crate) fn sine_pos(h: usize, w: usize, n: usize) -> Vec<f32> {
1792 let _p = crate::cpuprof::spans::span("op.sine_pos");
1793 let scale = std::f32::consts::TAU;
1794 let mut dim_t = vec![0f32; n];
1795 for i in 0..n { dim_t[i] = 10000f32.powf((2 * (i / 2)) as f32 / n as f32); }
1796 let mut out = vec![0f32; h * w * n * 2];
1797 let (hn, wn) = (h as f32, w as f32);
1798 for y in 0..h {
1799 let yy = (y + 1) as f32 / (hn + 1e-6) * scale;
1800 for x in 0..w {
1801 let xx = (x + 1) as f32 / (wn + 1e-6) * scale;
1802 let row = (y * w + x) * n * 2;
1803 for i in 0..n {
1804 let (py, px) = (yy / dim_t[i], xx / dim_t[i]);
1805 if i % 2 == 0 {
1806 out[row + i] = py.sin();
1807 out[row + n + i] = px.sin();
1808 } else {
1809 out[row + i] = py.cos();
1810 out[row + n + i] = px.cos();
1811 }
1812 }
1813 }
1814 }
1815 out
1816}
1817
1818fn sine_boxes(boxes: &[[f32; 4]]) -> Vec<f32> {
1822 let nf = 128;
1823 let scale = std::f32::consts::TAU;
1824 let mut dim_t = vec![0f32; nf];
1825 for i in 0..nf { dim_t[i] = 10000f32.powf((2 * (i / 2)) as f32 / nf as f32); }
1826 let mut out = vec![0f32; boxes.len() * 4 * nf];
1827 for (q, b) in boxes.iter().enumerate() {
1828 let base = q * 4 * nf;
1829 for (ci, v) in [b[1], b[0], b[2], b[3]].iter().enumerate() {
1830 for i in 0..nf {
1831 let val = v * scale / dim_t[i];
1832 out[base + ci * nf + i] = if i % 2 == 0 { val.sin() } else { val.cos() };
1833 }
1834 }
1835 }
1836 out
1837}
1838
1839fn rpb_bias(refs: &[[f32; 4]], rx: &DecoderMlp, ry: &DecoderMlp, n: usize, pool: Option<&Pool>) -> Result<Vec<f32>> {
1842 let gh = GRID;
1843 debug_assert_eq!(n, gh * gh);
1844 let l2_8 = 8.0f32.ln();
1845 let mut dy = vec![0f32; refs.len() * gh * 2];
1846 let mut dx = vec![0f32; refs.len() * gh * 2];
1847 for (q, r) in refs.iter().enumerate() {
1848 let x1 = r[0] - 0.5 * r[2];
1849 let y1 = r[1] - 0.5 * r[3];
1850 let x2 = r[0] + 0.5 * r[2];
1851 let y2 = r[1] + 0.5 * r[3];
1852 for hi in 0..gh {
1853 let c = hi as f32 / gh as f32;
1854 dy[(q * gh + hi) * 2] = log_compress(c - y1, l2_8);
1855 dy[(q * gh + hi) * 2 + 1] = log_compress(c - y2, l2_8);
1856 }
1857 for wi in 0..gh {
1858 let c = wi as f32 / gh as f32;
1859 dx[(q * gh + wi) * 2] = log_compress(c - x1, l2_8);
1860 dx[(q * gh + wi) * 2 + 1] = log_compress(c - x2, l2_8);
1861 }
1862 }
1863 let py = ry.forward(&dy, refs.len() * gh, pool, FH);
1864 let px = rx.forward(&dx, refs.len() * gh, pool, FH);
1865 let mut out = vec![0f32; FH * (NQ + 1) * n];
1866 for t in 0..FH {
1867 for q in 0..refs.len() {
1868 for hi in 0..gh {
1869 for wi in 0..gh {
1870 out[(t * (NQ + 1) + (q + 1)) * n + hi * gh + wi] =
1871 py[(q * gh + hi) * FH + t] + px[(q * gh + wi) * FH + t];
1872 }
1873 }
1874 }
1875 }
1876 Ok(out)
1877}
1878
1879fn log_compress(d: f32, l2_8: f32) -> f32 {
1881 d.signum() * ((8.0 * d).abs() + 1.0).log2() / l2_8
1882}
1883
1884fn add_rows(a: &[f32], b: &[f32]) -> Vec<f32> {
1886 a.iter().zip(b).map(|(x, y)| x + y).collect()
1887}
1888
1889#[allow(dead_code)]
1891fn dbg_stats(tag: &str, v: &[f32]) {
1892 if std::env::var("SAM3_TEXT_DEBUG").is_err() {
1893 return;
1894 }
1895 let (mut bad, mut mn, mut mx) = (0usize, f32::INFINITY, f32::NEG_INFINITY);
1896 for &x in v {
1897 if !x.is_finite() {
1898 bad += 1;
1899 continue;
1900 }
1901 if x < mn { mn = x; }
1902 if x > mx { mx = x; }
1903 }
1904 eprintln!(
1905 "[sam3] {tag} n={} nonfinite={} min={:.4e} max={:.4e}",
1906 v.len(), bad, mn, mx
1907 );
1908}
1909
1910#[allow(dead_code)]
1912fn dump_f32(dir: &str, name: &str, v: &[f32]) {
1913 use std::io::Write;
1914 let p = std::path::Path::new(dir).join(format!("{name}.f32"));
1915 let mut b = Vec::with_capacity(v.len() * 4);
1916 for x in v {
1917 b.extend_from_slice(&x.to_le_bytes());
1918 }
1919 match std::fs::File::create(&p).and_then(|mut f| f.write_all(&b)) {
1920 Ok(()) => eprintln!("[sam3] dumped {} ({} elems)", p.display(), v.len()),
1921 Err(e) => eprintln!("[sam3] dump {} failed: {e}", p.display()),
1922 }
1923}
1924
1925#[cfg(all(test, feature = "gpu"))]
1932mod attn_gpu_tests {
1933 use super::*;
1934
1935 fn lcg(state: &mut u64) -> f32 {
1936 *state = state
1937 .wrapping_mul(6364136223846793005)
1938 .wrapping_add(1442695040888963407);
1939 ((*state >> 40) as f32 / (1u64 << 24) as f32) - 0.5
1940 }
1941
1942 fn rel_cos(g: &[f32], w: &[f32]) -> (f32, f32) {
1943 let mut md = 0f32;
1944 let mut mx = 0f32;
1945 let (mut dot, mut ng, mut nw) = (0f64, 0f64, 0f64);
1946 for (&g, &w) in g.iter().zip(w) {
1947 let d = (g - w).abs();
1948 if d > md { md = d; }
1949 if g.abs() > mx { mx = g.abs(); }
1950 dot += g as f64 * w as f64;
1951 ng += g as f64 * g as f64;
1952 nw += w as f64 * w as f64;
1953 }
1954 let rel = if mx > 0.0 { md / mx } else { md };
1955 let cos = if ng > 0.0 && nw > 0.0 {
1956 (dot / (ng.sqrt() * nw.sqrt())) as f32
1957 } else {
1958 1.0
1959 };
1960 (rel, cos)
1961 }
1962
1963 #[test]
1964 fn attn_gpu_matches_cpu() {
1965 if std::env::var("CMF_GPU").as_deref() != Ok("1") {
1966 eprintln!("skip attn_gpu_matches_cpu: CMF_GPU != 1");
1967 return;
1968 }
1969 if !crate::gpu::backend_available() {
1970 eprintln!("skip attn_gpu_matches_cpu: no wgpu backend");
1971 return;
1972 }
1973 let cases: [(usize, usize, usize, usize, bool); 3] = [
1977 (1024, 1024, 256, 4, true),
1978 (576, 576, 1024, 16, false),
1979 (5184, 5184, 1024, 16, false),
1980 ];
1981 for &(n_q, n_k, dim, heads, with_head) in &cases {
1982 let h3 = dim * 3;
1983 let mut st = 0x9e37_79b9_7f4a_7c15u64;
1984 let xs: Vec<f32> = (0..n_k * h3).map(|_| lcg(&mut st) * 2.0).collect();
1985 let mut st2 = 0xdead_beefu64.wrapping_mul(0x5bd1_e995);
1986 let w_qkv = QTensor::F32 {
1987 data: (0..h3 * dim).map(|_| lcg(&mut st2)).collect(),
1988 rows: h3,
1989 cols: dim,
1990 };
1991 let w_o = QTensor::F32 {
1992 data: (0..dim * dim).map(|_| lcg(&mut st2)).collect(),
1993 rows: dim,
1994 cols: dim,
1995 };
1996 let qb = vec![0.01f32; h3];
1997 let ob = vec![0.02f32; dim];
1998 let head_mask: Vec<f32>;
1999 let mut head_owned: Vec<f32> = Vec::new();
2000 if with_head {
2001 head_owned = vec![0f32; heads * n_q * n_k];
2002 for (i, v) in head_owned.iter_mut().enumerate() {
2003 *v = (i % 97) as f32 * 0.001 - 0.048;
2004 }
2005 }
2006 head_mask = head_owned;
2007 let mask_kinds: [(Option<usize>, &str); 2] =
2008 [(None, "none"), (with_head.then_some(0usize), "head")];
2009 for (kind, label) in mask_kinds {
2010 if kind.is_none() && !matches!(label, "none") {
2011 continue;
2012 }
2013 let mask = match kind {
2014 Some(_) if with_head => Mask::Head(&head_mask),
2015 Some(_) => continue,
2016 None => Mask::None,
2017 };
2018 if label == "head" && !with_head {
2019 continue;
2020 }
2021 let mut out_cpu = vec![0f32; n_q * dim];
2022 let mut out_gpu = vec![0f32; n_q * dim];
2023 crate::gpu::cpu_scope(|| {
2024 run_attn(
2025 &w_qkv, &qb, &w_o, &ob, &xs, &xs, &xs,
2026 n_q, n_k, dim, heads, None, mask, None, &mut out_cpu,
2027 )
2028 });
2029 run_attn(
2030 &w_qkv, &qb, &w_o, &ob, &xs, &xs, &xs,
2031 n_q, n_k, dim, heads, None, mask, None, &mut out_gpu,
2032 );
2033 let (rel, cos) = rel_cos(&out_gpu, &out_cpu);
2034 eprintln!(
2035 "attn_gpu_vs_cpu[{n_q}x{n_k}x{dim}/{heads} {label}] rel={rel:.3e} cos={cos:.6}"
2036 );
2037 assert!(
2038 rel <= 1e-2 && cos >= 0.999,
2039 "attn {n_q}x{n_k} mask={label}: rel={rel:.3e} cos={cos:.6} outside gate"
2040 );
2041 }
2042 }
2043 }
2044}