1use crate::Pipeline;
26use crate::pipeline::masked::SetMask;
27use crate::tokenizer::Tokenizer;
28use cortiq_core::CmfModel;
29use serde_json::{Map, Value, json};
30
31const IM_START: &str = "<|im_start|>";
32const IM_END: &str = "<|im_end|>";
33const LEAD: &str = "<think></think>";
34const ZWSP: char = '\u{200b}';
35const EDGES: [&str; 4] = ["top", "bottom", "left", "right"];
36const INSTANCES: usize = 16;
37const MAX_INSTANCES: usize = 64;
38const DRAWS: usize = 512;
39
40#[derive(Clone, Debug, Default)]
44pub struct Calibration {
45 default: f64,
46 temperatures: Map<String, Value>,
47 tables: Map<String, Value>,
48}
49
50fn through_knots(value: f64, knots: &[(f64, f64)]) -> f64 {
53 if knots.is_empty() {
54 return value;
55 }
56 let index = knots.partition_point(|&(raw, _)| raw < value);
57 if index == 0 {
58 return knots[0].1;
59 }
60 if index == knots.len() {
61 return knots[knots.len() - 1].1;
62 }
63 let ((a, pa), (b, pb)) = (knots[index - 1], knots[index]);
64 pa + (value - a) * (pb - pa) / (b - a)
65}
66
67impl Calibration {
68 fn from_json(v: &Value) -> Self {
69 Self {
70 default: v.get("default").and_then(Value::as_f64).unwrap_or(1.0),
71 temperatures: v.get("temperatures").and_then(Value::as_object).cloned().unwrap_or_default(),
72 tables: v.get("tables").and_then(Value::as_object).cloned().unwrap_or_default(),
73 }
74 }
75
76 fn knots(&self, name: &str) -> Vec<(f64, f64)> {
77 self.tables
78 .get(name)
79 .and_then(Value::as_array)
80 .map(|rows| {
81 rows.iter()
82 .filter_map(|r| Some((r.get(0)?.as_f64()?, r.get(1)?.as_f64()?)))
83 .collect()
84 })
85 .unwrap_or_default()
86 }
87
88 fn temperature(&self, bucket: &str) -> f64 {
89 self.temperatures.get(bucket).and_then(Value::as_f64).unwrap_or(self.default)
90 }
91
92 fn through(&self, name: &str, value: f64) -> f64 {
93 through_knots(value, &self.knots(name))
94 }
95
96 fn level(&self, name: &str, share: f64) -> f64 {
98 let flipped: Vec<(f64, f64)> = self.knots(name).into_iter().map(|(x, y)| (y, x)).collect();
99 through_knots(share, &flipped)
100 }
101}
102
103#[derive(Clone, Debug)]
105pub struct D1Spec {
106 codes: u8,
107 set_size: usize,
108 numbers: bool,
109 vision_max_pixels: usize,
110 edges_interp: bool,
111 prompt: u8,
112 calibration: Calibration,
113}
114
115pub fn is_d1(model: &CmfModel) -> bool {
117 model
118 .header
119 .provenance
120 .as_ref()
121 .is_some_and(|p| p.get("d1").is_some())
122}
123
124impl D1Spec {
125 pub fn from_model(model: &CmfModel) -> Result<Self, String> {
126 let p = model
127 .header
128 .provenance
129 .as_ref()
130 .and_then(|p| p.get("d1"))
131 .ok_or("not a d1 file (no provenance.d1)")?;
132 let c = &p["config"];
133 let attention = c.get("d1_attention").and_then(Value::as_str).unwrap_or("causal");
134 if attention != "set" {
135 return Err(format!("d1: attention '{attention}' is not supported (set only)"));
136 }
137 Ok(Self {
138 codes: c.get("d1_codes").and_then(Value::as_u64).unwrap_or(1) as u8,
139 set_size: c.get("d1_set_size").and_then(Value::as_u64).unwrap_or(16) as usize,
140 numbers: c.get("d1_numbers").and_then(Value::as_bool).unwrap_or(false),
141 vision_max_pixels: c
142 .get("d1_vision_max_pixels")
143 .and_then(Value::as_u64)
144 .unwrap_or(1_638_400) as usize,
145 edges_interp: c.get("d1_box_edges").and_then(Value::as_str) == Some("interp"),
146 prompt: c.get("d1_prompt").and_then(Value::as_u64).unwrap_or(1) as u8,
147 calibration: Calibration::from_json(&p["calibration"]),
148 })
149 }
150}
151
152#[derive(Clone, Debug, PartialEq)]
155enum ReadKind {
156 Text,
157 Number,
158 Date,
159}
160
161impl ReadKind {
162 fn name(&self) -> &'static str {
163 match self {
164 ReadKind::Text => "text",
165 ReadKind::Number => "number",
166 ReadKind::Date => "date",
167 }
168 }
169 fn cap(&self) -> usize {
171 match self {
172 ReadKind::Text => 512,
173 _ => 32,
174 }
175 }
176}
177
178#[derive(Clone, Debug)]
179enum Question {
180 Choice { said: String, criteria: Vec<(String, Option<String>)>, multiple: bool },
181 Noul { said: String, yes_no: Option<(String, String)> },
182 Score { said: String, levels: Vec<String> },
183 Read { said: String, kind: ReadKind, unit: Option<String> },
184 Estimate { said: String, grid: Grid, unit: Option<String> },
185 Box { said: String },
186 Boxes { said: String, max: Option<usize> },
187}
188
189impl Question {
190 fn said(&self) -> &str {
191 match self {
192 Question::Choice { said, .. }
193 | Question::Noul { said, .. }
194 | Question::Score { said, .. }
195 | Question::Read { said, .. }
196 | Question::Estimate { said, .. }
197 | Question::Box { said }
198 | Question::Boxes { said, .. } => said,
199 }
200 }
201
202 fn typed(&self) -> bool {
204 matches!(
205 self,
206 Question::Read { .. }
207 | Question::Estimate { .. }
208 | Question::Box { .. }
209 | Question::Boxes { .. }
210 | Question::Choice { multiple: true, .. }
211 )
212 }
213}
214
215fn py_str(v: &Value) -> String {
216 match v {
217 Value::String(s) => s.clone(),
218 Value::Null => "None".into(),
219 other => other.to_string(),
220 }
221}
222
223fn parse_question(q: &Value, prompt: u8) -> Result<Question, String> {
226 let obj = q.as_object().ok_or(
227 "a question is an object such as {\"type\": \"noul\", \"instructions\": \"Is it urgent?\"}",
228 )?;
229 let kind = obj.get("type").and_then(Value::as_str).unwrap_or("");
230 const TYPES: [&str; 8] = ["noul", "choice", "score", "text", "number", "date", "box", "boxes"];
231 if !TYPES.contains(&kind) {
232 return Err(format!("\"type\" is one of {}, not {kind:?}", TYPES.join(", ")));
233 }
234 let said = match obj.get("instructions") {
235 Some(Value::String(s)) if !s.trim().is_empty() => s.clone(),
236 Some(Value::Object(_)) => return Err("spoken instructions need an audio model (d1-Omni)".into()),
237 _ => return Err("\"instructions\" is the question as text".into()),
238 };
239 let criteria = obj.get("criteria");
240 if obj.get("multiple").and_then(Value::as_bool).unwrap_or(false) && kind != "choice" {
241 return Err("\"multiple\" belongs to a choice".into());
242 }
243 if obj.contains_key("max") {
244 let most = if prompt == 1 { INSTANCES } else { MAX_INSTANCES };
245 let ok = kind == "boxes"
246 && obj["max"].as_u64().is_some_and(|m| m >= 1 && m as usize <= most);
247 if !ok {
248 return Err(format!("\"max\" belongs to boxes, a whole number from 1 to {most}"));
249 }
250 }
251 if obj.contains_key("step") && !obj.contains_key("range") {
252 return Err("\"step\" needs a \"range\"".into());
253 }
254 let unit = obj.get("unit").and_then(Value::as_str).map(str::to_string);
255 if let Some(range) = obj.get("range") {
256 let bounds: Vec<f64> = range
257 .as_array()
258 .filter(|a| a.len() == 2)
259 .map(|a| a.iter().filter_map(Value::as_f64).collect())
260 .unwrap_or_default();
261 if kind != "number" || bounds.len() != 2 {
262 return Err("an estimate is {\"type\": \"number\", \"range\": [low, high]}, with an optional \"step\"".into());
263 }
264 let (low, high) = (bounds[0], bounds[1]);
265 let step = obj.get("step").and_then(Value::as_f64);
266 let even = |s: f64| ((high - low) / s - ((high - low) / s).round()).abs() <= 1e-9 * ((high - low) / s).abs().max(1.0);
267 if low >= high || step.is_some_and(|s| !(s > 0.0 && even(s))) {
268 return Err("an estimate's range runs low to high and its step, above 0, divides it".into());
269 }
270 let size = match step {
271 None => 128,
272 Some(s) => 128.min(((high - low) / s).round() as usize + 1),
273 };
274 return Ok(Question::Estimate { said, grid: Grid { low, high, size }, unit });
275 }
276 Ok(match kind {
277 "text" => Question::Read { said, kind: ReadKind::Text, unit },
278 "number" => Question::Read { said, kind: ReadKind::Number, unit },
279 "date" => Question::Read { said, kind: ReadKind::Date, unit },
280 "box" => Question::Box { said },
281 "boxes" => Question::Boxes { said, max: obj.get("max").and_then(Value::as_u64).map(|m| m as usize) },
282 "noul" => {
283 let yes_no = match criteria {
284 None | Some(Value::Null) => None,
285 Some(Value::Object(m)) => Some((
286 py_str(m.get("true").unwrap_or(&Value::Null)),
287 py_str(m.get("false").unwrap_or(&Value::Null)),
288 )),
289 _ => return Err("a noul's \"criteria\", when given, is {\"true\": ..., \"false\": ...}".into()),
290 };
291 Question::Noul { said, yes_no }
292 }
293 "score" => {
294 let levels: Vec<String> = criteria
295 .and_then(Value::as_array)
296 .map(|a| a.iter().filter_map(Value::as_str).map(str::to_string).collect())
297 .unwrap_or_default();
298 let n = criteria.and_then(Value::as_array).map_or(0, |a| a.len());
299 if !(2..=10).contains(&n) || levels.len() != n {
300 return Err("a score's \"criteria\" lists 2 to 10 levels, lowest first".into());
301 }
302 Question::Score { said, levels }
303 }
304 _ => {
305 let m = criteria.and_then(Value::as_object).filter(|m| !m.is_empty()).ok_or(
306 "a choice's \"criteria\" names the options: {\"billing\": \"Charges and refunds\"}",
307 )?;
308 Question::Choice {
309 said,
310 criteria: m
311 .iter()
312 .map(|(k, v)| (k.clone(), v.as_str().map(str::to_string)))
313 .collect(),
314 multiple: obj.get("multiple").and_then(Value::as_bool).unwrap_or(false),
315 }
316 }
317 })
318}
319
320#[derive(Clone, Copy, Debug, PartialEq)]
323struct Grid {
324 low: f64,
325 high: f64,
326 size: usize,
327}
328
329const THOUSANDTHS: Grid = Grid { low: 0.0, high: 0.999, size: 1000 };
331const EDGE_CODES: Grid = Grid { low: 0.0, high: 1.0, size: 128 };
333
334impl Grid {
335 fn step(&self) -> f64 {
336 (self.high - self.low) / (self.size - 1) as f64
337 }
338
339 fn values(&self) -> Vec<f64> {
340 let step = self.step();
341 let mut v: Vec<f64> = (0..self.size - 1).map(|i| self.low + i as f64 * step).collect();
342 v.push(self.high);
343 v
344 }
345
346 fn read(&self, probs: &[f64], level: Option<&dyn Fn(f64) -> f64>) -> Map<String, Value> {
349 let total: f64 = probs.iter().sum();
350 let values = self.values();
351 let mut out = Map::new();
352 out.insert(
353 "mean".into(),
354 json!(values.iter().zip(probs).map(|(v, p)| v * (p / total)).sum::<f64>()),
355 );
356 let at: Vec<(&str, f64)> = [("p10", 0.1), ("median", 0.5), ("p90", 0.9)]
357 .iter()
358 .map(|&(k, q)| (k, level.map_or(q, |f| f(q))))
359 .collect();
360 let mut mass = 0.0;
361 for (value, p) in values.iter().zip(probs) {
362 mass += p / total;
363 for &(key, quantile) in &at {
364 if !out.contains_key(key) && mass >= quantile {
365 out.insert(key.into(), json!(*value));
366 }
367 }
368 }
369 for &(key, _) in &at {
370 out.entry(key.to_string()).or_insert(json!(values[values.len() - 1]));
371 }
372 out
373 }
374}
375
376fn py_g(x: f64) -> String {
379 if x == 0.0 {
380 return if x.is_sign_negative() { "-0".into() } else { "0".into() };
381 }
382 if !x.is_finite() {
383 return if x.is_nan() { "nan".into() } else if x > 0.0 { "inf".into() } else { "-inf".into() };
384 }
385 let sci = format!("{:.5e}", x);
386 let (mant, exp) = sci.split_once('e').unwrap();
387 let exp: i32 = exp.parse().unwrap();
388 if !(-4..6).contains(&exp) {
389 let mant = mant.trim_end_matches('0').trim_end_matches('.');
390 return format!("{mant}e{}{:02}", if exp < 0 { '-' } else { '+' }, exp.abs());
391 }
392 let decimals = (5 - exp).max(0) as usize;
393 let s = format!("{:.*}", decimals, x);
394 if s.contains('.') {
395 s.trim_end_matches('0').trim_end_matches('.').to_string()
396 } else {
397 s
398 }
399}
400
401const LETTERS: [&str; 26] = [
404 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
405 "V", "W", "X", "Y", "Z",
406];
407
408fn option_codes(labels: &[String], scheme: u8) -> Vec<String> {
409 let labs: Vec<String> = labels.iter().map(|x| x.trim().to_string()).collect();
410 if !labs.is_empty() && labs.iter().all(|k| k.chars().count() == 1 && k.chars().all(char::is_alphabetic)) {
411 return labs;
412 }
413 if labs.len() <= 26 {
414 return LETTERS[..labs.len()].iter().map(|s| s.to_string()).collect();
415 }
416 if scheme == 2 {
417 let mut all: Vec<String> = LETTERS.iter().map(|s| s.to_string()).collect();
418 for a in LETTERS {
419 for b in LETTERS {
420 all.push(format!("{a}{b}"));
421 }
422 }
423 all.truncate(labs.len());
424 return all;
425 }
426 (0..labs.len()).map(|i| format!("{i:02}")).collect()
427}
428
429struct Tok<'a> {
430 tok: &'a Tokenizer,
431 specials: Vec<String>,
432}
433
434impl<'a> Tok<'a> {
435 fn new(tok: &'a Tokenizer) -> Self {
436 let mut specials: Vec<String> = tok
437 .special_texts()
438 .into_iter()
439 .filter(|s| s.chars().count() > 1 && !s.contains(ZWSP))
440 .collect();
441 specials.sort_by_key(|s| std::cmp::Reverse(s.len()));
442 Self { tok, specials }
443 }
444
445 fn encode(&self, text: &str) -> Vec<u32> {
446 self.tok.encode(text)
447 }
448
449 fn single(&self, text: &str) -> Option<u32> {
450 let ids = self.encode(text);
451 (ids.len() == 1).then(|| ids[0])
452 }
453
454 fn ids(&self, texts: &[String]) -> Vec<u32> {
456 let mut out = Vec::new();
457 for t in texts {
458 if let Some(id) = self.single(t) {
459 if !out.contains(&id) {
460 out.push(id);
461 }
462 }
463 }
464 out
465 }
466
467 fn aliases(&self, labels: &[String], scheme: u8) -> Result<Vec<(String, u32)>, String> {
469 let codes = option_codes(labels, scheme);
470 let mut pool: Vec<String> = ('A'..='Z').map(|c| c.to_string()).collect();
471 pool.extend((0..100).map(|i| format!("{i:02}")));
472 pool.extend(('a'..='z').map(|c| c.to_string()));
473 pool.extend((0..200).map(|i| format!("#{i}")));
474 for a in 'A'..='Z' {
475 for b in 'A'..='Z' {
476 pool.push(format!("{a}{b}"));
477 }
478 }
479 let mut used = Vec::new();
480 let mut out = Vec::new();
481 for code in codes {
482 let mut take = |raw: &str| -> bool {
483 match self.single(raw) {
484 Some(id) if !used.contains(&id) => {
485 out.push((raw.to_string(), id));
486 used.push(id);
487 true
488 }
489 _ => false,
490 }
491 };
492 if take(&code) {
493 continue;
494 }
495 if !pool.iter().any(|raw| take(raw)) {
496 return Err(format!("no single-token alias left for {} options", labels.len()));
497 }
498 }
499 Ok(out)
500 }
501
502 fn plain_offsets(&self, text: &str) -> (String, Vec<usize>) {
506 let mut text: Vec<char> = text.chars().collect();
507 let mut boundaries: Vec<usize> = (0..=text.len()).collect();
508 if self.specials.is_empty() {
509 return (text.into_iter().collect(), boundaries);
510 }
511 let pats: Vec<Vec<char>> = self.specials.iter().map(|s| s.chars().collect()).collect();
512 loop {
513 let mut hits = Vec::new();
515 let mut i = 0;
516 while i < text.len() {
517 let found = pats.iter().find(|p| text[i..].starts_with(p));
518 match found {
519 Some(p) => {
520 hits.push(i);
521 i += p.len();
522 }
523 None => i += 1,
524 }
525 }
526 if hits.is_empty() {
527 break;
528 }
529 for &at in hits.iter().rev() {
530 boundaries.insert(at + 1, boundaries[at + 1]);
531 text.insert(at + 1, ZWSP);
532 }
533 }
534 (text.into_iter().collect(), boundaries)
535 }
536
537 fn plain(&self, text: &str) -> String {
538 self.plain_offsets(text).0
539 }
540
541 fn encode_offsets(&self, text: &str) -> (Vec<u32>, Vec<(usize, usize)>) {
544 let ids = self.encode(text);
545 let char_of_byte: Vec<usize> = {
546 let mut v = vec![0usize; text.len() + 1];
547 let mut ci = 0;
548 for (bi, ch) in text.char_indices() {
549 for k in 0..ch.len_utf8() {
550 v[bi + k] = ci;
551 }
552 ci += 1;
553 }
554 v[text.len()] = ci;
555 v
556 };
557 let mut offsets = Vec::with_capacity(ids.len());
558 let mut at = 0usize;
559 for &id in &ids {
560 let n = self.tok.token_bytes(id).len();
561 let (a, b) = (at.min(text.len()), (at + n).min(text.len()));
562 let start = char_of_byte[a];
563 let end = if b > a { char_of_byte[b - 1] + 1 } else { start };
564 offsets.push((start, end));
565 at += n;
566 }
567 (ids, offsets)
568 }
569}
570
571#[derive(Clone, Debug)]
575enum Slot {
576 Position,
578 Codes { groups: Vec<Vec<u32>>, bucket: Option<String> },
581}
582
583fn bucket(kind: &str, k: usize) -> String {
584 match kind {
585 "noul" => "noul".into(),
586 "score" => format!("score:{k}"),
587 _ if k <= 3 => "choice:small".into(),
588 _ if k <= 8 => "choice:mid".into(),
589 _ if k <= 26 => "choice:large".into(),
590 _ => "choice:inventory".into(),
591 }
592}
593
594struct Proto<'a> {
595 t: Tok<'a>,
596 spec: &'a D1Spec,
597}
598
599impl Proto<'_> {
600 fn yes_no(&self) -> Result<Vec<Vec<u32>>, String> {
601 let s = |v: &[&str]| v.iter().map(|x| x.to_string()).collect::<Vec<_>>();
602 let (yes, no) = (self.t.ids(&s(&["yes", "Yes", "YES"])), self.t.ids(&s(&["no", "No", "NO"])));
603 if yes.is_empty() || no.is_empty() {
604 return Err("tokenizer has no single-token yes/no".into());
605 }
606 Ok(vec![yes, no])
607 }
608
609 fn score_groups(&self, n: usize) -> Result<Vec<Vec<u32>>, String> {
610 let groups: Vec<Vec<u32>> = (0..n).map(|i| self.t.ids(&[i.to_string()])).collect();
611 if groups.iter().any(Vec::is_empty) {
612 return Err(format!("score with {n} levels needs single-token digits"));
613 }
614 Ok(groups)
615 }
616
617 fn choice_groups(&self, labels: &[String]) -> Result<Vec<Vec<u32>>, String> {
618 let mut groups = Vec::new();
619 for (code, tid) in self.t.aliases(labels, self.spec.codes)? {
620 let mut g = vec![tid];
621 g.extend(self.t.ids(&[format!(" {code}")]).into_iter().filter(|&i| i != tid));
622 groups.push(g);
623 }
624 Ok(groups)
625 }
626
627 fn number_ids(&self, grid: &Grid, scale: usize) -> Option<Vec<Vec<u32>>> {
630 let mut groups = Vec::new();
631 for value in grid.values() {
632 let x = value * scale as f64;
633 let n = x.round_ties_even();
634 if (n - x).abs() > 1e-6 {
635 return None;
636 }
637 let n = n as i64;
638 let group = self.t.ids(&[n.to_string(), format!(" {n}")]);
639 if group.is_empty() || self.t.encode(&n.to_string()).len() != 1 {
640 return None;
641 }
642 groups.push(group);
643 }
644 Some(groups)
645 }
646
647 fn grid_labels(grid: &Grid) -> Vec<String> {
649 grid.values().iter().map(|&v| py_g(v)).collect()
650 }
651
652 fn grid_slot(&self, grid: &Grid, scale: usize) -> Result<Slot, String> {
655 if self.spec.numbers {
656 if let Some(groups) = self.number_ids(grid, scale) {
657 return Ok(Slot::Codes { groups, bucket: None });
658 }
659 }
660 let labels = Self::grid_labels(grid);
661 let groups = if grid.size <= 10 {
662 self.score_groups(grid.size)?
663 } else {
664 let keys: Vec<String> = (0..labels.len()).map(|i| i.to_string()).collect();
665 self.choice_groups(&keys)?
666 };
667 Ok(Slot::Codes { groups, bucket: None })
668 }
669
670 fn uses_numbers(&self, grid: &Grid, scale: usize) -> bool {
671 self.spec.numbers && self.number_ids(grid, scale).is_some()
672 }
673
674 fn edge_grid(&self) -> (Grid, usize) {
676 if self.uses_numbers(&THOUSANDTHS, 1000) {
677 (THOUSANDTHS, 1000)
678 } else {
679 (EDGE_CODES, 1)
680 }
681 }
682
683 fn box_slots(&self) -> Result<Vec<Slot>, String> {
684 let (grid, scale) = self.edge_grid();
685 let mut out = vec![Slot::Codes { groups: self.yes_no()?, bucket: None }];
686 for _ in EDGES {
687 out.push(self.grid_slot(&grid, scale)?);
688 }
689 Ok(out)
690 }
691
692 fn slots(&self, q: &Question) -> Result<Vec<Slot>, String> {
693 Ok(match q {
694 Question::Read { .. } => vec![Slot::Position, Slot::Position],
695 Question::Estimate { grid, .. } => vec![self.grid_slot(grid, 1)?],
696 Question::Box { .. } => self.box_slots()?,
697 Question::Boxes { max, .. } => {
698 let one = self.box_slots()?;
699 let blocks = max.unwrap_or(INSTANCES);
700 (0..blocks).flat_map(|_| one.clone()).collect()
701 }
702 Question::Choice { criteria, multiple: true, .. } => {
703 let yn = self.yes_no()?;
704 criteria.iter().map(|_| Slot::Codes { groups: yn.clone(), bucket: None }).collect()
705 }
706 Question::Noul { .. } => vec![Slot::Codes { groups: self.yes_no()?, bucket: Some(bucket("noul", 2)) }],
707 Question::Score { levels, .. } => vec![Slot::Codes {
708 groups: self.score_groups(levels.len())?,
709 bucket: Some(bucket("score", levels.len())),
710 }],
711 Question::Choice { criteria, .. } => {
712 let labels: Vec<String> = criteria.iter().map(|(k, _)| k.clone()).collect();
713 vec![Slot::Codes {
714 groups: self.choice_groups(&labels)?,
715 bucket: Some(bucket("choice", labels.len())),
716 }]
717 }
718 })
719 }
720
721 fn edge_legend(&self) -> Result<String, String> {
724 let (grid, scale) = self.edge_grid();
725 if scale > 1 {
726 return Ok("Each edge is a number from 0 to 999, in thousandths of the picture: top and bottom from its \
727 top edge, left and right from its left edge."
728 .into());
729 }
730 let keys: Vec<String> = (0..grid.size).map(|i| i.to_string()).collect();
731 let codes = self.t.aliases(&keys, self.spec.codes)?;
732 Ok(format!(
733 "Each edge is a code on a {}-value scale: {} is 0 (top or left), {} is 1 (bottom or right). Values are \
734 equally spaced.",
735 grid.size,
736 codes[0].0,
737 codes[codes.len() - 1].0
738 ))
739 }
740
741 fn question_block(&self, q: &Question) -> Result<String, String> {
742 let said = q.said();
743 Ok(match q {
744 Question::Box { .. } => {
745 let one = if self.edge_grid().1 > 1 { "one number" } else { "one code" };
746 format!(
747 "{said}\n\nPresence is yes or no. {} Reply with yes or no at present, then {one} at top, bottom, \
748 left and right.",
749 self.edge_legend()?
750 )
751 }
752 Question::Boxes { .. } => {
753 let most = if self.spec.prompt == 1 { format!("{INSTANCES} at most, ") } else { String::new() };
754 format!(
755 "{said}\n\nEvery one of them, {most}in reading order (left edge, then top). Present k is yes \
756 when there are at least k. {} Reply with yes or no at each present, then its top, bottom, left \
757 and right.",
758 self.edge_legend()?
759 )
760 }
761 Question::Estimate { grid, unit, .. } => {
762 let unit = unit.as_deref().map(|u| format!(" {u}")).unwrap_or_default();
763 if self.uses_numbers(grid, 1) {
764 format!(
765 "{said}\n\nEstimate from {} to {}{unit}, in steps of {}. Reply with the number only.",
766 py_g(grid.low),
767 py_g(grid.high),
768 py_g(grid.step())
769 )
770 } else {
771 let codes: Vec<String> = if grid.size <= 10 {
772 (0..grid.size).map(|i| i.to_string()).collect()
773 } else {
774 let keys: Vec<String> = (0..grid.size).map(|i| i.to_string()).collect();
775 self.t.aliases(&keys, self.spec.codes)?.into_iter().map(|(c, _)| c).collect()
776 };
777 format!(
778 "{said}\n\nEstimate from {} to {}{unit} on {} equally spaced values, step {}: {} is {}, {} \
779 is {}. Reply with the code only.",
780 py_g(grid.low),
781 py_g(grid.high),
782 grid.size,
783 py_g(grid.step()),
784 codes[0],
785 py_g(grid.low),
786 codes[codes.len() - 1],
787 py_g(grid.high)
788 )
789 }
790 }
791 Question::Read { kind, unit, .. } => {
792 let unit = match (kind, unit) {
793 (ReadKind::Number, Some(u)) => format!(" (unit: {u})"),
794 _ => String::new(),
795 };
796 format!("{said}\n\nRead the {}{unit} from the state, or nothing if it is absent.", kind.name())
797 }
798 Question::Choice { criteria, multiple: true, .. } => {
799 let lines: Vec<String> = criteria
800 .iter()
801 .map(|(name, desc)| {
802 format!("{}: {}", serde_json::to_string(name).unwrap(), desc.as_deref().unwrap_or(name))
803 })
804 .collect();
805 format!("{said}\n\nOptions:\n{}\n\nReply with yes or no for each option.", lines.join("\n"))
806 }
807 Question::Choice { criteria, .. } => {
808 let labels: Vec<String> = criteria.iter().map(|(k, _)| k.clone()).collect();
809 let codes = self.t.aliases(&labels, self.spec.codes)?;
810 let lines: Vec<String> = codes
811 .iter()
812 .zip(criteria)
813 .map(|((code, _), (label, desc))| {
814 let text = desc.clone().unwrap_or_else(|| label.replace('_', " "));
815 format!("{code} {text}")
816 })
817 .collect();
818 format!("{said}\n\nOptions:\n{}\n\nReply with the option code only.", lines.join("\n"))
819 }
820 Question::Noul { yes_no, .. } => {
821 let extra = yes_no
822 .as_ref()
823 .map(|(y, n)| format!("\nYes: {y}\nNo: {n}"))
824 .unwrap_or_default();
825 format!("{said}{extra}\n\nReply with yes or no only.")
826 }
827 Question::Score { levels, .. } => {
828 let legend: Vec<String> = levels.iter().enumerate().map(|(i, l)| format!("{i} {l}")).collect();
829 format!(
830 "{said}\n\n{}\n\nReply with a single digit 0-{} only.",
831 legend.join("\n"),
832 levels.len() - 1
833 )
834 }
835 })
836 }
837
838 fn suffix_layout(&self, q: &Question) -> Result<(String, Vec<isize>), String> {
842 let body = self.question_block(q)?;
843 let names: Vec<String> = match q {
844 Question::Read { .. } => vec!["start".into(), "end".into()],
845 Question::Box { .. } => ["present"].iter().chain(EDGES.iter()).map(|s| s.to_string()).collect(),
846 Question::Boxes { max, .. } => {
847 let blocks = max.unwrap_or(INSTANCES);
848 (1..=blocks)
849 .flat_map(|k| ["present"].iter().chain(EDGES.iter()).map(move |n| format!("{n} {k}")))
850 .collect()
851 }
852 Question::Choice { criteria, multiple: true, .. } => {
853 criteria.iter().map(|(n, _)| serde_json::to_string(n).unwrap()).collect()
854 }
855 _ => {
856 return Ok((
857 format!("{}{IM_END}\n{IM_START}assistant\n{LEAD}", self.t.plain(&body)),
858 vec![0],
859 ));
860 }
861 };
862 let after = match q {
863 Question::Boxes { max, .. } if max.unwrap_or(INSTANCES) < INSTANCES || self.spec.prompt > 1 => "\n",
864 _ => "",
865 };
866 self.marked_suffix(&body, &names, after)
867 }
868
869 fn marked_suffix(&self, body: &str, names: &[String], after: &str) -> Result<(String, Vec<isize>), String> {
870 let mut text = format!("{}{IM_END}\n{IM_START}assistant\n{LEAD}", self.t.plain(body));
871 let mut ends = Vec::new();
872 for (i, name) in names.iter().enumerate() {
873 if i > 0 {
874 text.push('\n');
875 }
876 text.push_str(&self.t.plain(name));
877 text.push(':');
878 ends.push(text.chars().count());
879 }
880 if !after.is_empty() {
881 let ids = self.t.encode(&format!("{text}{after}"));
882 let last = self.t.tok.decode_token(*ids.last().unwrap());
883 if last.trim_end().ends_with(':') {
884 text.push_str(after);
885 }
886 }
887 let (ids, offsets) = self.t.encode_offsets(&text);
888 let chars: Vec<char> = text.chars().collect();
889 let mut slots = Vec::new();
890 for &end in &ends {
891 let matching: Vec<(usize, usize)> = offsets
892 .iter()
893 .enumerate()
894 .filter(|(_, se)| se.0 < end && end - 1 < se.1)
895 .map(|(i, se)| (i, se.1))
896 .collect();
897 if matching.len() != 1 {
898 return Err("a slot marker must have an exact colon token position".into());
899 }
900 let (tok_i, stop) = matching[0];
901 let tail: String = chars[end..stop.min(chars.len())].iter().collect();
902 if !tail.trim().is_empty() || !self.t.tok.decode_token(ids[tok_i]).trim_end().ends_with(':') {
903 return Err("a slot marker token must end with its colon".into());
904 }
905 slots.push(tok_i as isize - ids.len() as isize + 1);
906 }
907 Ok((text, slots))
908 }
909}
910
911fn state_block(state: &Value) -> String {
916 match state {
917 Value::String(s) => format!("{s}\n\n"),
918 other => format!("{}\n\n", serde_json::to_string_pretty(other).unwrap_or_default()),
919 }
920}
921
922fn string_paths(text: &str) -> Vec<(usize, usize, String)> {
925 let chars: Vec<char> = text.chars().collect();
926 let mut out = Vec::new();
927 fn skip(c: &[char], mut at: usize) -> usize {
928 while at < c.len() && c[at].is_whitespace() {
929 at += 1;
930 }
931 at
932 }
933 fn string_end(c: &[char], at: usize) -> Option<usize> {
934 let mut i = at + 1;
936 while i < c.len() {
937 match c[i] {
938 '\\' => i += 2,
939 '"' => return Some(i + 1),
940 _ => i += 1,
941 }
942 }
943 None
944 }
945 fn value(c: &[char], at: usize, path: &str, out: &mut Vec<(usize, usize, String)>) -> Option<usize> {
946 let at = skip(c, at);
947 match c.get(at)? {
948 '{' => {
949 let mut at = skip(c, at + 1);
950 while c.get(at)? != &'}' {
951 let end = string_end(c, at)?;
952 let key: String = serde_json::from_str(&c[at..end].iter().collect::<String>()).ok()?;
953 let colon = skip(c, end);
954 if c.get(colon)? != &':' {
955 return None;
956 }
957 let name = key.replace('~', "~0").replace('/', "~1");
958 at = skip(c, value(c, colon + 1, &format!("{path}/{name}"), out)?);
959 if c.get(at)? == &',' {
960 at = skip(c, at + 1);
961 }
962 }
963 Some(at + 1)
964 }
965 '[' => {
966 let mut at = skip(c, at + 1);
967 let mut index = 0;
968 while c.get(at)? != &']' {
969 at = skip(c, value(c, at, &format!("{path}/{index}"), out)?);
970 index += 1;
971 if c.get(at)? == &',' {
972 at = skip(c, at + 1);
973 }
974 }
975 Some(at + 1)
976 }
977 '"' => {
978 let end = string_end(c, at)?;
979 out.push((at + 1, end - 1, path.to_string()));
980 Some(end)
981 }
982 _ => {
983 let mut i = at;
984 while i < c.len() && !matches!(c[i], ',' | '}' | ']') && !c[i].is_whitespace() {
985 i += 1;
986 }
987 Some(i)
988 }
989 }
990 }
991 if value(&chars, 0, "", &mut out).is_none() {
992 out.clear();
993 }
994 out
995}
996
997struct StatePositions {
999 text: Vec<char>,
1000 offsets: Vec<Option<(usize, usize)>>,
1001 paths: Vec<(usize, usize, String)>,
1002}
1003
1004impl StatePositions {
1005 fn pointer_mask(&self) -> Vec<bool> {
1006 let valid: Vec<bool> = self.offsets.iter().enumerate().map(|(i, s)| i == 0 || s.is_some()).collect();
1007 let last = valid.iter().rposition(|&v| v).unwrap_or(0);
1008 valid[..=last].to_vec()
1009 }
1010
1011 fn read(&self, first: usize, last: usize) -> Result<Map<String, Value>, String> {
1012 let mut out = Map::new();
1013 if first == 0 && last == 0 {
1014 out.insert("text".into(), Value::Null);
1015 out.insert("span".into(), Value::Null);
1016 return Ok(out);
1017 }
1018 let (a, b) = match (self.offsets.get(first).copied().flatten(), self.offsets.get(last).copied().flatten()) {
1019 (Some(a), Some(b)) => (a, b),
1020 _ => return Err("pointer leaves the state text".into()),
1021 };
1022 let (mut s0, mut s1) = (a.0, b.1);
1023 while s0 < s1 && self.text[s0].is_whitespace() {
1024 s0 += 1;
1025 }
1026 while s1 > s0 && self.text[s1 - 1].is_whitespace() {
1027 s1 -= 1;
1028 }
1029 let held: Vec<&(usize, usize, String)> =
1030 self.paths.iter().filter(|(start, end, _)| *start < s1 && s0 < *end).collect();
1031 if held.len() == 1 {
1032 let (start, end, path) = held[0];
1033 let (s0, s1) = (s0.max(*start), s1.min(*end));
1034 let slice: String = self.text[s0..s1].iter().collect();
1035 let text = serde_json::from_str::<String>(&format!("\"{slice}\"")).unwrap_or(slice);
1036 out.insert("text".into(), json!(text));
1037 out.insert("span".into(), json!([s0, s1]));
1038 out.insert("path".into(), json!(path));
1039 return Ok(out);
1040 }
1041 out.insert("text".into(), json!(self.text[s0..s1].iter().collect::<String>()));
1042 out.insert("span".into(), json!([s0, s1]));
1043 Ok(out)
1044 }
1045}
1046
1047fn state_positions(
1050 t: &Tok,
1051 state: &Value,
1052 prefix: &str,
1053 full: &str,
1054 offsets: &[(usize, usize)],
1055) -> Result<StatePositions, String> {
1056 let full_c: Vec<char> = full.chars().collect();
1057 let prefix_len = prefix.chars().count();
1058 let (text, boundaries, start) = if state.is_null() {
1059 (String::new(), vec![0usize], prefix_len)
1060 } else {
1061 let body = state_block(state);
1062 let (escaped, boundaries) = t.plain_offsets(&body);
1063 let tail = format!("{escaped}\nQUESTION:\n");
1064 if !prefix.ends_with(&tail) {
1065 return Err("state rendering differs from the prompt prefix".into());
1066 }
1067 let start = prefix_len - tail.chars().count();
1068 let text: String = body.chars().take(body.chars().count() - 2).collect();
1069 (text, boundaries, start)
1070 };
1071 let text_len = text.chars().count();
1072 let text_c: Vec<char> = text.chars().collect();
1073 let stop = start + boundaries.partition_point(|&b| b <= text_len) - 1;
1074 let blank = |s: &[char]| s.iter().all(|c| c.is_whitespace());
1075 let mut out = Vec::with_capacity(offsets.len());
1076 for &(a, b) in offsets {
1077 let (lo, hi) = (a.max(start), b.min(stop));
1078 let mut span = None;
1079 if lo < hi && blank(&full_c[a..lo]) && blank(&full_c[hi..b.max(hi)]) {
1080 let (left, right) = (boundaries[lo - start], boundaries[hi - start]);
1081 if left < right && right <= text_len && !blank(&text_c[left..right]) {
1082 span = Some((left, right));
1083 }
1084 }
1085 out.push(span);
1086 }
1087 if out.first().is_none_or(|s| s.is_some()) {
1088 return Err("position 0 must be a template token for the nothing readout".into());
1089 }
1090 let paths = if !state.is_null() && !state.is_string() { string_paths(&text) } else { Vec::new() };
1091 Ok(StatePositions { text: text_c, offsets: out, paths })
1092}
1093
1094fn halton() -> Vec<[f64; 4]> {
1097 let radical = |mut i: usize, base: usize| {
1098 let (mut value, mut scale) = (0.0, 1.0);
1099 while i > 0 {
1100 let digit = i % base;
1101 i /= base;
1102 scale /= base as f64;
1103 value += digit as f64 * scale;
1104 }
1105 value
1106 };
1107 (1..=DRAWS).map(|i| [radical(i, 2), radical(i, 3), radical(i, 5), radical(i, 7)]).collect()
1108}
1109
1110fn grid_of(row: &[f64]) -> Grid {
1111 if row.len() == THOUSANDTHS.size { THOUSANDTHS } else { EDGE_CODES }
1112}
1113
1114fn ordered(a: f64, b: f64, grid: &Grid) -> (f64, f64) {
1115 if a < b {
1116 return (a, b);
1117 }
1118 let half = grid.step() / 2.0;
1119 let middle = (1.0 - half).min(half.max((a + b) / 2.0));
1120 (middle - half, middle + half)
1121}
1122
1123fn rectangle(e: [f64; 4], grid: &Grid) -> [f64; 4] {
1124 let (top, bottom) = ordered(e[0], e[1], grid);
1125 let (left, right) = ordered(e[2], e[3], grid);
1126 [top, bottom, left, right]
1127}
1128
1129fn iou(a: &[f64; 4], b: &[f64; 4]) -> f64 {
1130 let area = |x: &[f64; 4]| (x[1] - x[0]) * (x[3] - x[2]);
1131 let overlap = (a[1].min(b[1]) - a[0].max(b[0])).max(0.0) * (a[3].min(b[3]) - a[2].max(b[2])).max(0.0);
1132 overlap / (area(a) + area(b) - overlap)
1133}
1134
1135fn crossing(probs: &[f64], grid: &Grid) -> f64 {
1136 let total: f64 = probs.iter().sum();
1137 let mut mass = 0.0;
1138 for (value, p) in grid.values().iter().zip(probs) {
1139 let share = p / total;
1140 if share > 0.0 && mass + share >= 0.5 {
1141 return grid.high.min(grid.low.max(value - grid.step() / 2.0 + (0.5 - mass) / share * grid.step()));
1142 }
1143 mass += share;
1144 }
1145 grid.high
1146}
1147
1148fn medians(edges: &[Vec<f64>], interp: bool) -> [f64; 4] {
1149 let grid = grid_of(&edges[0]);
1150 let at = |row: &Vec<f64>| {
1151 if interp {
1152 crossing(row, &grid)
1153 } else {
1154 grid.read(row, None)["median"].as_f64().unwrap()
1155 }
1156 };
1157 rectangle([at(&edges[0]), at(&edges[1]), at(&edges[2]), at(&edges[3])], &grid)
1158}
1159
1160fn box_confidence(edges: &[Vec<f64>], b: &[f64; 4]) -> f64 {
1161 let grid = grid_of(&edges[0]);
1162 let values = grid.values();
1163 let cdfs: Vec<Vec<f64>> = edges
1164 .iter()
1165 .map(|row| {
1166 let total: f64 = row.iter().sum();
1167 let mut acc = 0.0;
1168 row.iter()
1169 .map(|p| {
1170 acc += p / total;
1171 acc
1172 })
1173 .collect()
1174 })
1175 .collect();
1176 let hits = halton()
1177 .iter()
1178 .filter(|point| {
1179 let mut e = [0f64; 4];
1180 for (k, (cdf, &u)) in cdfs.iter().zip(point.iter()).enumerate() {
1181 let i = cdf.partition_point(|&c| c < u).min(grid.size - 1);
1182 e[k] = values[i];
1183 }
1184 iou(&rectangle(e, &grid), b) >= 0.5
1185 })
1186 .count();
1187 hits as f64 / DRAWS as f64
1188}
1189
1190fn box_json(b: &[f64; 4]) -> Value {
1191 json!({"top": b[0], "bottom": b[1], "left": b[2], "right": b[3]})
1192}
1193
1194pub struct Request {
1200 pub state: Value,
1201 pub questions: Map<String, Value>,
1202 pub images: Vec<Value>,
1203 pub audios: Vec<Value>,
1204}
1205
1206pub struct D1 {
1208 spec: D1Spec,
1209}
1210
1211enum Probs {
1213 Codes(Vec<Vec<f64>>),
1214 Read(Map<String, Value>),
1215}
1216
1217fn softmax(scores: &[f64]) -> Vec<f64> {
1218 let m = scores.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1219 let exps: Vec<f64> = scores.iter().map(|s| (s - m).exp()).collect();
1220 let total: f64 = exps.iter().sum();
1221 exps.iter().map(|e| e / total).collect()
1222}
1223
1224fn log_softmax(x: &[f64]) -> Vec<f64> {
1225 let m = x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1226 if !m.is_finite() {
1227 return x.to_vec();
1228 }
1229 let lse = m + x.iter().map(|v| (v - m).exp()).sum::<f64>().ln();
1230 x.iter().map(|v| v - lse).collect()
1231}
1232
1233impl D1 {
1234 pub fn from_model(model: &CmfModel) -> Result<Self, String> {
1235 Ok(Self { spec: D1Spec::from_model(model)? })
1236 }
1237
1238 pub fn system_one(&self, pipeline: &Pipeline, req: &Request) -> Result<Value, String> {
1240 let p = Proto { t: Tok::new(&pipeline.tokenizer), spec: &self.spec };
1241 let mut questions = Vec::new();
1242 for (name, q) in &req.questions {
1243 let q = parse_question(q, self.spec.prompt).map_err(|e| format!("question {name:?}: {e}"))?;
1244 questions.push((name.clone(), q));
1245 }
1246 if questions.is_empty() {
1247 return Err("no questions".into());
1248 }
1249 if req.state.is_null() && req.images.is_empty() && req.audios.is_empty() {
1250 return Err("a request needs a state, media, or both".into());
1251 }
1252 let boxes = questions.iter().any(|(_, q)| matches!(q, Question::Box { .. } | Question::Boxes { .. }));
1253 if boxes && req.images.len() != 1 {
1254 return Err(format!(
1255 "a box or boxes question reads exactly one picture; this request has {}",
1256 req.images.len()
1257 ));
1258 }
1259 let mut markup = String::new();
1262 let mut media_rows: Vec<f32> = Vec::new();
1263 if !req.images.is_empty() {
1264 let model = pipeline.model.as_ref().ok_or("d1 media need a file-backed pipeline")?;
1265 let tower = crate::lfm3_vl::Lfm3VlTower::from_model(model, crate::pool::Pool::from_env())?;
1266 for src in &req.images {
1267 let frame = crate::media::decode_rgb(&crate::media::load_image_bytes(src)?)?;
1268 let frame = cap_pixels(&frame, self.spec.vision_max_pixels);
1269 let input = crate::lfm3_vl::prepare_image(&frame, &tower.cfg, None)?;
1270 media_rows.extend(tower.forward(&input)?);
1271 markup.push_str("<|vision_start|>");
1272 markup.push_str(&"<|image|>".repeat(input.tokens()));
1273 markup.push_str("<|vision_end|>");
1274 }
1275 }
1276 if !req.audios.is_empty() {
1279 let model = pipeline.model.as_ref().ok_or("d1 media need a file-backed pipeline")?;
1280 if !crate::lfm3_audio::has_audio(model) {
1281 return Err("this d1 file takes no audio (d1-Omni does)".into());
1282 }
1283 let enc = crate::lfm3_audio::Lfm3AudioEncoder::from_model(model, crate::pool::Pool::from_env())?;
1284 for src in &req.audios {
1285 let rows = enc.rows(&crate::egemma2_audio::load_audio_source(src)?)?;
1286 markup.push_str("<|vision_start|>");
1287 markup.push_str(&"<|audio|>".repeat(rows.len() / enc.text_hidden));
1288 markup.push_str("<|vision_end|>");
1289 media_rows.extend(rows);
1290 }
1291 }
1292 let bos = pipeline
1293 .tokenizer
1294 .bos_token_id
1295 .map(|id| pipeline.tokenizer.decode_for_protocol(&[id]))
1296 .unwrap_or_default();
1297 let header = if req.state.is_null() { "" } else { "QUESTION:\n" };
1298 let prefix = if req.state.is_null() {
1299 format!("{bos}{IM_START}user\n{markup}")
1300 } else {
1301 format!("{bos}{IM_START}user\n{markup}{}\n{header}", p.t.plain(&state_block(&req.state)))
1302 };
1303 let layouts: Vec<(String, Vec<isize>)> =
1304 questions.iter().map(|(_, q)| p.suffix_layout(q)).collect::<Result<_, _>>()?;
1305 let slot_kinds: Vec<Vec<Slot>> = questions.iter().map(|(_, q)| p.slots(q)).collect::<Result<_, _>>()?;
1306 let mut probs: Vec<Option<Probs>> = (0..questions.len()).map(|_| None).collect();
1307 let mut read_tokens = 0usize;
1308 let step = self.spec.set_size.max(1);
1309 for k0 in (0..questions.len()).step_by(step) {
1310 let k1 = (k0 + step).min(questions.len());
1311 let first_text = format!("{prefix}{}", layouts[k0].0);
1312 if std::env::var_os("CMF_D1_DEBUG").is_some() {
1313 eprintln!("d1: prompt {first_text:?}");
1314 }
1315 let (mut ids, offsets) = p.t.encode_offsets(&first_text);
1316 let state_len = ids.len() - p.t.encode(&layouts[k0].0).len();
1317 let mut group = vec![-1i32; state_len];
1318 group.extend(std::iter::repeat_n(1, ids.len() - state_len));
1319 let mut ends: Vec<Vec<usize>> =
1320 vec![layouts[k0].1.iter().map(|o| (ids.len() as isize - 1 + o) as usize).collect()];
1321 for (j, k) in (k0 + 1..k1).enumerate() {
1322 let own = p.t.encode(&format!("{header}{}", layouts[k].0));
1323 ids.extend(&own);
1324 group.extend(std::iter::repeat_n(j as i32 + 2, own.len()));
1325 ends.push(layouts[k].1.iter().map(|o| (ids.len() as isize - 1 + o) as usize).collect());
1326 }
1327 read_tokens += ids.len();
1328 let needs_positions = (k0..k1).any(|k| matches!(questions[k].1, Question::Read { .. }));
1329 let positions = if needs_positions {
1330 Some(state_positions(&p.t, &req.state, &prefix, &first_text, &offsets)?)
1331 } else {
1332 None
1333 };
1334 let h = pipeline.hidden_size;
1336 let image_id = p.t.single("<|image|>").ok_or("tokenizer lacks <|image|>")?;
1337 let audio_id = p.t.single("<|audio|>");
1338 let mut rows = Vec::with_capacity(ids.len() * h);
1339 let mut at = 0usize;
1340 for &id in &ids {
1341 if (id == image_id || Some(id) == audio_id) && !media_rows.is_empty() {
1342 rows.extend_from_slice(media_rows.get(at..at + h).ok_or("picture rows run short")?);
1343 at += h;
1344 } else {
1345 rows.extend(pipeline.embed_id(id));
1346 }
1347 }
1348 if at != media_rows.len() {
1349 return Err("picture placeholders and rows differ".into());
1350 }
1351 let mut code_at: Vec<usize> = Vec::new();
1354 for (j, k) in (k0..k1).enumerate() {
1355 if !matches!(questions[k].1, Question::Read { .. }) {
1356 code_at.extend(&ends[j]);
1357 }
1358 }
1359 code_at.sort_unstable();
1360 code_at.dedup();
1361 let t_fwd = std::time::Instant::now();
1362 let (hidden, logz_all) =
1363 pipeline.forward_masked_head(&rows, &SetMask { state: state_len, group: &group }, &code_at)?;
1364 if std::env::var_os("CMF_D1_DEBUG").is_some() {
1365 eprintln!(
1366 "d1: forward {} tokens + head over {} slots in {:.0} ms",
1367 ids.len(),
1368 code_at.len(),
1369 t_fwd.elapsed().as_secs_f64() * 1e3
1370 );
1371 }
1372 let vocab = pipeline.vocab_size;
1373 let logz_of = |pos: usize| {
1374 let r = code_at.binary_search(&pos).expect("code slot");
1375 &logz_all[r * vocab..(r + 1) * vocab]
1376 };
1377 for (j, k) in (k0..k1).enumerate() {
1378 let q = &questions[k].1;
1379 let slots = &slot_kinds[k];
1380 if slots.len() != ends[j].len() {
1381 return Err("slot layout and readouts disagree".into());
1382 }
1383 if let Question::Read { kind, .. } = q {
1384 let pos = positions.as_ref().unwrap();
1385 let mask = pos.pointer_mask();
1386 let width = mask.len();
1387 let logits: Vec<Vec<f64>> = ends[j]
1388 .iter()
1389 .map(|&slot| {
1390 let qv = &hidden[slot * h..(slot + 1) * h];
1391 (0..width)
1392 .map(|i| {
1393 if !mask[i] {
1394 return f64::NEG_INFINITY;
1395 }
1396 let kv = &hidden[i * h..(i + 1) * h];
1397 let dot: f32 = qv.iter().zip(kv).map(|(a, b)| a * b).sum();
1398 (dot / (h as f32).sqrt()) as f64
1399 })
1400 .collect()
1401 })
1402 .collect();
1403 let (first, last, found) = span(&logits[0], &logits[1], kind.cap());
1404 let mut read = pos.read(first, last)?;
1405 read.insert("found".into(), json!(found));
1406 probs[k] = Some(Probs::Read(read));
1407 continue;
1408 }
1409 let mut rows_p = Vec::new();
1410 for (slot, &end) in slots.iter().zip(&ends[j]) {
1411 let Slot::Codes { groups, bucket } = slot else {
1412 return Err("a pointer slot outside a read".into());
1413 };
1414 let logz = logz_of(end);
1415 let mut scores: Vec<f64> = groups
1416 .iter()
1417 .map(|g| g.iter().map(|&i| logz[i as usize] as f64).fold(f64::NEG_INFINITY, f64::max))
1418 .collect();
1419 if let (Some(b), false) = (bucket, q.typed()) {
1420 let temp = self.spec.calibration.temperature(b);
1421 for s in &mut scores {
1422 *s /= temp;
1423 }
1424 }
1425 rows_p.push(softmax(&scores));
1426 }
1427 probs[k] = Some(Probs::Codes(rows_p));
1428 }
1429 }
1430 let context = |q: &Question| match q {
1431 Question::Read { .. } if !req.state.is_null() => {
1432 let b = state_block(&req.state);
1433 b[..b.len() - 2].to_string()
1434 }
1435 _ => String::new(),
1436 };
1437 let debug = std::env::var_os("CMF_D1_DEBUG").is_some();
1438 let mut answers = Map::new();
1439 for ((name, q), pr) in questions.iter().zip(probs) {
1440 let pr = pr.ok_or("unanswered question")?;
1441 if debug {
1442 match &pr {
1443 Probs::Codes(rows) => eprintln!("d1: {name}: {rows:?}"),
1444 Probs::Read(r) => eprintln!("d1: {name}: {r:?}"),
1445 }
1446 }
1447 answers.insert(name.clone(), self.answer(q, pr, &context(q))?);
1448 }
1449 Ok(json!({"answers": answers, "usage": {"input_tokens": read_tokens, "output_tokens": 0}}))
1450 }
1451
1452 fn answer(&self, q: &Question, probs: Probs, context: &str) -> Result<Value, String> {
1454 let cal = &self.spec.calibration;
1455 Ok(match (q, probs) {
1456 (Question::Read { kind, unit, .. }, Probs::Read(r)) => {
1457 let raw = r["found"].as_f64().unwrap_or(0.0);
1458 let found = cal.through(&format!("found:{}", kind.name()), raw);
1459 let present = found >= 0.5;
1460 let text = if present { r.get("text").cloned().unwrap_or(Value::Null) } else { Value::Null };
1461 let span = if present { r.get("span").cloned().unwrap_or(Value::Null) } else { Value::Null };
1462 let mut out = Map::new();
1463 out.insert("type".into(), json!(kind.name()));
1464 let (mut value, mut unit_out) = (text.clone(), unit.clone().map(Value::String).unwrap_or(Value::Null));
1465 if let Some(s) = text.as_str() {
1466 match kind {
1467 ReadKind::Number => {
1468 if unit.is_none() {
1469 unit_out = crate::d1_parse::written_unit(s).map(Value::String).unwrap_or(Value::Null);
1470 }
1471 value = crate::d1_parse::parse_number(s, context, unit.as_deref());
1472 }
1473 ReadKind::Date => value = crate::d1_parse::parse_date(s, context).0.map(Value::String).unwrap_or(Value::Null),
1474 ReadKind::Text => {}
1475 }
1476 }
1477 out.insert(kind.name().into(), value);
1478 out.insert("span".into(), span);
1479 out.insert("found".into(), json!(found));
1480 if *kind != ReadKind::Text {
1481 out.insert("text".into(), text);
1482 }
1483 if present {
1484 if let Some(path) = r.get("path") {
1485 out.insert("path".into(), path.clone());
1486 }
1487 }
1488 if *kind == ReadKind::Number {
1489 out.insert("unit".into(), unit_out);
1490 }
1491 Value::Object(out)
1492 }
1493 (Question::Box { .. }, Probs::Codes(rows)) => {
1494 let found = cal.through("found:box", rows[0][0]);
1495 if found < 0.5 {
1496 json!({"type": "box", "box": null, "found": found, "confidence": 0.0})
1497 } else {
1498 let b = medians(&rows[1..5], self.spec.edges_interp);
1499 let conf = cal.through("box", box_confidence(&rows[1..5], &b));
1500 json!({"type": "box", "box": box_json(&b), "found": found, "confidence": conf})
1501 }
1502 }
1503 (Question::Boxes { .. }, Probs::Codes(rows)) => {
1504 let mut presence = Vec::new();
1505 let mut below = 1.0f64;
1506 for k in (0..rows.len()).step_by(5) {
1507 below = below.min(cal.through("found:boxes", rows[k][0]));
1508 presence.push(below);
1509 }
1510 let mut out: Vec<([f64; 4], f64, f64)> = Vec::new();
1511 for (k, &found) in presence.iter().enumerate() {
1512 if found < 0.5 {
1513 break;
1514 }
1515 let block = &rows[5 * k + 1..5 * k + 5];
1516 let b = medians(block, self.spec.edges_interp);
1517 out.push((b, found, cal.through("boxes", box_confidence(block, &b))));
1518 }
1519 out.sort_by(|a, b| (a.0[2], a.0[0]).partial_cmp(&(b.0[2], b.0[0])).unwrap());
1520 let count = out.len();
1521 json!({
1522 "type": "boxes",
1523 "boxes": out.iter().map(|(b, f, c)| json!({"box": box_json(b), "found": f, "confidence": c})).collect::<Vec<_>>(),
1524 "count": count,
1525 "at_least": count > 0 && count == presence.len(),
1526 "presence": presence,
1527 })
1528 }
1529 (Question::Estimate { grid, unit, .. }, Probs::Codes(rows)) => {
1530 let level = |share: f64| cal.level("estimate", share);
1531 let has = !cal.knots("estimate").is_empty();
1532 let v = grid.read(&rows[0], if has { Some(&level) } else { None });
1533 json!({"type": "number", "number": v["median"], "interval": [v["p10"], v["p90"]], "unit": unit})
1534 }
1535 (Question::Noul { .. }, Probs::Codes(rows)) => json!({"type": "noul", "noul": rows[0][0]}),
1536 (Question::Choice { criteria, multiple: true, .. }, Probs::Codes(rows)) => {
1537 let ps: Vec<f64> = rows.iter().map(|r| cal.through("multiple", r[0])).collect();
1538 let names: Vec<&String> = criteria.iter().map(|(n, _)| n).collect();
1539 let chosen: Vec<&String> = names.iter().zip(&ps).filter(|(_, p)| **p >= 0.5).map(|(n, _)| *n).collect();
1540 let probs: Map<String, Value> = names.iter().zip(&ps).map(|(n, p)| ((*n).clone(), json!(p))).collect();
1541 json!({"type": "choice", "choices": chosen, "probabilities": probs})
1542 }
1543 (Question::Choice { criteria, .. }, Probs::Codes(rows)) => {
1544 let ps = &rows[0];
1545 let best = argmax(ps);
1546 let probs: Map<String, Value> = criteria.iter().zip(ps).map(|((n, _), p)| (n.clone(), json!(p))).collect();
1547 json!({"type": "choice", "choice": criteria[best].0, "confidence": ps[best], "probabilities": probs})
1548 }
1549 (Question::Score { levels, .. }, Probs::Codes(rows)) => {
1550 let ps = &rows[0];
1551 let best = argmax(ps);
1552 let total: f64 = ps.iter().sum();
1553 let mut mass = 0.0;
1554 let mut level = levels.len() - 1;
1555 for (i, p) in ps.iter().enumerate() {
1556 mass += p / total;
1557 if mass >= 0.5 {
1558 level = i;
1559 break;
1560 }
1561 }
1562 let probs: Map<String, Value> = ps.iter().enumerate().map(|(i, p)| (i.to_string(), json!(p))).collect();
1563 let legend: Map<String, Value> =
1564 levels.iter().enumerate().map(|(i, l)| (i.to_string(), json!(l))).collect();
1565 json!({"type": "score", "score": level as f64, "confidence": ps[best], "probabilities": probs, "legend": legend})
1566 }
1567 _ => return Err("readout and question disagree".into()),
1568 })
1569 }
1570}
1571
1572fn argmax(v: &[f64]) -> usize {
1573 let mut best = 0;
1574 for (i, &x) in v.iter().enumerate() {
1575 if x > v[best] {
1576 best = i;
1577 }
1578 }
1579 best
1580}
1581
1582fn span(start_logits: &[f64], end_logits: &[f64], cap: usize) -> (usize, usize, f64) {
1585 let start = log_softmax(start_logits);
1586 let end = log_softmax(end_logits);
1587 let found = -start[0].exp_m1();
1588 let t = start.len();
1589 if t == 1 {
1590 return (0, 0, found);
1591 }
1592 let cap = cap.min(t - 1);
1593 let mut best = (f64::NEG_INFINITY, 1usize, 1usize);
1594 for i in 1..t {
1595 let mut mj = i;
1597 for j in i..(i + cap).min(t) {
1598 if end[j] > end[mj] {
1599 mj = j;
1600 }
1601 }
1602 let score = start[i] + end[mj];
1603 if score > best.0 {
1604 best = (score, i, mj);
1605 }
1606 }
1607 if found < 0.5 {
1608 return (0, 0, found);
1609 }
1610 (best.1, best.2, found)
1611}
1612
1613fn cap_pixels(frame: &crate::media::RgbFrame, max_pixels: usize) -> crate::media::RgbFrame {
1616 let (w, h) = (frame.width, frame.height);
1617 if w * h <= max_pixels {
1618 return frame.clone();
1619 }
1620 let scale = (max_pixels as f64 / (w * h) as f64).sqrt();
1621 let (nw, nh) = (((w as f64 * scale) as usize).max(1), ((h as f64 * scale) as usize).max(1));
1622 crate::egemma2_vision::resize_bicubic_aa(frame, nh, nw)
1623}
1624
1625#[cfg(test)]
1626mod tests {
1627 use super::*;
1628
1629 #[test]
1630 fn py_g_matches_python() {
1631 for (x, want) in [
1632 (0.0, "0"),
1633 (10.0, "10"),
1634 (0.5, "0.5"),
1635 (0.999, "0.999"),
1636 (1234567.0, "1.23457e+06"),
1637 (0.00001, "1e-05"),
1638 (0.1111111, "0.111111"),
1639 (100000.0, "100000"),
1640 (-2.5, "-2.5"),
1641 ] {
1642 assert_eq!(py_g(x), want, "{x}");
1643 }
1644 }
1645
1646 #[test]
1647 fn option_codes_follow_the_schemes() {
1648 let labels = |n: usize| (0..n).map(|i| format!("o{i}")).collect::<Vec<_>>();
1649 assert_eq!(option_codes(&labels(3), 2), vec!["A", "B", "C"]);
1650 assert_eq!(option_codes(&labels(28), 2)[26..], ["AA".to_string(), "AB".to_string()]);
1651 assert_eq!(option_codes(&labels(28), 1)[27], "27");
1652 assert_eq!(option_codes(&["x".into(), "y".into()], 2), vec!["x", "y"]);
1653 }
1654
1655 #[test]
1656 fn grid_reads_quantiles() {
1657 let g = Grid { low: 0.0, high: 10.0, size: 11 };
1658 let mut p = vec![0.0; 11];
1659 p[2] = 0.6;
1660 p[3] = 0.4;
1661 let r = g.read(&p, None);
1662 assert_eq!(r["median"], json!(2.0));
1663 assert_eq!(r["p90"], json!(3.0));
1664 assert_eq!(r["p10"], json!(2.0));
1665 }
1666
1667 #[test]
1668 fn string_paths_of_rendered_json() {
1669 let text = "{\n \"customer\": {\n \"name\": \"Maria\"\n },\n \"m\": [\n \"a\",\n 1\n ]\n}";
1670 let p = string_paths(text);
1671 assert_eq!(p.len(), 2);
1672 assert_eq!(p[0].2, "/customer/name");
1673 assert_eq!(p[1].2, "/m/0");
1674 }
1675
1676 #[test]
1677 fn span_picks_the_best_pair_or_nothing() {
1678 let ninf = f64::NEG_INFINITY;
1679 let (a, b, f) = span(&[-5.0, 3.0, 0.0, ninf], &[-5.0, 0.0, 3.0, ninf], 32);
1680 assert_eq!((a, b), (1, 2));
1681 assert!(f > 0.5);
1682 let (a, b, _) = span(&[5.0, -3.0, -3.0], &[5.0, -3.0, -3.0], 32);
1683 assert_eq!((a, b), (0, 0));
1684 }
1685}