1use crate::Pipeline;
41use crate::media::{self, RgbFrame};
42use crate::onevision2::{Mat, gelu_rows, layer_norm_rows};
43use crate::pool::Pool;
44use cortiq_core::CmfModel;
45use serde_json::Value;
46use std::sync::Arc;
47
48pub const ARCH: &str = "unlimited-ocr";
49const BASE_SIZE: usize = 1024;
50const IMAGE_SIZE: usize = 640;
51const PATCH: usize = 16;
52const DOWNSAMPLE: usize = 4;
53const MIN_CROPS: usize = 2;
54const MAX_CROPS: usize = 16;
55const BOS_ID: u32 = 0;
56const ACT_CLAMP: f32 = 1.0e4;
58const ENTMAX_NEG: f32 = -1.0e4;
59const TOP_M: usize = 16;
61
62pub fn is_uocr(model: &CmfModel) -> bool {
64 model.header.arch.arch_name == ARCH
65}
66
67#[derive(Clone, Debug, Default)]
71pub struct PageRope {
72 pub image_id: u32,
73 pub page_id: u32,
74 coords: Vec<(u32, u32)>,
76 image_page: Vec<i32>,
78 query_page: Vec<i32>,
80 pf_cnt: Vec<u32>,
82 page: u32,
84 tok: u32,
85 mcum: i32,
87 pp: i32,
88 bmin: Vec<i32>,
90 bmax: Vec<i32>,
91 block: usize,
92}
93
94impl PageRope {
95 pub fn begin(ids: &[u32], image_id: u32, page_id: u32, block: usize) -> Result<Self, String> {
98 let s = ids.len();
99 let last_img = ids.iter().rposition(|&i| i == image_id).ok_or("unlimited-ocr: the prompt has no image")?;
100 let mut cum = vec![0i32; s];
101 let mut c = 0i32;
102 for (i, &id) in ids.iter().enumerate() {
103 c += i32::from(id == page_id);
104 cum[i] = c;
105 }
106 let n_pages = cum[last_img];
107 if n_pages == 0 {
108 return Err("unlimited-ocr: the prompt carries no <PAGE> marker".into());
109 }
110 let page_of = |i: usize| -> u32 {
111 let p = if i <= last_img { cum[i] } else { cum[i] - n_pages };
112 p.max(0) as u32
113 };
114 let n_pg = (0..s).map(page_of).max().unwrap_or(0) as usize + 1;
115 let mut run = vec![0u32; n_pg];
116 let mut pf_cnt = vec![0u32; n_pg];
117 let mut coords = Vec::with_capacity(s);
118 for i in 0..s {
119 let p = page_of(i) as usize;
120 coords.push((run[p], p as u32));
121 run[p] += 1;
122 if i <= last_img {
123 pf_cnt[p] += 1;
124 }
125 }
126 let image_page: Vec<i32> =
127 (0..s).map(|i| if ids[i] == image_id { cum[i] - 1 } else { -1 }).collect();
128 let query_page: Vec<i32> = (0..s)
129 .map(|i| {
130 let p = if i <= last_img { cum[i] - 1 } else { cum[i] - 1 - n_pages };
131 p.max(-1)
132 })
133 .collect();
134 let nblk = s.div_ceil(block);
135 let (mut bmin, mut bmax) = (vec![-1i32; nblk], vec![-1i32; nblk]);
136 for (i, &pg) in image_page.iter().enumerate() {
137 if pg >= 0 {
138 let b = i / block;
139 bmax[b] = bmax[b].max(pg);
140 bmin[b] = if bmin[b] < 0 { pg } else { bmin[b].min(pg) };
141 }
142 }
143 let (tok, page) = coords[s - 1];
144 Ok(Self {
145 image_id,
146 page_id,
147 coords,
148 image_page,
149 query_page,
150 pf_cnt,
151 page,
152 tok,
153 mcum: cum[s - 1],
154 pp: n_pages,
155 bmin,
156 bmax,
157 block,
158 })
159 }
160
161 pub fn prompt_len(&self) -> usize {
162 self.coords.len()
163 }
164
165 pub fn prompt_coords(&self, pos: usize) -> (u32, u32) {
167 self.coords[pos]
168 }
169
170 pub fn decode_step(&mut self, id: u32) -> (u32, u32) {
172 if id == self.page_id {
173 self.page = (self.page + 1).min(self.pf_cnt.len() as u32 - 1);
174 self.tok = self.pf_cnt[self.page as usize];
175 self.mcum += 1;
176 } else {
177 self.tok += 1;
178 }
179 (self.tok, self.page)
180 }
181
182 pub fn prefill_allow(&self, pos: usize, nblk: usize, out: &mut Vec<bool>) {
186 out.clear();
187 out.resize(nblk, false);
188 let qp = self.query_page[pos];
189 let mut any = false;
190 if qp >= 0 {
191 for b in 0..nblk.min(self.bmax.len()) {
194 let hit = self.bmin[b] >= 0
195 && self.bmin[b] <= qp
196 && self.bmax[b] >= qp
197 && self.block_has_page(b, qp);
198 out[b] = hit;
199 any |= hit;
200 }
201 }
202 if !any {
203 out.iter_mut().for_each(|v| *v = true);
204 }
205 }
206
207 fn block_has_page(&self, b: usize, page: i32) -> bool {
208 let at = b * self.block;
209 let end = (at + self.block).min(self.image_page.len());
210 self.image_page[at..end].iter().any(|&p| p == page)
211 }
212
213 pub fn decode_allow(&self, nblk: usize, out: &mut Vec<bool>) {
216 out.clear();
217 out.resize(nblk, false);
218 let qp = self.mcum - 1 - self.pp;
219 let qp = if qp >= 0 { qp } else { -1 };
220 let mut any = false;
221 if qp >= 0 {
222 for b in 0..nblk.min(self.bmax.len()) {
223 let hit = self.bmin[b] >= 0 && self.bmin[b] <= qp && self.bmax[b] >= qp;
224 out[b] = hit;
225 any |= hit;
226 }
227 }
228 if !any {
229 out.iter_mut().for_each(|v| *v = true);
230 }
231 }
232}
233
234pub fn rope_angles(tok: u32, page: u32, inv_tok: &[f32], out: &mut [f32]) {
238 let t = tok as f32;
239 for (o, &f) in out.iter_mut().zip(inv_tok) {
240 *o = t * f;
241 }
242 *out.last_mut().expect("at least one pair") = (page % 2) as f32;
243}
244
245pub fn inv_tok(half: usize, theta: f32) -> Vec<f32> {
247 let n = half - 1;
248 (0..n).map(|j| 1.0 / theta.powf(j as f32 / n as f32)).collect()
249}
250
251pub fn rotate(x: &mut [f32], cs: &[(f32, f32)]) {
253 let half = cs.len();
254 let (a, b) = x.split_at_mut(half);
255 for j in 0..half {
256 let (s, c) = cs[j];
257 let (p, q) = (a[j], b[j]);
258 a[j] = p * c - q * s;
259 b[j] = q * c + p * s;
260 }
261}
262
263pub fn entmax15_sorted(x: &[f32], y: &mut [f32]) {
267 let d = x.len();
268 let top = x[0];
269 let (mut sum, mut sum2) = (0f32, 0f32);
270 let mut tau_star = f32::NAN;
271 let mut support = 0usize;
272 let mut taus = [0f32; TOP_M];
273 for i in 0..d {
274 let v = (x[i] - top) / 2.0;
275 sum += v;
276 sum2 += v * v;
277 let rho = (i + 1) as f32;
278 let mean = sum / rho;
279 let mean_sq = sum2 / rho;
280 let ss = rho * (mean_sq - mean * mean);
281 let delta = ((1.0 - ss) / rho).max(0.0);
282 let tau = mean - delta.sqrt();
283 taus[i] = tau;
284 if tau <= v {
285 support += 1;
286 }
287 }
288 if support > 0 {
289 tau_star = taus[support - 1];
290 } else if d > 0 {
291 tau_star = taus[0];
292 }
293 for i in 0..d {
294 let v = (x[i] - top) / 2.0 - tau_star;
295 y[i] = if v > 0.0 { v * v } else { 0.0 };
296 }
297}
298
299#[derive(Clone, Copy, Debug)]
301pub struct RouteParams {
302 pub idx_scale: f32,
304 pub floor: f32,
305 pub sigma: f32,
306 pub k: usize,
307 pub top_m: usize,
308}
309
310pub fn route_select(
314 scores: &[Option<f32>],
315 allow: &[bool],
316 p: &RouteParams,
317 window: &mut Vec<(f32, u32, bool)>,
318 pick: &mut Vec<(u32, f32)>,
319) {
320 pick.clear();
321 let nblk = scores.len();
322 if nblk == 0 {
323 return;
324 }
325 let any_cand = scores.iter().zip(allow).any(|(s, &a)| s.is_some() && a);
326 let is_cand = |b: usize| scores[b].is_some() && (!any_cand || allow[b]);
327 window.clear();
328 for b in 0..nblk {
329 let c = is_cand(b);
330 let v = if c { scores[b].unwrap_or(ENTMAX_NEG) * p.idx_scale } else { ENTMAX_NEG };
331 let v = if v.is_finite() { v } else { ENTMAX_NEG };
332 window.push((v, b as u32, c));
333 }
334 let m = p.top_m.min(nblk).max(1);
335 window.select_nth_unstable_by(m - 1, |a, b| b.0.total_cmp(&a.0).then(a.1.cmp(&b.1)));
338 window.truncate(m);
339 window.sort_unstable_by(|a, b| b.0.total_cmp(&a.0).then(a.1.cmp(&b.1)));
340 let xv: Vec<f32> = window.iter().map(|w| w.0).collect();
341 let mut prior = [0f32; TOP_M];
342 entmax15_sorted(&xv, &mut prior[..m]);
343 let k = p.k.min(m).max(1);
344 let mut sum = 0f32;
345 let mut cnt = 0usize;
346 let mut logp = [0f32; TOP_M];
347 for i in 0..k {
348 logp[i] = prior[i].max(p.floor).ln();
349 if window[i].2 {
350 sum += logp[i];
351 cnt += 1;
352 }
353 }
354 let mu = sum / cnt.max(1) as f32;
355 for i in 0..k {
356 if window[i].2 {
357 pick.push((window[i].1, (logp[i] - mu) / p.sigma));
358 }
359 }
360}
361
362pub struct DashLayer {
366 pub qk_proj: Vec<f32>,
368 pub q_norm: Vec<f32>,
369 pub k_norm: Vec<f32>,
370 pub head_cls: Vec<f32>,
371 pub route: RouteParams,
372}
373
374pub struct Dash {
376 pub layers: Vec<DashLayer>,
377 pub block: usize,
378 pub local_blocks: usize,
379 pub local_window: usize,
380 pub index_dim: usize,
381 pub head_dim: usize,
382 pub inv_tok: Vec<f32>,
383 pub image_id: u32,
384 pub page_id: u32,
385 pub rope: Option<PageRope>,
386 pub guard: Option<PageGuard>,
388 pub cs: Vec<(f32, f32)>,
390 pub angles: Vec<f32>,
392 scratch_angles: Vec<f32>,
393 pub allow: Vec<bool>,
395 pub cands: Vec<u32>,
398 pub coff: Vec<u32>,
399 pub k_bar: Vec<Vec<f32>>,
401 pub open: Vec<Vec<f32>>,
403 pub on_device: bool,
405 scores: Vec<Option<f32>>,
407 window: Vec<(f32, u32, bool)>,
408 pick: Vec<(u32, f32)>,
409 row: Vec<f32>,
410}
411
412fn sp_usize(sp: &Value, key: &str) -> Result<usize, String> {
413 sp.get(key)
414 .and_then(Value::as_u64)
415 .map(|v| v as usize)
416 .ok_or_else(|| format!("unlimited-ocr: sparse_attention_config has no '{key}'"))
417}
418
419fn sp_list(sp: &Value, key: &str, layers: usize) -> Result<Vec<f32>, String> {
420 let a = sp
421 .get(key)
422 .and_then(Value::as_array)
423 .filter(|a| a.len() == layers)
424 .ok_or_else(|| format!("unlimited-ocr: sparse_attention_config.{key} must list {layers} layers"))?;
425 Ok(a.iter().map(|v| v.as_f64().unwrap_or(0.0) as f32).collect())
426}
427
428impl Dash {
429 pub fn from_model(model: &CmfModel) -> Result<Self, String> {
430 let cfg = model
431 .header
432 .provenance
433 .as_ref()
434 .and_then(|p| p.pointer("/uocr/config"))
435 .ok_or("unlimited-ocr: the file carries no provenance.uocr (reconvert it)")?;
436 let sp = cfg.get("sparse_attention_config").ok_or("unlimited-ocr: config has no sparse_attention_config")?;
437 let layers_n = cfg
438 .get("num_hidden_layers")
439 .and_then(Value::as_u64)
440 .ok_or("unlimited-ocr: config has no num_hidden_layers")? as usize;
441 let block = sp_usize(sp, "sparse_block_size")?;
442 let local_blocks = sp_usize(sp, "sparse_local_block")?;
443 let index_dim = sp_usize(sp, "sparse_index_dim")?;
444 let (hidden, heads) = (model.header.arch.hidden_size, model.header.arch.num_attention_heads);
445 let head_dim = hidden / heads;
446 if index_dim != head_dim {
447 return Err("unlimited-ocr: an index width other than the head width is not supported".into());
448 }
449 let on = |k: &str, want: &str| sp.get(k).and_then(Value::as_str) == Some(want);
450 if !on("training_stage", "dash") || !on("dash_stage0", "head_cls") || !on("sparse_main_rope_mode", "page2d") {
451 return Err("unlimited-ocr: only the dash head_cls page2d router is supported".into());
452 }
453 if sp.get("sparse_rope_page_pairs").and_then(Value::as_u64) != Some(1)
454 || sp.get("sparse_rope_page_mod").and_then(Value::as_u64) != Some(2)
455 {
456 return Err("unlimited-ocr: only one page pair at parity is supported".into());
457 }
458 let scales = sp_list(sp, "dash_scale_per_layer", layers_n)?;
459 let floors = sp_list(sp, "dash_floor_per_layer", layers_n)?;
460 let sigma = sp.get("dash_sigma").and_then(Value::as_f64).unwrap_or(1.0) as f32;
461 let k = sp_usize(sp, "dash_topk_blocks")?;
462 let f = |n: String, len: usize| -> Result<Vec<f32>, String> {
463 let v = crate::dit::cmf_f32(model, &n)?;
464 if v.len() != len {
465 return Err(format!("tensor '{n}' has {} values, expected {len}", v.len()));
466 }
467 Ok(v)
468 };
469 let mut layers = Vec::with_capacity(layers_n);
470 for li in 0..layers_n {
471 let p = format!("model.layers.{li}.self_attn.dash_router");
472 layers.push(DashLayer {
473 qk_proj: {
474 let mut w = f(format!("{p}.q_proj.weight"), index_dim * hidden)?;
475 w.extend(f(format!("{p}.k_proj.weight"), index_dim * hidden)?);
476 w
477 },
478 q_norm: f(format!("{p}.q_norm.weight"), index_dim)?,
479 k_norm: f(format!("{p}.k_norm.weight"), index_dim)?,
480 head_cls: f(format!("{p}.head_cls"), index_dim)?,
481 route: RouteParams {
482 idx_scale: scales[li] / (index_dim as f32).sqrt(),
483 floor: floors[li],
484 sigma,
485 k,
486 top_m: TOP_M,
487 },
488 });
489 }
490 let half = head_dim / 2;
491 let theta = sp.get("sparse_rope_tok_theta").and_then(Value::as_f64).unwrap_or(10000.0) as f32;
492 Ok(Self {
493 block,
494 local_blocks,
495 local_window: local_blocks * block,
496 index_dim,
497 head_dim,
498 inv_tok: inv_tok(half, theta),
499 image_id: sp_usize(sp, "image_token_id")? as u32,
500 page_id: sp_usize(sp, "page_token_id")? as u32,
501 rope: None,
502 guard: None,
503 cs: vec![(0.0, 1.0); half],
504 angles: vec![0.0; half],
505 scratch_angles: vec![0.0; half],
506 allow: Vec::new(),
507 cands: Vec::new(),
508 coff: Vec::new(),
509 k_bar: vec![Vec::new(); layers_n],
510 open: vec![Vec::new(); layers_n],
511 on_device: false,
512 scores: Vec::new(),
513 window: Vec::new(),
514 pick: Vec::new(),
515 row: vec![0.0; index_dim],
516 layers,
517 })
518 }
519
520 pub fn begin(&mut self, ids: &[u32], eos_id: u32) -> Result<(), String> {
522 self.rope = Some(PageRope::begin(ids, self.image_id, self.page_id, self.block)?);
523 let pages = ids.iter().filter(|&&i| i == self.page_id).count();
524 self.guard = Some(PageGuard::new(pages, self.page_id, eos_id));
525 for v in self.k_bar.iter_mut().chain(self.open.iter_mut()) {
526 v.clear();
527 }
528 Ok(())
529 }
530
531 pub fn at_prompt(&mut self, pos: usize) {
533 self.at_prompt_chunk(pos, 1);
534 let rope = self.rope.as_ref().expect("begin ran");
535 let (tok, page) = rope.prompt_coords(pos);
536 self.set_cs(tok, page);
537 }
538
539 pub fn at_prompt_chunk(&mut self, pos: usize, k: usize) {
542 let half = self.head_dim / 2;
543 let mut allow = std::mem::take(&mut self.allow);
544 self.angles.clear();
545 self.angles.resize(k * half, 0.0);
546 self.cands.clear();
547 self.coff.clear();
548 self.coff.push(0);
549 let rope = self.rope.as_ref().expect("begin ran");
550 let nblk = rope.prompt_len().div_ceil(self.block);
551 for i in 0..k {
552 let p = pos + i;
553 let (tok, page) = rope.prompt_coords(p);
554 rope_angles(tok, page, &self.inv_tok, &mut self.angles[i * half..(i + 1) * half]);
555 rope.prefill_allow(p, nblk, &mut allow);
556 Self::push_cands(&mut self.cands, &allow, p, self.block, self.local_blocks);
557 self.coff.push(self.cands.len() as u32);
558 }
559 self.allow = allow;
560 }
561
562 pub fn at_decode(&mut self, id: u32, pos: usize) {
564 let half = self.head_dim / 2;
565 let rope = self.rope.as_mut().expect("begin ran");
566 let (tok, page) = rope.decode_step(id);
567 let nblk = (pos + 1).div_ceil(self.block);
568 let mut allow = std::mem::take(&mut self.allow);
569 self.rope.as_ref().expect("begin ran").decode_allow(nblk, &mut allow);
570 self.cands.clear();
571 Self::push_cands(&mut self.cands, &allow, pos, self.block, self.local_blocks);
572 self.coff.clear();
573 self.coff.extend([0, self.cands.len() as u32]);
574 self.allow = allow;
575 self.angles.clear();
576 self.angles.resize(half, 0.0);
577 rope_angles(tok, page, &self.inv_tok, &mut self.angles);
578 self.set_cs(tok, page);
579 }
580
581 fn set_cs(&mut self, tok: u32, page: u32) {
582 rope_angles(tok, page, &self.inv_tok, &mut self.scratch_angles);
583 for (o, a) in self.cs.iter_mut().zip(&self.scratch_angles) {
584 *o = a.sin_cos();
585 }
586 }
587
588 fn push_cands(out: &mut Vec<u32>, allow: &[bool], pos: usize, block: usize, local: usize) {
590 let nrem = (pos / block + 1).saturating_sub(local);
591 let at = out.len();
592 out.extend((0..nrem.min(allow.len())).filter(|&b| allow[b]).map(|b| b as u32));
593 if out.len() == at {
594 out.extend(0..nrem as u32);
595 }
596 }
597
598 fn norm_rotate(x: &mut [f32], w: &[f32], eps: f32, cs: &[(f32, f32)]) {
600 let d = x.len();
601 let mean = x.iter().map(|v| v * v).sum::<f32>() / d as f32;
602 let inv = (mean + eps).sqrt().recip();
603 for (v, &wv) in x.iter_mut().zip(w) {
604 let n = (*v * inv).clamp(-ACT_CLAMP, ACT_CLAMP);
605 *v = (n * (1.0 + wv)).clamp(-ACT_CLAMP, ACT_CLAMP);
606 }
607 rotate(x, cs);
608 }
609
610 fn route_host(&mut self, li: usize, n1: &[f32], pos: usize, eps: f32) {
613 let d = self.index_dim;
614 let hs = n1.len();
615 let l = &self.layers[li];
616 let mut q = vec![0f32; d];
617 let mut k = std::mem::take(&mut self.row);
618 for r in 0..d {
619 let wq = &l.qk_proj[r * hs..(r + 1) * hs];
620 let wk = &l.qk_proj[(d + r) * hs..(d + r + 1) * hs];
621 let (mut a, mut b) = (0f32, 0f32);
622 for c in 0..hs {
623 a += wq[c] * n1[c];
624 b += wk[c] * n1[c];
625 }
626 q[r] = a;
627 k[r] = b;
628 }
629 Self::norm_rotate(&mut q, &l.q_norm, eps, &self.cs);
630 Self::norm_rotate(&mut k, &l.k_norm, eps, &self.cs);
631 let inv_sqrt = (d as f32).sqrt().recip();
634 let qblk = pos / self.block;
635 let done = self.k_bar[li].len() / d;
636 let nblk = self.allow.len().max(done);
637 self.scores.clear();
638 self.scores.resize(nblk, None);
639 for b in 0..done.min((qblk + 1).saturating_sub(self.local_blocks)) {
640 let kb = &self.k_bar[li][b * d..(b + 1) * d];
641 let z: f32 = q.iter().zip(kb).map(|(x, y)| x * y).sum::<f32>() * inv_sqrt;
642 self.scores[b] = Some(z.clamp(-ACT_CLAMP, ACT_CLAMP));
643 }
644 if self.allow.len() < nblk {
645 self.allow.resize(nblk, false);
646 }
647 let (scores, allow, window, pick) = (&self.scores, &self.allow, &mut self.window, &mut self.pick);
648 route_select(scores, allow, &l.route, window, pick);
649 self.open[li].extend_from_slice(&k);
650 self.row = k;
651 if self.open[li].len() == self.block * d {
652 let mut kb = vec![0f32; d];
653 summarize_block(&self.open[li], &self.layers[li].head_cls, &mut kb);
654 self.k_bar[li].extend_from_slice(&kb);
655 self.open[li].clear();
656 }
657 }
658}
659
660pub fn summarize_block(keys: &[f32], hc: &[f32], out: &mut [f32]) {
663 let d = hc.len();
664 let n = keys.len() / d;
665 let inv_sqrt = (d as f32).sqrt().recip();
666 let mut aw = vec![0f32; n];
667 let mut mx = f32::NEG_INFINITY;
668 for (i, a) in aw.iter_mut().enumerate() {
669 let k = &keys[i * d..(i + 1) * d];
670 *a = (k.iter().zip(hc).map(|(x, y)| x * y).sum::<f32>() * inv_sqrt).clamp(-ACT_CLAMP, ACT_CLAMP);
671 mx = mx.max(*a);
672 }
673 let mut den = 0f32;
674 for a in aw.iter_mut() {
675 *a = (*a - mx).exp();
676 den += *a;
677 }
678 let den = den.max(1e-6);
679 out.iter_mut().for_each(|v| *v = 0.0);
680 for (i, a) in aw.iter().enumerate() {
681 let w = a / den;
682 for (o, &v) in out.iter_mut().zip(&keys[i * d..(i + 1) * d]) {
683 *o += w * v;
684 }
685 }
686}
687
688#[allow(clippy::too_many_arguments)]
692pub(crate) fn attention_host(
693 dash: &mut Dash,
694 li: usize,
695 n1: &[f32],
696 wq: &crate::qtensor::QTensor,
697 wk: &crate::qtensor::QTensor,
698 wv: &crate::qtensor::QTensor,
699 cache: &mut crate::kv_cache::LayerKvCache,
700 position: usize,
701 nh: usize,
702 hd: usize,
703 eps: f32,
704 pool: Option<&Pool>,
705 out: &mut [f32],
706) {
707 dash.route_host(li, n1, position, eps);
708 let (mut q, mut k, mut v) = (vec![0f32; nh * hd], vec![0f32; nh * hd], vec![0f32; nh * hd]);
709 wq.matvec(n1, &mut q, pool);
710 wk.matvec(n1, &mut k, pool);
711 wv.matvec(n1, &mut v, pool);
712 for h in 0..nh {
713 rotate(&mut q[h * hd..(h + 1) * hd], &dash.cs);
714 rotate(&mut k[h * hd..(h + 1) * hd], &dash.cs);
715 }
716 cache.append(&k, &v, &[]);
717 let scale = (hd as f32).sqrt().recip();
718 let (block, window) = (dash.block, dash.local_window);
719 let lo = (position + 1).saturating_sub(window);
720 let pick = &dash.pick;
721 let (keys, vals) = (cache.k_heads(), cache.v_heads());
722 let attend = |h: usize, o: &mut [f32]| {
723 let qh = &q[h * hd..(h + 1) * hd];
724 let (kh, vh) = (&keys[h], &vals[h]);
725 let mut sc: Vec<(u32, f32)> = Vec::with_capacity(pick.len() * block + window);
726 let dot = |p: usize| -> f32 {
727 let kr = &kh[p * hd..(p + 1) * hd];
728 qh.iter().zip(kr).map(|(a, b)| a * b).sum::<f32>() * scale
729 };
730 for &(b, bias) in pick {
731 let at = b as usize * block;
732 for p in at..(at + block).min(lo) {
734 sc.push((p as u32, dot(p) + bias));
735 }
736 }
737 for p in lo..=position {
738 sc.push((p as u32, dot(p)));
739 }
740 let mx = sc.iter().fold(f32::NEG_INFINITY, |m, s| m.max(s.1));
741 let mut den = 0f32;
742 for s in sc.iter_mut() {
743 s.1 = (s.1 - mx).exp();
744 den += s.1;
745 }
746 let inv = den.recip();
747 o.iter_mut().for_each(|x| *x = 0.0);
748 for &(p, w) in &sc {
749 let w = w * inv;
750 let vr = &vh[p as usize * hd..(p as usize + 1) * hd];
751 for (x, &y) in o.iter_mut().zip(vr) {
752 *x += w * y;
753 }
754 }
755 };
756 let oo = crate::pool::SendMut::new(out.as_mut_ptr());
757 let run = |lo_h: usize, hi_h: usize| {
758 for h in lo_h..hi_h {
759 let o = unsafe { std::slice::from_raw_parts_mut(oo.at(h * hd), hd) };
761 attend(h, o);
762 }
763 };
764 match pool {
765 Some(p) => p.run_rows(nh, &run),
766 None => run(0, nh),
767 }
768}
769
770fn round_half_even(v: f64) -> i64 {
773 let r = v.round();
774 if (v - v.trunc()).abs() == 0.5 && (r as i64) % 2 != 0 { (r - v.signum()) as i64 } else { r as i64 }
775}
776
777fn pil_taps(inp: usize, out: usize) -> Vec<(usize, Vec<i32>)> {
780 let bic = |x: f64| -> f64 {
781 const A: f64 = -0.5;
782 let x = x.abs();
783 if x < 1.0 {
784 ((A + 2.0) * x - (A + 3.0)) * x * x + 1.0
785 } else if x < 2.0 {
786 (((x - 5.0) * x + 8.0) * x - 4.0) * A
787 } else {
788 0.0
789 }
790 };
791 let scale = inp as f64 / out as f64;
792 let fs = scale.max(1.0);
793 let support = 2.0 * fs;
794 (0..out)
795 .map(|i| {
796 let center = (i as f64 + 0.5) * scale;
797 let lo = ((center - support + 0.5) as i64).max(0) as usize;
798 let hi = ((center + support + 0.5) as i64).min(inp as i64) as usize;
799 let w: Vec<f64> = (lo..hi).map(|x| bic((x as f64 - center + 0.5) / fs)).collect();
800 let sum: f64 = w.iter().sum();
801 let k = w
802 .iter()
803 .map(|v| {
804 let v = if sum != 0.0 { v / sum } else { *v } * (1u32 << 22) as f64;
805 (if v < 0.0 { v - 0.5 } else { v + 0.5 }) as i32
806 })
807 .collect();
808 (lo, k)
809 })
810 .collect()
811}
812
813pub fn pil_bicubic(img: &RgbFrame, height: usize, width: usize) -> RgbFrame {
816 let clip = |v: i64| -> u8 { (v >> 22).clamp(0, 255) as u8 };
817 let round = 1i64 << 21;
818 let (h0, w0) = (img.height, img.width);
819 let mut cur = img.data.clone();
820 if width != w0 {
821 let taps = pil_taps(w0, width);
822 let mut out = vec![0u8; h0 * width * 3];
823 for y in 0..h0 {
824 let src = &cur[y * w0 * 3..(y + 1) * w0 * 3];
825 let dst = &mut out[y * width * 3..(y + 1) * width * 3];
826 for (x, (lo, k)) in taps.iter().enumerate() {
827 for c in 0..3 {
828 let mut ss = round;
829 for (j, &w) in k.iter().enumerate() {
830 ss += src[(lo + j) * 3 + c] as i64 * w as i64;
831 }
832 dst[x * 3 + c] = clip(ss);
833 }
834 }
835 }
836 cur = out;
837 }
838 if height != h0 {
839 let taps = pil_taps(h0, height);
840 let mut out = vec![0u8; height * width * 3];
841 for (y, (lo, k)) in taps.iter().enumerate() {
842 for x in 0..width * 3 {
843 let mut ss = round;
844 for (j, &w) in k.iter().enumerate() {
845 ss += cur[(lo + j) * width * 3 + x] as i64 * w as i64;
846 }
847 out[y * width * 3 + x] = clip(ss);
848 }
849 }
850 cur = out;
851 }
852 RgbFrame { width, height, data: cur }
853}
854
855pub fn pad_square(img: &RgbFrame, size: usize) -> RgbFrame {
858 let (w, h) = (img.width as f64, img.height as f64);
859 let (mut nw, mut nh) = (size, size);
860 if img.width != img.height {
861 if img.width > img.height {
862 nh = round_half_even(h / w * size as f64) as usize;
863 } else {
864 nw = round_half_even(w / h * size as f64) as usize;
865 }
866 }
867 let r = if (nh, nw) == (img.height, img.width) {
868 img.clone()
869 } else {
870 pil_bicubic(img, nh, nw)
871 };
872 if nw == size && nh == size {
873 return r;
874 }
875 let mut data = vec![127u8; size * size * 3];
876 let x0 = round_half_even((size - nw) as f64 * 0.5) as usize;
877 let y0 = round_half_even((size - nh) as f64 * 0.5) as usize;
878 for y in 0..nh {
879 let d = ((y0 + y) * size + x0) * 3;
880 let s = y * nw * 3;
881 data[d..d + nw * 3].copy_from_slice(&r.data[s..s + nw * 3]);
882 }
883 RgbFrame { width: size, height: size, data }
884}
885
886fn closest_grid(w: usize, h: usize, tile: usize) -> (usize, usize) {
889 let mut ratios: Vec<(usize, usize)> = Vec::new();
890 for i in 1..=MAX_CROPS {
891 for j in 1..=MAX_CROPS {
892 if i * j >= MIN_CROPS && i * j <= MAX_CROPS {
893 ratios.push((i, j));
894 }
895 }
896 }
897 ratios.sort_by_key(|r| r.0 * r.1);
902 let aspect = w as f64 / h as f64;
903 let area = (w * h) as f64;
904 let mut best = (1usize, 1usize);
905 let mut best_diff = f64::INFINITY;
906 for &(i, j) in &ratios {
907 let diff = (aspect - i as f64 / j as f64).abs();
908 if diff < best_diff {
909 best_diff = diff;
910 best = (i, j);
911 } else if diff == best_diff && area > 0.5 * (tile * tile * i * j) as f64 {
912 best = (i, j);
913 }
914 }
915 best
916}
917
918pub struct PageViews {
921 pub global: Vec<f32>,
922 pub tiles: Vec<Vec<f32>>,
923 pub cols: usize,
924 pub rows: usize,
925}
926
927fn to_chw(img: &RgbFrame, top: usize, left: usize, side: usize) -> Vec<f32> {
928 let mut out = vec![0f32; 3 * side * side];
929 for c in 0..3 {
930 for y in 0..side {
931 let line = ((top + y) * img.width + left) * 3 + c;
932 for x in 0..side {
933 let v = img.data[line + x * 3] as f32 / 255.0;
935 out[(c * side + y) * side + x] = (v - 0.5) / 0.5;
936 }
937 }
938 }
939 out
940}
941
942pub fn page_views(img: &RgbFrame, tiled: bool) -> PageViews {
944 let global = to_chw(&pad_square(img, BASE_SIZE), 0, 0, BASE_SIZE);
945 if !tiled || (img.width <= IMAGE_SIZE && img.height <= IMAGE_SIZE) {
946 return PageViews { global, tiles: Vec::new(), cols: 1, rows: 1 };
947 }
948 let (cols, rows) = closest_grid(img.width, img.height, IMAGE_SIZE);
949 let resized = pil_bicubic(img, IMAGE_SIZE * rows, IMAGE_SIZE * cols);
950 let mut tiles = Vec::with_capacity(cols * rows);
951 for y in 0..rows {
952 for x in 0..cols {
953 tiles.push(to_chw(&resized, y * IMAGE_SIZE, x * IMAGE_SIZE, IMAGE_SIZE));
954 }
955 }
956 PageViews { global, tiles, cols, rows }
957}
958
959pub fn page_tokens(cols: usize, rows: usize) -> usize {
961 let nq = (IMAGE_SIZE / PATCH).div_ceil(DOWNSAMPLE);
962 let nqb = (BASE_SIZE / PATCH).div_ceil(DOWNSAMPLE);
963 let mut n = nqb * (nqb + 1) + 1;
964 if cols > 1 || rows > 1 {
965 n += (nq * cols + 1) * (nq * rows);
966 }
967 n
968}
969
970fn resize_table(t: &[f32], src: usize, dst: usize, d: usize) -> Vec<f32> {
975 if src == dst {
976 return t.to_vec();
977 }
978 let weights = |src: usize, dst: usize| -> Vec<(usize, Vec<f32>)> {
979 let scale = src as f64 / dst as f64;
981 let fs = scale.max(1.0);
982 let support = 2.0 * fs;
983 (0..dst)
984 .map(|i| {
985 let center = (i as f64 + 0.5) * scale;
986 let lo = ((center - support + 0.5) as i64).max(0) as usize;
987 let hi = ((center + support + 0.5) as i64).min(src as i64) as usize;
988 let bic = |x: f64| -> f64 {
989 let a = -0.5;
990 let x = x.abs();
991 if x < 1.0 {
992 ((a + 2.0) * x - (a + 3.0)) * x * x + 1.0
993 } else if x < 2.0 {
994 (((x - 5.0) * x + 8.0) * x - 4.0) * a
995 } else {
996 0.0
997 }
998 };
999 let w: Vec<f64> = (lo..hi).map(|j| bic((j as f64 + 0.5 - center) / fs)).collect();
1000 let s: f64 = w.iter().sum();
1001 (lo, w.iter().map(|v| (v / s) as f32).collect())
1002 })
1003 .collect()
1004 };
1005 let w = weights(src, dst);
1006 let mut h = vec![0f32; src * dst * d];
1008 for y in 0..src {
1009 for x in 0..dst {
1010 let (lo, ws) = &w[x];
1011 for c in 0..d {
1012 let mut v = 0f32;
1013 for (j, &wt) in ws.iter().enumerate() {
1014 v += wt * t[(y * src + lo + j) * d + c];
1015 }
1016 h[(y * dst + x) * d + c] = v;
1017 }
1018 }
1019 }
1020 let mut out = vec![0f32; dst * dst * d];
1021 for y in 0..dst {
1022 let (lo, ws) = &w[y];
1023 for x in 0..dst {
1024 for c in 0..d {
1025 let mut v = 0f32;
1026 for (j, &wt) in ws.iter().enumerate() {
1027 v += wt * h[((lo + j) * dst + x) * d + c];
1028 }
1029 out[(y * dst + x) * d + c] = v;
1030 }
1031 }
1032 }
1033 out
1034}
1035
1036fn vec_of(model: &Arc<CmfModel>, name: &str, len: usize) -> Result<Vec<f32>, String> {
1037 let v = crate::dit::cmf_f32(model, name)?;
1038 if v.len() != len {
1039 return Err(format!("tensor '{name}' has {} values, expected {len}", v.len()));
1040 }
1041 Ok(v)
1042}
1043
1044struct SamBlock {
1045 n1_w: Vec<f32>,
1046 n1_b: Vec<f32>,
1047 qkv: Mat,
1048 qkv_b: Vec<f32>,
1049 proj: Mat,
1050 proj_b: Vec<f32>,
1051 rel_h: Vec<f32>,
1052 rel_w: Vec<f32>,
1053 rel_cat: Vec<f32>,
1055 rel_len: usize,
1056 n2_w: Vec<f32>,
1057 n2_b: Vec<f32>,
1058 fc1: Mat,
1059 fc1_b: Vec<f32>,
1060 fc2: Mat,
1061 fc2_b: Vec<f32>,
1062 window: usize,
1063}
1064
1065struct ClipBlock {
1066 n1_w: Vec<f32>,
1067 n1_b: Vec<f32>,
1068 qkv: Mat,
1069 qkv_b: Vec<f32>,
1070 out: Mat,
1071 out_b: Vec<f32>,
1072 n2_w: Vec<f32>,
1073 n2_b: Vec<f32>,
1074 fc1: Mat,
1075 fc1_b: Vec<f32>,
1076 fc2: Mat,
1077 fc2_b: Vec<f32>,
1078}
1079
1080pub struct UocrTower {
1082 patch: Mat,
1083 patch_b: Vec<f32>,
1084 pos: Vec<f32>,
1085 sam: Vec<SamBlock>,
1086 neck0: Vec<f32>,
1087 neck1_w: Vec<f32>,
1088 neck1_b: Vec<f32>,
1089 neck2: Vec<f32>,
1090 neck3_w: Vec<f32>,
1091 neck3_b: Vec<f32>,
1092 net2: Vec<f32>,
1093 net3: Vec<f32>,
1094 conv_w: [Vec<f32>; 4],
1096 cls: Vec<f32>,
1097 clip_pos: Vec<f32>,
1098 pre_w: Vec<f32>,
1099 pre_b: Vec<f32>,
1100 clip: Vec<ClipBlock>,
1101 proj: Mat,
1102 proj_b: Vec<f32>,
1103 pub newline: Vec<f32>,
1104 pub view_sep: Vec<f32>,
1105 pool: Option<Arc<Pool>>,
1106 cache: std::sync::Mutex<std::collections::HashMap<usize, Arc<GridTables>>>,
1108}
1109
1110struct GridTables {
1111 pos: Vec<f32>,
1112 clip_pos: Vec<f32>,
1113 rel_global: Vec<(Vec<f32>, Vec<f32>)>,
1115}
1116
1117const SAM_D: usize = 768;
1118const SAM_HEADS: usize = 12;
1119const SAM_HD: usize = 64;
1120const SAM_INTER: usize = 3072;
1121const SAM_GLOBAL: [usize; 4] = [2, 5, 8, 11];
1122const SAM_WINDOW: usize = 14;
1123const SAM_SIDE: usize = 64;
1124const SAM_EPS: f64 = 1e-6;
1125const CLIP_D: usize = 1024;
1126const CLIP_HEADS: usize = 16;
1127const CLIP_HD: usize = 64;
1128const CLIP_INTER: usize = 4096;
1129const CLIP_LAYERS: usize = 24;
1130const CLIP_SIDE: usize = 16;
1131const CLIP_EPS: f64 = 1e-5;
1132const OUT_D: usize = 1280;
1133
1134fn resize_rel(t: &[f32], c: usize, n: usize) -> Vec<f32> {
1136 let l = t.len() / c;
1137 if l == n {
1138 return t.to_vec();
1139 }
1140 let mut out = vec![0f32; n * c];
1141 let scale = l as f64 / n as f64;
1142 for i in 0..n {
1143 let x = ((i as f64 + 0.5) * scale - 0.5).max(0.0);
1144 let x0 = x.floor() as usize;
1145 let x1 = (x0 + 1).min(l - 1);
1146 let f = (x - x0 as f64) as f32;
1147 for k in 0..c {
1148 out[i * c + k] = t[x0 * c + k] * (1.0 - f) + t[x1 * c + k] * f;
1149 }
1150 }
1151 out
1152}
1153
1154#[allow(clippy::too_many_arguments)]
1157fn conv_same(x: &[f32], g: usize, ci: usize, w: &[f32], co: usize, k: usize, stride: usize, pad: usize) -> (Vec<f32>, usize) {
1158 let g2 = (g + 2 * pad - k) / stride + 1;
1159 let mut out = vec![0f32; g2 * g2 * co];
1160 let row = |oy: usize, ox: usize, o: &mut [f32]| {
1161 for ky in 0..k {
1162 let Some(iy) = (oy * stride + ky).checked_sub(pad).filter(|&v| v < g) else { continue };
1163 for kx in 0..k {
1164 let Some(ix) = (ox * stride + kx).checked_sub(pad).filter(|&v| v < g) else { continue };
1165 let src = &x[(iy * g + ix) * ci..(iy * g + ix + 1) * ci];
1166 for (co_i, ov) in o.iter_mut().enumerate() {
1167 let base = co_i * ci * k * k + ky * k + kx;
1168 let mut acc = 0f32;
1169 for (ci_i, &s) in src.iter().enumerate() {
1170 acc += w[base + ci_i * k * k] * s;
1171 }
1172 *ov += acc;
1173 }
1174 }
1175 }
1176 };
1177 let p = Pool::from_env();
1178 let oo = crate::pool::SendMut::new(out.as_mut_ptr());
1179 let f = |lo: usize, hi: usize| {
1180 for r in lo..hi {
1181 let (oy, ox) = (r / g2, r % g2);
1182 let o = unsafe { std::slice::from_raw_parts_mut(oo.at(r * co), co) };
1184 row(oy, ox, o);
1185 }
1186 };
1187 match p.as_deref() {
1188 Some(pool) => pool.run_rows(g2 * g2, &f),
1189 None => f(0, g2 * g2),
1190 }
1191 (out, g2)
1192}
1193
1194fn norm2d(x: &mut [f32], c: usize, w: &[f32], b: &[f32]) {
1196 for row in x.chunks_exact_mut(c) {
1197 let u = row.iter().sum::<f32>() / c as f32;
1198 let s = row.iter().map(|v| (v - u) * (v - u)).sum::<f32>() / c as f32;
1199 let inv = (s + 1e-6).sqrt().recip();
1200 for ((v, &ww), &bb) in row.iter_mut().zip(w).zip(b) {
1201 *v = ww * ((*v - u) * inv) + bb;
1202 }
1203 }
1204}
1205
1206impl UocrTower {
1207 pub fn from_model(model: &Arc<CmfModel>, pool: Option<Arc<Pool>>) -> Result<Self, String> {
1208 let mut sam = Vec::with_capacity(12);
1209 for i in 0..12 {
1210 let p = format!("vis.sam.blocks.{i}");
1211 let global = SAM_GLOBAL.contains(&i);
1212 let rel_len = if global { 2 * SAM_SIDE - 1 } else { 2 * SAM_WINDOW - 1 };
1213 let d = SAM_D;
1214 sam.push(SamBlock {
1215 n1_w: vec_of(model, &format!("{p}.norm1.weight"), d)?,
1216 n1_b: vec_of(model, &format!("{p}.norm1.bias"), d)?,
1217 qkv: Mat::load(model, &format!("{p}.attn.qkv.weight"), 3 * d, d)?,
1218 qkv_b: vec_of(model, &format!("{p}.attn.qkv.bias"), 3 * d)?,
1219 proj: Mat::load(model, &format!("{p}.attn.proj.weight"), d, d)?,
1220 proj_b: vec_of(model, &format!("{p}.attn.proj.bias"), d)?,
1221 rel_h: vec_of(model, &format!("{p}.attn.rel_pos_h"), rel_len * SAM_HD)?,
1222 rel_w: vec_of(model, &format!("{p}.attn.rel_pos_w"), rel_len * SAM_HD)?,
1223 rel_cat: {
1224 let mut v = vec_of(model, &format!("{p}.attn.rel_pos_h"), rel_len * SAM_HD)?;
1225 v.extend(vec_of(model, &format!("{p}.attn.rel_pos_w"), rel_len * SAM_HD)?);
1226 v
1227 },
1228 rel_len,
1229 n2_w: vec_of(model, &format!("{p}.norm2.weight"), d)?,
1230 n2_b: vec_of(model, &format!("{p}.norm2.bias"), d)?,
1231 fc1: Mat::load(model, &format!("{p}.mlp.lin1.weight"), SAM_INTER, d)?,
1232 fc1_b: vec_of(model, &format!("{p}.mlp.lin1.bias"), SAM_INTER)?,
1233 fc2: Mat::load(model, &format!("{p}.mlp.lin2.weight"), d, SAM_INTER)?,
1234 fc2_b: vec_of(model, &format!("{p}.mlp.lin2.bias"), d)?,
1235 window: if global { 0 } else { SAM_WINDOW },
1236 });
1237 }
1238 let mut clip = Vec::with_capacity(CLIP_LAYERS);
1239 for i in 0..CLIP_LAYERS {
1240 let p = format!("vis.clip.transformer.layers.{i}");
1241 let d = CLIP_D;
1242 clip.push(ClipBlock {
1243 n1_w: vec_of(model, &format!("{p}.layer_norm1.weight"), d)?,
1244 n1_b: vec_of(model, &format!("{p}.layer_norm1.bias"), d)?,
1245 qkv: Mat::load(model, &format!("{p}.self_attn.qkv_proj.weight"), 3 * d, d)?,
1246 qkv_b: vec_of(model, &format!("{p}.self_attn.qkv_proj.bias"), 3 * d)?,
1247 out: Mat::load(model, &format!("{p}.self_attn.out_proj.weight"), d, d)?,
1248 out_b: vec_of(model, &format!("{p}.self_attn.out_proj.bias"), d)?,
1249 n2_w: vec_of(model, &format!("{p}.layer_norm2.weight"), d)?,
1250 n2_b: vec_of(model, &format!("{p}.layer_norm2.bias"), d)?,
1251 fc1: Mat::load(model, &format!("{p}.mlp.fc1.weight"), CLIP_INTER, d)?,
1252 fc1_b: vec_of(model, &format!("{p}.mlp.fc1.bias"), CLIP_INTER)?,
1253 fc2: Mat::load(model, &format!("{p}.mlp.fc2.weight"), d, CLIP_INTER)?,
1254 fc2_b: vec_of(model, &format!("{p}.mlp.fc2.bias"), d)?,
1255 });
1256 }
1257 let reorder = |w: &[f32], ci: usize, co: usize, k: usize| -> Vec<f32> {
1259 let mut out = vec![0f32; w.len()];
1260 for c in 0..co {
1261 for t in 0..k * k {
1262 for i in 0..ci {
1263 out[(c * k * k + t) * ci + i] = w[(c * ci + i) * k * k + t];
1264 }
1265 }
1266 }
1267 out
1268 };
1269 let neck0 = vec_of(model, "vis.sam.neck.0.weight", 256 * SAM_D)?;
1270 let neck2 = vec_of(model, "vis.sam.neck.2.weight", 256 * 256 * 9)?;
1271 let net2 = vec_of(model, "vis.sam.net_2.weight", 512 * 256 * 9)?;
1272 let net3 = vec_of(model, "vis.sam.net_3.weight", 1024 * 512 * 9)?;
1273 let conv_w = [
1274 reorder(&neck0, SAM_D, 256, 1),
1275 reorder(&neck2, 256, 256, 3),
1276 reorder(&net2, 256, 512, 3),
1277 reorder(&net3, 512, 1024, 3),
1278 ];
1279 Ok(Self {
1280 patch: Mat::load(model, "vis.sam.patch_embed.proj.weight", SAM_D, 3 * PATCH * PATCH)?,
1281 patch_b: vec_of(model, "vis.sam.patch_embed.proj.bias", SAM_D)?,
1282 pos: vec_of(model, "vis.sam.pos_embed", SAM_SIDE * SAM_SIDE * SAM_D)?,
1283 sam,
1284 neck0,
1285 neck1_w: vec_of(model, "vis.sam.neck.1.weight", 256)?,
1286 neck1_b: vec_of(model, "vis.sam.neck.1.bias", 256)?,
1287 neck2,
1288 neck3_w: vec_of(model, "vis.sam.neck.3.weight", 256)?,
1289 neck3_b: vec_of(model, "vis.sam.neck.3.bias", 256)?,
1290 net2,
1291 net3,
1292 conv_w,
1293 cls: vec_of(model, "vis.clip.embeddings.class_embedding", CLIP_D)?,
1294 clip_pos: vec_of(model, "vis.clip.embeddings.position_embedding.weight", (CLIP_SIDE * CLIP_SIDE + 1) * CLIP_D)?,
1295 pre_w: vec_of(model, "vis.clip.pre_layrnorm.weight", CLIP_D)?,
1296 pre_b: vec_of(model, "vis.clip.pre_layrnorm.bias", CLIP_D)?,
1297 clip,
1298 proj: Mat::load(model, "vis.projector.layers.weight", OUT_D, 2 * CLIP_D)?,
1299 proj_b: vec_of(model, "vis.projector.layers.bias", OUT_D)?,
1300 newline: vec_of(model, "vis.image_newline", OUT_D)?,
1301 view_sep: vec_of(model, "vis.view_seperator", OUT_D)?,
1302 pool,
1303 cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1304 })
1305 }
1306
1307 fn tables(&self, g: usize) -> Arc<GridTables> {
1308 let mut c = self.cache.lock().unwrap();
1309 if let Some(t) = c.get(&g) {
1310 return t.clone();
1311 }
1312 let cg = g / 4;
1313 let mut clip_pos = self.clip_pos[..CLIP_D].to_vec();
1314 clip_pos.extend(resize_table(&self.clip_pos[CLIP_D..], CLIP_SIDE, cg, CLIP_D));
1315 let t = Arc::new(GridTables {
1316 pos: resize_table(&self.pos, SAM_SIDE, g, SAM_D),
1317 clip_pos,
1318 rel_global: SAM_GLOBAL
1319 .iter()
1320 .map(|&i| {
1321 let b = &self.sam[i];
1322 (resize_rel(&b.rel_h, SAM_HD, 2 * g - 1), resize_rel(&b.rel_w, SAM_HD, 2 * g - 1))
1323 })
1324 .collect(),
1325 });
1326 c.insert(g, t.clone());
1327 t
1328 }
1329
1330 #[allow(clippy::too_many_arguments)]
1333 fn sam_attention(&self, b: &SamBlock, x: &[f32], groups: usize, s: usize, rel_h: &[f32], rel_w: &[f32], out: &mut [f32]) {
1334 let pool = self.pool.as_deref();
1335 let n = s * s;
1336 let rows = groups * n;
1337 let (d, nh, hd) = (SAM_D, SAM_HEADS, SAM_HD);
1338 let mut qkv = vec![0f32; rows * 3 * d];
1339 b.qkv.matmat(x, rows, Some(&b.qkv_b), &mut qkv, pool);
1340 let scale = (hd as f32).sqrt().recip();
1341 let center = s - 1;
1342 let mut att = vec![0f32; rows * d];
1343 let aa = crate::pool::SendMut::new(att.as_mut_ptr());
1344 let one = |gi: usize, h: usize, qi: usize, sc: &mut [f32], rh: &mut [f32], rw: &mut [f32]| {
1347 let at = |r: usize, part: usize| &qkv[(gi * n + r) * 3 * d + part * d + h * hd..(gi * n + r) * 3 * d + part * d + (h + 1) * hd];
1348 let q = at(qi, 0);
1349 let (qy, qx) = (qi / s, qi % s);
1350 for ky in 0..s {
1351 let r = &rel_h[(qy + center - ky) * hd..(qy + center - ky + 1) * hd];
1352 rh[ky] = q.iter().zip(r).map(|(a, b)| a * b).sum();
1353 }
1354 for kx in 0..s {
1355 let r = &rel_w[(qx + center - kx) * hd..(qx + center - kx + 1) * hd];
1356 rw[kx] = q.iter().zip(r).map(|(a, b)| a * b).sum();
1357 }
1358 let mut mx = f32::NEG_INFINITY;
1359 for (kj, v) in sc.iter_mut().enumerate() {
1360 let k = at(kj, 1);
1361 let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum();
1362 *v = dot * scale + rh[kj / s] + rw[kj % s];
1363 mx = mx.max(*v);
1364 }
1365 let mut den = 0f32;
1366 for v in sc.iter_mut() {
1367 *v = (*v - mx).exp();
1368 den += *v;
1369 }
1370 let inv = den.recip();
1371 let o = unsafe { std::slice::from_raw_parts_mut(aa.at((gi * n + qi) * d + h * hd), hd) };
1373 o.iter_mut().for_each(|v| *v = 0.0);
1374 for (kj, &w) in sc.iter().enumerate() {
1375 let w = w * inv;
1376 for (a, &v) in o.iter_mut().zip(at(kj, 2)) {
1377 *a += w * v;
1378 }
1379 }
1380 };
1381 let total = rows * nh;
1382 let f = |lo: usize, hi: usize| {
1383 let (mut sc, mut rh, mut rw) = (vec![0f32; n], vec![0f32; s], vec![0f32; s]);
1384 for t in lo..hi {
1385 let (r, h) = (t / nh, t % nh);
1386 one(r / n, h, r % n, &mut sc, &mut rh, &mut rw);
1387 }
1388 };
1389 match pool {
1390 Some(p) => p.run_rows(total, &f),
1391 None => f(0, total),
1392 }
1393 b.proj.matmat(&att, rows, Some(&b.proj_b), out, pool);
1394 }
1395
1396 fn encode(&self, px: &[f32], side: usize) -> (Vec<f32>, Vec<f32>) {
1399 let pool = self.pool.as_deref();
1400 let g = side / PATCH;
1401 let n = g * g;
1402 let d = SAM_D;
1403 let tb = self.tables(g);
1404 let prof = std::env::var_os("CMF_OCR_PROF").is_some();
1405 let t0 = std::time::Instant::now();
1406 let mut rows = vec![0f32; n * 3 * PATCH * PATCH];
1407 for y in 0..g {
1408 for x in 0..g {
1409 let o = &mut rows[(y * g + x) * 3 * PATCH * PATCH..(y * g + x + 1) * 3 * PATCH * PATCH];
1410 let mut k = 0;
1411 for c in 0..3 {
1412 for py in 0..PATCH {
1413 let s = (c * side + y * PATCH + py) * side + x * PATCH;
1414 o[k..k + PATCH].copy_from_slice(&px[s..s + PATCH]);
1415 k += PATCH;
1416 }
1417 }
1418 }
1419 }
1420 let mut h = vec![0f32; n * d];
1421 self.patch.matmat(&rows, n, Some(&self.patch_b), &mut h, pool);
1422 for (a, &p) in h.iter_mut().zip(&tb.pos) {
1423 *a += p;
1424 }
1425 let mut dev_out = Vec::new();
1426 let device_sam = crate::gpu::enabled_here()
1427 && std::env::var("CMF_OCR_GPU").as_deref() != Ok("0")
1428 && self.sam_device(&h, g, &tb, &mut dev_out);
1429 if device_sam {
1430 let t_sam = t0.elapsed().as_secs_f64();
1431 let clip = self.clip_forward(&dev_out, g / 4, &tb, pool);
1432 if prof {
1433 eprintln!("uocr tower {side}² (device): sam+neck {t_sam:.3} s, clip {:.3} s", t0.elapsed().as_secs_f64() - t_sam);
1434 }
1435 return (dev_out, clip);
1436 }
1437 let mut hn = vec![0f32; n * d];
1438 let mut res = vec![0f32; n * d];
1439 let mut ff = vec![0f32; n * SAM_INTER];
1440 let mut gi = 0usize;
1441 for b in &self.sam {
1442 layer_norm_rows(&h, &b.n1_w, &b.n1_b, SAM_EPS, &mut hn, pool);
1443 if b.window == 0 {
1444 let (rh, rw) = &tb.rel_global[gi];
1445 gi += 1;
1446 self.sam_attention(b, &hn, 1, g, rh, rw, &mut res);
1447 } else {
1448 let w = b.window;
1450 let gp = g.div_ceil(w) * w;
1451 let wn = gp / w;
1452 let mut win = vec![0f32; wn * wn * w * w * d];
1453 for wy in 0..wn {
1454 for wx in 0..wn {
1455 for iy in 0..w {
1456 let y = wy * w + iy;
1457 if y >= g {
1458 continue;
1459 }
1460 for ix in 0..w {
1461 let x = wx * w + ix;
1462 if x >= g {
1463 continue;
1464 }
1465 let dst = ((wy * wn + wx) * w * w + iy * w + ix) * d;
1466 win[dst..dst + d].copy_from_slice(&hn[(y * g + x) * d..(y * g + x + 1) * d]);
1467 }
1468 }
1469 }
1470 }
1471 let mut wo = vec![0f32; win.len()];
1472 self.sam_attention(b, &win, wn * wn, w, &b.rel_h, &b.rel_w, &mut wo);
1473 debug_assert_eq!(b.rel_len, 2 * w - 1);
1474 for wy in 0..wn {
1475 for wx in 0..wn {
1476 for iy in 0..w {
1477 let y = wy * w + iy;
1478 if y >= g {
1479 continue;
1480 }
1481 for ix in 0..w {
1482 let x = wx * w + ix;
1483 if x >= g {
1484 continue;
1485 }
1486 let src = ((wy * wn + wx) * w * w + iy * w + ix) * d;
1487 res[(y * g + x) * d..(y * g + x + 1) * d].copy_from_slice(&wo[src..src + d]);
1488 }
1489 }
1490 }
1491 }
1492 }
1493 for (a, &r) in h.iter_mut().zip(&res) {
1494 *a += r;
1495 }
1496 layer_norm_rows(&h, &b.n2_w, &b.n2_b, SAM_EPS, &mut hn, pool);
1497 b.fc1.matmat(&hn, n, Some(&b.fc1_b), &mut ff, pool);
1498 gelu_rows(&mut ff, pool);
1499 b.fc2.matmat(&ff, n, Some(&b.fc2_b), &mut res, pool);
1500 for (a, &r) in h.iter_mut().zip(&res) {
1501 *a += r;
1502 }
1503 }
1504 let t_sam = t0.elapsed().as_secs_f64();
1505 let (mut c1, _) = conv_same(&h, g, d, &self.neck0, 256, 1, 1, 0);
1507 norm2d(&mut c1, 256, &self.neck1_w, &self.neck1_b);
1508 let (mut c2, _) = conv_same(&c1, g, 256, &self.neck2, 256, 3, 1, 1);
1509 norm2d(&mut c2, 256, &self.neck3_w, &self.neck3_b);
1510 let (c3, g2) = conv_same(&c2, g, 256, &self.net2, 512, 3, 2, 1);
1511 let (sam_out, g3) = conv_same(&c3, g2, 512, &self.net3, 1024, 3, 2, 1);
1512 let t_neck = t0.elapsed().as_secs_f64();
1513 let clip = self.clip_forward(&sam_out, g3, &tb, pool);
1514 if prof {
1515 eprintln!(
1516 "uocr tower {side}²: sam {t_sam:.3} s, neck {:.3} s, clip {:.3} s",
1517 t_neck - t_sam,
1518 t0.elapsed().as_secs_f64() - t_neck
1519 );
1520 }
1521 (sam_out, clip)
1522 }
1523
1524 fn sam_device(&self, h: &[f32], g: usize, tb: &GridTables, out: &mut Vec<f32>) -> bool {
1526 let layers = self
1527 .sam
1528 .iter()
1529 .map(|b| crate::gpu::SamLayer {
1530 ln1_w: &b.n1_w,
1531 ln1_b: &b.n1_b,
1532 qkv: b.qkv.weights(),
1533 qkv_b: &b.qkv_b,
1534 proj: b.proj.weights(),
1535 proj_b: &b.proj_b,
1536 rel: &b.rel_cat,
1537 ln2_w: &b.n2_w,
1538 ln2_b: &b.n2_b,
1539 fc1: b.fc1.weights(),
1540 fc1_b: &b.fc1_b,
1541 fc2: b.fc2.weights(),
1542 fc2_b: &b.fc2_b,
1543 window: b.window,
1544 })
1545 .collect();
1546 let conv = |w: &'_ [f32], ci: usize, co: usize, k: usize, stride: usize, pad: usize, norm: bool| crate::gpu::SamConv {
1547 w: unsafe { std::slice::from_raw_parts(w.as_ptr(), w.len()) },
1548 ci,
1549 co,
1550 k,
1551 stride,
1552 pad,
1553 norm,
1554 };
1555 let args = crate::gpu::SamArgs {
1556 key: self as *const Self as u64,
1557 layers,
1558 convs: vec![
1559 conv(&self.conv_w[0], SAM_D, 256, 1, 1, 0, true),
1560 conv(&self.conv_w[1], 256, 256, 3, 1, 1, true),
1561 conv(&self.conv_w[2], 256, 512, 3, 2, 1, false),
1562 conv(&self.conv_w[3], 512, 1024, 3, 2, 1, false),
1563 ],
1564 norms: vec![&self.neck1_w, &self.neck1_b, &self.neck3_w, &self.neck3_b],
1565 rel_global: tb
1566 .rel_global
1567 .iter()
1568 .map(|(rh, rw)| {
1569 let mut v = rh.clone();
1570 v.extend_from_slice(rw);
1571 v
1572 })
1573 .collect(),
1574 g,
1575 d: SAM_D,
1576 heads: SAM_HEADS,
1577 head_dim: SAM_HD,
1578 inter: SAM_INTER,
1579 eps: SAM_EPS,
1580 };
1581 crate::gpu::sam_encode(&args, h, out)
1582 }
1583
1584 fn clip_forward(&self, sam_out: &[f32], cg: usize, tb: &GridTables, pool: Option<&Pool>) -> Vec<f32> {
1586 let nc = cg * cg + 1;
1587 let cd = CLIP_D;
1588 let mut x = vec![0f32; nc * cd];
1589 x[..cd].copy_from_slice(&self.cls);
1590 x[cd..].copy_from_slice(sam_out);
1591 for (a, &p) in x.iter_mut().zip(&tb.clip_pos) {
1592 *a += p;
1593 }
1594 let mut xn = vec![0f32; nc * cd];
1595 layer_norm_rows(&x, &self.pre_w, &self.pre_b, CLIP_EPS, &mut xn, pool);
1596 std::mem::swap(&mut x, &mut xn);
1597 let resident = crate::gpu::enabled_here() && std::env::var("CMF_OCR_GPU").as_deref() != Ok("0") && {
1598 let (cos, sin) = (vec![1f32; nc * CLIP_HD], vec![0f32; nc * CLIP_HD]);
1599 let layers = self
1600 .clip
1601 .iter()
1602 .map(|l| crate::gpu::VitLayer {
1603 ln1_w: &l.n1_w,
1604 ln1_b: &l.n1_b,
1605 qkv: l.qkv.weights(),
1606 qkv_b: &l.qkv_b,
1607 proj: l.out.weights(),
1608 proj_b: &l.out_b,
1609 ln2_w: &l.n2_w,
1610 ln2_b: &l.n2_b,
1611 fc1: l.fc1.weights(),
1612 fc1_b: &l.fc1_b,
1613 fc2: l.fc2.weights(),
1614 fc2_b: &l.fc2_b,
1615 })
1616 .collect();
1617 let args = crate::gpu::VitArgs {
1618 key: &self.clip as *const _ as u64,
1619 layers,
1620 n: nc,
1621 d: cd,
1622 heads: CLIP_HEADS,
1623 head_dim: CLIP_HD,
1624 inter: CLIP_INTER,
1625 eps: CLIP_EPS,
1626 gelu_tanh: false,
1627 quick_gelu: true,
1628 rope_halves: true,
1629 cos: &cos,
1630 sin: &sin,
1631 prof: false,
1632 };
1633 crate::gpu::vit_encode(&args, &mut x)
1634 };
1635 if resident {
1636 return x[cd..].to_vec();
1637 }
1638 let mut qkv = vec![0f32; nc * 3 * cd];
1639 let (mut qh, mut kh, mut vh) = (vec![0f32; nc * cd], vec![0f32; nc * cd], vec![0f32; nc * cd]);
1640 let mut att = vec![0f32; nc * cd];
1641 let mut tmp = vec![0f32; nc * cd];
1642 let mut cf = vec![0f32; nc * CLIP_INTER];
1643 let scale = (CLIP_HD as f32).sqrt().recip();
1644 for l in &self.clip {
1645 layer_norm_rows(&x, &l.n1_w, &l.n1_b, CLIP_EPS, &mut xn, pool);
1646 l.qkv.matmat(&xn, nc, Some(&l.qkv_b), &mut qkv, pool);
1647 for p in 0..nc {
1649 let row = &qkv[p * 3 * cd..(p + 1) * 3 * cd];
1650 for hh in 0..CLIP_HEADS {
1651 let at = (hh * nc + p) * CLIP_HD;
1652 qh[at..at + CLIP_HD].copy_from_slice(&row[hh * CLIP_HD..(hh + 1) * CLIP_HD]);
1653 kh[at..at + CLIP_HD].copy_from_slice(&row[cd + hh * CLIP_HD..cd + (hh + 1) * CLIP_HD]);
1654 vh[at..at + CLIP_HD].copy_from_slice(&row[2 * cd + hh * CLIP_HD..2 * cd + (hh + 1) * CLIP_HD]);
1655 }
1656 }
1657 crate::onevision2::attend_host(&qh, &kh, &vh, CLIP_HEADS, CLIP_HD, nc, scale, &mut att, pool);
1658 l.out.matmat(&att, nc, Some(&l.out_b), &mut tmp, pool);
1659 for (a, &b) in x.iter_mut().zip(&tmp) {
1660 *a += b;
1661 }
1662 layer_norm_rows(&x, &l.n2_w, &l.n2_b, CLIP_EPS, &mut xn, pool);
1663 l.fc1.matmat(&xn, nc, Some(&l.fc1_b), &mut cf, pool);
1664 for v in cf.iter_mut() {
1666 *v /= 1.0 + (-1.702 * *v).exp();
1667 }
1668 l.fc2.matmat(&cf, nc, Some(&l.fc2_b), &mut tmp, pool);
1669 for (a, &b) in x.iter_mut().zip(&tmp) {
1670 *a += b;
1671 }
1672 }
1673 x[cd..].to_vec()
1674 }
1675
1676 fn view_rows(&self, px: &[f32], side: usize) -> Vec<f32> {
1678 let (sam, clip) = self.encode(px, side);
1679 let n = sam.len() / CLIP_D;
1680 let mut joined = vec![0f32; n * 2 * CLIP_D];
1681 for i in 0..n {
1682 joined[i * 2 * CLIP_D..i * 2 * CLIP_D + CLIP_D].copy_from_slice(&clip[i * CLIP_D..(i + 1) * CLIP_D]);
1683 joined[i * 2 * CLIP_D + CLIP_D..(i + 1) * 2 * CLIP_D].copy_from_slice(&sam[i * CLIP_D..(i + 1) * CLIP_D]);
1684 }
1685 let mut out = vec![0f32; n * OUT_D];
1686 self.proj.matmat(&joined, n, Some(&self.proj_b), &mut out, self.pool.as_deref());
1687 out
1688 }
1689
1690 pub fn page_rows(&self, v: &PageViews) -> Vec<f32> {
1693 let d = OUT_D;
1694 let mut out = Vec::new();
1695 if !v.tiles.is_empty() {
1696 let tiles: Vec<Vec<f32>> = v.tiles.iter().map(|t| self.view_rows(t, IMAGE_SIZE)).collect();
1697 let tg = IMAGE_SIZE / PATCH / DOWNSAMPLE;
1698 for ty in 0..v.rows {
1699 for iy in 0..tg {
1700 for tx in 0..v.cols {
1701 let t = &tiles[ty * v.cols + tx];
1702 out.extend_from_slice(&t[iy * tg * d..(iy + 1) * tg * d]);
1703 }
1704 out.extend_from_slice(&self.newline);
1705 }
1706 }
1707 }
1708 let g = self.view_rows(&v.global, BASE_SIZE);
1709 let gg = BASE_SIZE / PATCH / DOWNSAMPLE;
1710 for y in 0..gg {
1711 out.extend_from_slice(&g[y * gg * d..(y + 1) * gg * d]);
1712 out.extend_from_slice(&self.newline);
1713 }
1714 out.extend_from_slice(&self.view_sep);
1715 out
1716 }
1717}
1718
1719fn tower_for(model: &Arc<CmfModel>) -> Result<Arc<UocrTower>, String> {
1720 static TOWER: std::sync::Mutex<Option<(usize, Arc<UocrTower>)>> = std::sync::Mutex::new(None);
1721 let key = Arc::as_ptr(model) as usize;
1722 let mut g = TOWER.lock().unwrap();
1723 if let Some((k, t)) = g.as_ref() {
1724 if *k == key {
1725 return Ok(t.clone());
1726 }
1727 }
1728 let t = Arc::new(UocrTower::from_model(model, Pool::from_env())?);
1729 *g = Some((key, t.clone()));
1730 Ok(t)
1731}
1732
1733pub struct PreparedUocrPrompt {
1737 pub token_ids: Vec<u32>,
1738 pub rows: Vec<f32>,
1739 pub pages: usize,
1741}
1742
1743pub fn prepare_for_pipeline(pipeline: &Pipeline, sources: &[Value], prompt: Option<&str>) -> Result<PreparedUocrPrompt, String> {
1745 let model = pipeline.model.as_ref().ok_or("Unlimited-OCR requires a file-backed pipeline")?;
1746 prepare(model, pipeline, sources, prompt)
1747}
1748
1749pub fn prepare(
1752 model: &Arc<CmfModel>,
1753 pipeline: &Pipeline,
1754 sources: &[Value],
1755 prompt: Option<&str>,
1756) -> Result<PreparedUocrPrompt, String> {
1757 if sources.is_empty() {
1758 return Err("Unlimited-OCR reads images: pass at least one".into());
1759 }
1760 let tower = tower_for(model)?;
1761 let dash = pipeline.dash.as_ref().ok_or("Unlimited-OCR: the pipeline has no router")?;
1762 let (image_id, page_id) = (dash.image_id, dash.page_id);
1763 let tiled = sources.len() == 1;
1764 let tok = &pipeline.tokenizer;
1765 let mut ids = vec![BOS_ID];
1766 let mut media: Vec<Vec<f32>> = Vec::with_capacity(sources.len());
1767 let dump = std::env::var_os("CMF_OCR_DUMP").map(std::path::PathBuf::from);
1768 for (pi, source) in sources.iter().enumerate() {
1769 let t0 = std::time::Instant::now();
1770 let frame = media::decode_rgb(&media::load_image_bytes(source)?)?;
1771 let v = page_views(&frame, tiled);
1772 let t_pre = t0.elapsed().as_secs_f64();
1773 let rows = tower.page_rows(&v);
1774 let n = page_tokens(v.cols, v.rows);
1775 if rows.len() != n * OUT_D {
1776 return Err(format!("Unlimited-OCR: {} rows for {n} image tokens", rows.len() / OUT_D));
1777 }
1778 if let Some(dir) = &dump {
1779 let write = |name: String, data: &[f32]| {
1780 let bytes: Vec<u8> = data.iter().flat_map(|x| x.to_le_bytes()).collect();
1781 std::fs::write(dir.join(name), bytes).map_err(|e| format!("CMF_OCR_DUMP: {e}"))
1782 };
1783 write(format!("global_{pi}.f32"), &v.global)?;
1784 for (ti, t) in v.tiles.iter().enumerate() {
1785 write(format!("tile_{pi}_{ti}.f32"), t)?;
1786 }
1787 write(format!("rows_{pi}.f32"), &rows)?;
1788 }
1789 if dump.is_some() || std::env::var_os("CMF_OCR_PROF").is_some() {
1790 eprintln!(
1791 "uocr: page {pi} {}x{}, grid {}x{}, {n} tokens, preprocess {t_pre:.3} s, total {:.2} s",
1792 frame.width,
1793 frame.height,
1794 v.cols,
1795 v.rows,
1796 t0.elapsed().as_secs_f64()
1797 );
1798 }
1799 ids.push(page_id);
1800 ids.extend(std::iter::repeat_n(image_id, n));
1801 media.push(rows);
1802 }
1803 let instruction = prompt.filter(|p| !p.trim().is_empty()).unwrap_or("Multi page parsing.");
1804 let tail = tok.encode(instruction);
1805 ids.extend(tail);
1806 let h = pipeline.hidden_size;
1807 let mut rows = Vec::with_capacity(ids.len() * h);
1808 let mut flat = media.iter().flatten().copied();
1809 for &id in &ids {
1810 if id == image_id {
1811 rows.extend((&mut flat).take(h));
1812 } else {
1813 rows.extend(pipeline.embed_id(id));
1814 }
1815 }
1816 if rows.len() != ids.len() * h {
1817 return Err("Unlimited-OCR: placeholder and image row counts differ".into());
1818 }
1819 Ok(PreparedUocrPrompt { token_ids: ids, rows, pages: sources.len() })
1820}
1821
1822pub fn request_parts(messages: &[Value]) -> Result<(Vec<Value>, Option<String>), String> {
1825 let mut sources = Vec::new();
1826 let mut text = None;
1827 for message in messages {
1828 match message.get("content") {
1829 Some(Value::String(t)) => text = Some(t.clone()),
1830 Some(Value::Array(parts)) => {
1831 for part in parts {
1832 let typ = part.get("type").and_then(Value::as_str).unwrap_or("");
1833 if typ == "image" || typ == "image_url" || part.get("image").is_some() || part.get("image_url").is_some() {
1834 let source = part.get("image_url").or_else(|| part.get("image")).unwrap_or(part);
1835 sources.push(match source.as_str() {
1836 Some(url) => serde_json::json!({"url": url}),
1837 None => source.clone(),
1838 });
1839 } else if let Some(t) = part.get("text").and_then(Value::as_str) {
1840 text = Some(t.to_string());
1841 } else if typ != "text" {
1842 return Err(format!("Unlimited-OCR: unsupported content part '{typ}' (text and images only)"));
1843 }
1844 }
1845 }
1846 _ => {}
1847 }
1848 }
1849 Ok((sources, text))
1850}
1851
1852pub struct PageGuard {
1854 pub expected: usize,
1855 pub page_id: u32,
1856 pub eos_id: u32,
1857 pub max_page_tokens: usize,
1858 marker_cum: usize,
1859 scanned: usize,
1860 page_len: usize,
1861}
1862
1863impl PageGuard {
1864 pub fn new(expected: usize, page_id: u32, eos_id: u32) -> Self {
1865 Self { expected, page_id, eos_id, max_page_tokens: 20000, marker_cum: 0, scanned: 0, page_len: 0 }
1866 }
1867
1868 pub fn apply(&mut self, logits: &mut [f32], generated: &[u32]) {
1869 if self.expected == 0 {
1870 return;
1871 }
1872 if generated.len() > self.scanned {
1873 let tail = &generated[self.scanned..];
1874 let m = tail.iter().filter(|&&t| t == self.page_id).count();
1875 if m > 0 {
1876 self.marker_cum += m;
1877 self.page_len = tail.iter().rev().position(|&t| t == self.page_id).unwrap_or(0);
1878 } else {
1879 self.page_len += tail.len();
1880 }
1881 self.scanned = generated.len();
1882 }
1883 let only = |logits: &mut [f32], t: u32| {
1884 let keep = logits[t as usize];
1885 logits.iter_mut().for_each(|v| *v = f32::NEG_INFINITY);
1886 logits[t as usize] = keep;
1887 };
1888 if self.marker_cum > self.expected {
1889 only(logits, self.eos_id);
1890 } else if self.max_page_tokens > 0 && self.page_len >= self.max_page_tokens {
1891 let t = if self.marker_cum < self.expected { self.page_id } else { self.eos_id };
1892 only(logits, t);
1893 } else if self.marker_cum < self.expected {
1894 logits[self.eos_id as usize] = f32::NEG_INFINITY;
1895 }
1896 }
1897}
1898
1899#[cfg(test)]
1900mod tests {
1901 use super::*;
1902
1903 #[test]
1904 fn page_tokens_match_the_trained_layout() {
1905 assert_eq!(page_tokens(1, 1), 273);
1906 assert_eq!(page_tokens(3, 4), 273 + 31 * 40);
1908 }
1909
1910 #[test]
1911 fn page_coordinates_follow_the_reference() {
1912 let (bos, pg, im, t) = (0u32, 9u32, 7u32, 5u32);
1914 let ids = [bos, pg, im, im, im, pg, im, im, t, t];
1915 let mut r = PageRope::begin(&ids, im, pg, 4).unwrap();
1916 let c: Vec<_> = (0..ids.len()).map(|i| r.prompt_coords(i)).collect();
1917 assert_eq!(c[0], (0, 0));
1918 assert_eq!(c[1], (0, 1));
1919 assert_eq!(c[4], (3, 1));
1920 assert_eq!(c[5], (0, 2));
1921 assert_eq!(c[7], (2, 2));
1922 assert_eq!(c[8], (1, 0));
1924 assert_eq!(c[9], (2, 0));
1925 assert_eq!(r.decode_step(pg), (4, 1));
1927 assert_eq!(r.decode_step(t), (5, 1));
1928 assert_eq!(r.decode_step(pg), (3, 2));
1929 }
1930
1931 #[test]
1932 fn decode_candidates_follow_the_response_page() {
1933 let (bos, pg, im, t) = (0u32, 9u32, 7u32, 5u32);
1934 let mut ids = vec![bos, pg];
1935 ids.extend(std::iter::repeat_n(im, 6));
1936 ids.push(pg);
1937 ids.extend(std::iter::repeat_n(im, 7));
1938 ids.push(t);
1939 let mut r = PageRope::begin(&ids, im, pg, 4).unwrap();
1940 let mut allow = Vec::new();
1941 r.decode_allow(5, &mut allow);
1943 assert!(allow.iter().all(|&a| a));
1944 r.decode_step(pg);
1945 r.decode_allow(5, &mut allow);
1946 assert_eq!(allow, vec![true, true, false, false, false]);
1947 r.decode_step(pg);
1948 r.decode_allow(5, &mut allow);
1949 assert_eq!(allow, vec![false, false, true, true, false]);
1950 }
1951
1952 #[test]
1953 fn entmax_matches_a_hand_case() {
1954 let mut y = [0f32; 3];
1956 entmax15_sorted(&[0.0, 0.0, -50.0], &mut y);
1957 assert!((y[0] - 0.5).abs() < 1e-6, "{y:?}");
1958 assert!((y[1] - 0.5).abs() < 1e-6);
1959 assert_eq!(y[2], 0.0);
1960 let mut z = [0f32; 4];
1962 entmax15_sorted(&[3.0, 2.5, 1.0, 0.0], &mut z);
1963 assert!((z.iter().sum::<f32>() - 1.0).abs() < 1e-5);
1964 }
1965
1966 #[test]
1967 fn route_centers_the_bias_on_the_read_set() {
1968 let p = RouteParams { idx_scale: 1.0, floor: 1e-4, sigma: 1.0, k: 2, top_m: 16 };
1969 let scores = [Some(1.0), Some(5.0), None, Some(4.0)];
1970 let allow = [true, true, true, true];
1971 let (mut w, mut pick) = (Vec::new(), Vec::new());
1972 route_select(&scores, &allow, &p, &mut w, &mut pick);
1973 assert_eq!(pick.len(), 2);
1974 assert_eq!(pick[0].0, 1);
1975 assert_eq!(pick[1].0, 3);
1976 assert!((pick[0].1 + pick[1].1).abs() < 1e-5);
1977 let allow = [true, false, false, false];
1979 route_select(&scores, &allow, &p, &mut w, &mut pick);
1980 assert_eq!(pick.len(), 1);
1981 assert_eq!(pick[0].0, 0);
1982 }
1983
1984 #[test]
1985 fn closest_grid_picks_the_page_aspect() {
1986 let (c, r) = closest_grid(1191, 1684, 640);
1988 assert!((c as f64 / r as f64 - 1191.0 / 1684.0).abs() < 0.05, "{c}x{r}");
1989 }
1990
1991 #[test]
1992 fn conv_reads_kernels_as_channel_out_in_y_x() {
1993 let mut w = vec![0f32; 9];
1995 w[4] = 2.0;
1996 let x = [1.0, 2.0, 3.0, 4.0];
1997 let (y, g) = conv_same(&x, 2, 1, &w, 1, 3, 1, 1);
1998 assert_eq!(g, 2);
1999 assert_eq!(y, vec![2.0, 4.0, 6.0, 8.0]);
2000 }
2001}