pub mod batch_scheduler;
pub mod calib;
pub mod checkpoint;
pub mod connector;
pub mod decoder;
pub mod decoder_qwen2;
pub mod got;
pub(crate) mod lever_policy;
pub mod memory;
pub mod model_arch;
pub mod moe;
pub mod nn;
pub mod onechart;
pub mod postprocess;
pub mod progress;
pub mod rswa;
pub mod sampler;
pub mod shards;
pub mod smolvlm2;
pub(crate) mod spec;
pub mod srswa;
pub mod tensor;
pub mod token_compress;
pub mod tromr;
pub(crate) mod unlimited_ocr_census;
pub mod vision_bridge;
pub mod vision_clip;
pub mod vision_sam;
pub mod vision_siglip;
pub mod weights;
pub mod window;
use std::ffi::OsStr;
use std::io::Read;
use std::path::{Component, Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, Weak};
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
#[cfg(target_arch = "wasm32")]
use web_time::Instant;
use crate::error::{FocrError, FocrResult};
use crate::preprocess::{self, Preprocessed};
use crate::quant::recipe::{Recipe, is_truthy};
use sampler::{DecodeOutput, DecodeParams};
use tensor::Mat;
use weights::{DType, Weights};
struct ExclusiveGate {
occupied: std::sync::atomic::AtomicBool,
waiters: Mutex<()>,
wake: Condvar,
}
impl ExclusiveGate {
const fn new() -> Self {
Self {
occupied: std::sync::atomic::AtomicBool::new(false),
waiters: Mutex::new(()),
wake: Condvar::new(),
}
}
fn acquire(&self) -> ExclusivePermit<'_> {
use std::sync::atomic::Ordering;
if self
.occupied
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
return ExclusivePermit { gate: self };
}
let mut waiter = self
.waiters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
loop {
if self
.occupied
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
drop(waiter);
return ExclusivePermit { gate: self };
}
waiter = self
.wake
.wait(waiter)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
}
fn release(&self) {
use std::sync::atomic::Ordering;
let _waiter = self
.waiters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
self.occupied.store(false, Ordering::Release);
self.wake.notify_one();
}
}
struct ExclusivePermit<'a> {
gate: &'a ExclusiveGate,
}
impl Drop for ExclusivePermit<'_> {
fn drop(&mut self) {
self.gate.release();
}
}
struct FallibleOnce<T> {
value: OnceLock<T>,
init: ExclusiveGate,
}
impl<T> FallibleOnce<T> {
const fn new() -> Self {
Self {
value: OnceLock::new(),
init: ExclusiveGate::new(),
}
}
fn prefilled(value: Option<T>) -> Self {
let cell = Self::new();
if let Some(value) = value {
let _ = cell.value.set(value);
}
cell
}
fn get_or_try_init<E, F>(&self, initialize: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if let Some(value) = self.value.get() {
return Ok(value);
}
let _permit = self.init.acquire();
if let Some(value) = self.value.get() {
return Ok(value);
}
let value = initialize()?;
let set = self.value.set(value);
debug_assert!(set.is_ok(), "single initializer set raced unexpectedly");
Ok(self.value.get().expect("single initializer just set"))
}
}
enum RetainedOrOwned<'a, T> {
Retained(&'a T),
Owned(T),
}
impl<T> AsRef<T> for RetainedOrOwned<'_, T> {
fn as_ref(&self) -> &T {
match self {
Self::Retained(value) => value,
Self::Owned(value) => value,
}
}
}
fn retained_or_owned<'a, T, E, F>(
retain: bool,
cache: &'a FallibleOnce<T>,
initialize: F,
) -> Result<RetainedOrOwned<'a, T>, E>
where
F: FnOnce() -> Result<T, E>,
{
if retain {
cache
.get_or_try_init(initialize)
.map(RetainedOrOwned::Retained)
} else {
initialize().map(RetainedOrOwned::Owned)
}
}
#[derive(Default)]
pub struct SidecarBundle {
pub tokenizer_json: Option<Vec<u8>>,
pub qwen_tiktoken: Option<Vec<u8>>,
pub music_tables: Option<[String; 4]>,
pub opt_triple: Option<[Vec<u8>; 3]>,
}
pub struct OcrModel {
path: PathBuf,
sidecars: SidecarBundle,
arch: &'static dyn model_arch::ModelArch,
weights: Weights,
decode_params: DecodeParams,
decoder_cache_i8: FallibleOnce<decoder::DecoderWeightCacheI8>,
decoder_cache: FallibleOnce<decoder::DecoderWeightCache>,
decoder_cache_f32: FallibleOnce<decoder::DecoderWeightCache>,
clip_cache: FallibleOnce<vision_clip::ClipWeights>,
unlimited_vision: FallibleOnce<UnlimitedVisionStatics>,
got_statics: FallibleOnce<got::GotStatics>,
onechart_statics: FallibleOnce<onechart::OnechartStatics>,
smol_statics: FallibleOnce<smolvlm2::SmolStatics>,
tokenizer: FallibleOnce<crate::tokenizer::Tokenizer>,
got_tokenizer: FallibleOnce<crate::tokenizer::tiktoken::Tiktoken>,
tromr_tokenizer: FallibleOnce<crate::tokenizer::music::MusicTokenizer>,
music_meta: std::sync::Mutex<Option<MusicPageMeta>>,
chart_meta: std::sync::Mutex<Option<ChartPageMeta>>,
last_decode: std::sync::Mutex<Option<DecodeReport>>,
batch_decodes: std::sync::Mutex<Vec<Option<DecodeReport>>>,
batch_charts: std::sync::Mutex<Vec<Option<ChartPageMeta>>>,
}
struct UnlimitedVisionStatics {
sam: vision_sam::SamWeights,
projector: vision_bridge::ProjectorWeights,
}
type ModelCacheEntry = Option<(PathBuf, Weak<OcrModel>)>;
type ModelCache = Mutex<ModelCacheEntry>;
const MODEL_HEADER_VALIDATION_MAX_BYTES: usize = 8 * 1024 * 1024;
const MODEL_DIR_ENV: &str = "FOCR_MODEL_DIR";
const MODEL_QUANT_ENV: &str = "FOCR_QUANT";
const BASE_PROMPT_TEXT: &str = "document parsing.";
const MULTI_PAGE_PROMPT_TEXT: &str = "Multi page parsing.";
pub const UOCR2_UNWINDOWED_PAGE_LIMIT: usize = 102;
fn paged_prompt(
bos: u32,
marker: u32,
image: u32,
image_tokens: &[usize],
text: &[u32],
) -> (Vec<u32>, Vec<bool>) {
let total = 1 + image_tokens.iter().map(|n| n + 1).sum::<usize>() + text.len();
let mut ids = Vec::with_capacity(total);
let mut mask = Vec::with_capacity(total);
ids.push(bos);
mask.push(false);
for &n in image_tokens {
ids.push(marker);
mask.push(false);
ids.extend(std::iter::repeat_n(image, n));
mask.extend(std::iter::repeat_n(true, n));
}
ids.extend_from_slice(text);
mask.extend(std::iter::repeat_n(false, text.len()));
(ids, mask)
}
const MULTI_PAGE_BASE_SIZE: usize = 640;
const MAX_POSITION_EMBEDDINGS: usize = sampler::DEFAULT_MAX_POSITIONS;
const TEST_MAX_POSITIONS_ENV: &str = "FOCR_TEST_MAX_POSITIONS";
fn test_max_positions() -> &'static Result<Option<usize>, String> {
static VALUE: OnceLock<Result<Option<usize>, String>> = OnceLock::new();
VALUE.get_or_init(|| {
let Some(raw) = std::env::var_os(TEST_MAX_POSITIONS_ENV) else {
return Ok(None);
};
let raw = raw.to_string_lossy();
match raw.trim().parse::<usize>() {
Ok(n) if (1..=MAX_POSITION_EMBEDDINGS).contains(&n) => Ok(Some(n)),
_ => Err(format!(
"{TEST_MAX_POSITIONS_ENV}={raw:?} must be an integer in 1..={MAX_POSITION_EMBEDDINGS} \
(the architecture's context); it is a test-only override"
)),
}
})
}
pub fn validate_test_max_positions() -> FocrResult<()> {
test_max_positions()
.as_ref()
.map(|_| ())
.map_err(|why| FocrError::Usage(why.clone()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Stop,
Length,
Budget,
Cancelled,
Runaway,
}
impl FinishReason {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Stop => "stop",
Self::Length => "length",
Self::Budget => "budget",
Self::Cancelled => "cancelled",
Self::Runaway => "runaway",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LengthCap {
MaxLength,
Positions,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DecodeReport {
#[serde(skip)]
pub ids: Vec<u32>,
pub finish_reason: FinishReason,
pub cap: Option<LengthCap>,
pub pages: Option<PageBodies>,
pub max_length: usize,
pub max_positions: usize,
pub decodes: usize,
pub length_stops: usize,
#[serde(default)]
pub runaway_stops: usize,
pub document: Option<DocumentStats>,
#[serde(skip)]
pub admission_warnings: Vec<RunWarning>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PageFinish {
Marker,
Eos,
PageCap,
Runaway,
Length,
Budget,
Cancelled,
}
impl PageFinish {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Marker => "marker",
Self::Eos => "eos",
Self::PageCap => "page_cap",
Self::Runaway => "runaway",
Self::Length => "length",
Self::Budget => "budget",
Self::Cancelled => "cancelled",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DocumentStats {
pub page_finish: Vec<PageFinish>,
pub page_tokens: Vec<usize>,
pub leading_text: bool,
pub extra_markers: usize,
pub forced_steps: usize,
pub hf_all_neg_inf_steps: usize,
pub retired_blocks: usize,
pub peak_kv_bytes: usize,
pub retire_fallbacks: usize,
pub gundam_view: bool,
pub windows: usize,
pub vision_encodes: usize,
}
fn decode_with_retire_fallback(
retire: bool,
mut observer: Option<&mut dyn FnMut(u32)>,
mut pass: impl FnMut(
bool,
usize,
&mut Vec<u32>,
Option<&mut dyn FnMut(u32)>,
) -> FocrResult<DocumentDecode>,
) -> FocrResult<DocumentDecode> {
let mut first = Vec::new();
let mut observed = 0usize;
let outcome = {
let mut counting = |id: u32| {
observed += 1;
if let Some(f) = observer.as_deref_mut() {
f(id);
}
};
pass(retire, 0, &mut first, Some(&mut counting))
};
let error = match outcome {
Err(error) if srswa::retire_fallback(&error).is_some() => error,
done => return done,
};
timing_log(&format!(
"retire_fallback at step {}: {error:#}; decoding again without retirement",
first.len()
));
let mut seen = 0usize;
let mut replay = |id: u32| {
seen += 1;
if seen > observed
&& let Some(f) = observer.as_deref_mut()
{
f(id);
}
};
let mut ids = Vec::new();
let mut decode = pass(false, first.len(), &mut ids, Some(&mut replay))?;
if !decode.ids.starts_with(&first) {
return Err(FocrError::Other(anyhow::anyhow!(
"the unretired rerun departs from the retired run before its detector fired \
(step {}): block retirement changed the output",
first.len()
)));
}
decode.stats.retire_fallbacks = 1;
Ok(decode)
}
#[derive(Debug, Clone, Copy)]
struct DocumentPass {
precision: DocumentPrecision,
retire: bool,
deadline: Option<Instant>,
budget_floor: usize,
}
impl DocumentPass {
fn budget_spent(&self, generated: usize) -> bool {
generated >= self.budget_floor && self.deadline.is_some_and(|d| Instant::now() >= d)
}
}
#[derive(Debug, Clone, Copy)]
struct DocumentLoop<'p> {
params: &'p DecodeParams,
pass: DocumentPass,
marker: u32,
prompt_len: usize,
pages: usize,
forced_prefix: usize,
}
fn document_guard(pages: usize, marker: u32, params: &DecodeParams) -> sampler::PageGuard {
let page_cap = if params.max_page_tokens > 0 {
params.max_page_tokens.min(params.max_length)
} else {
params.max_length
};
sampler::PageGuard::new(pages, marker, params.eos_token_id, page_cap)
}
fn forced_finish(guard: &sampler::PageGuard) -> Option<PageFinish> {
if guard.at_cap() {
Some(PageFinish::PageCap)
} else if guard.ran_away() {
Some(PageFinish::Runaway)
} else {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DocumentPrecision {
Mixed,
CachedF32,
Stateless,
}
impl DocumentPrecision {
fn from_env() -> FocrResult<Self> {
Self::from_flags(
std::env::var_os(DECODE_STATELESS_ENV).is_some(),
std::env::var_os(DECODE_F32_ENV).is_some(),
int8_decode_requested(),
)
}
fn from_flags(stateless: bool, f32: bool, full_int8: bool) -> FocrResult<Self> {
match (stateless, f32, full_int8) {
(true, true, _) => Err(FocrError::Usage(format!(
"{DECODE_STATELESS_ENV} and {DECODE_F32_ENV} each pick the decoder; set one"
))),
(false, true, true) => Err(FocrError::Usage(format!(
"the full-int8 decoder ({DECODE_INT8_ENV} or the library override) and \
{DECODE_F32_ENV} each pick the decoder; set one"
))),
(true, false, _) => Ok(Self::Stateless),
(false, true, false) => Ok(Self::CachedF32),
(false, false, _) => Ok(Self::Mixed),
}
}
fn name(self) -> &'static str {
match self {
Self::Mixed => "focr-mixed-ffn-int8",
Self::CachedF32 => "f32-cached",
Self::Stateless => "f32-stateless",
}
}
}
#[cfg(feature = "native")]
pub(crate) fn document_budget_grace(budget: std::time::Duration) -> std::time::Duration {
(budget / 4).max(std::time::Duration::from_secs(30))
}
enum DocumentDecoder<'w> {
Cached {
wc: &'w decoder::DecoderWeightCache,
state: srswa::SrSwaState,
hidden: Mat,
},
Stateless {
embeds: Mat,
ids: Vec<u32>,
domain: srswa::EntmaxDomain,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct DocumentDecode {
ids: Vec<u32>,
finish_reason: FinishReason,
stats: DocumentStats,
}
impl DecodeReport {
#[must_use]
pub fn run_finish_reason(&self) -> FinishReason {
match (self.length_stops, self.finish_reason) {
(0, _) => FinishReason::Stop,
(_, stop @ (FinishReason::Budget | FinishReason::Cancelled)) => stop,
_ if self.runaway_stops > 0 => FinishReason::Runaway,
_ => FinishReason::Length,
}
}
fn v1_decode(ids: Vec<u32>, params: &sampler::DecodeParams, ran_away: bool) -> Self {
let stopped = ids.last() == Some(¶ms.eos_token_id);
let runaway = !stopped && ran_away;
Self {
finish_reason: match (stopped, runaway) {
(true, _) => FinishReason::Stop,
(false, true) => FinishReason::Runaway,
(false, false) => FinishReason::Length,
},
cap: (!stopped && !runaway).then_some(LengthCap::MaxLength),
ids,
pages: None,
max_length: params.max_length,
max_positions: params.max_positions,
decodes: 1,
length_stops: usize::from(!stopped),
runaway_stops: usize::from(runaway),
document: None,
admission_warnings: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PageBodies {
pub expected: usize,
pub bodies: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarningKind {
V1TruncatedPositions,
V1TruncatedUserCap,
V1PageMerge,
V1ExtraPages,
PromptTooLong,
V2PromptTooLong,
IgnoredLever,
V2Length,
V2Budget,
V2PageCap,
V2PageReread,
LeadingText,
IgnoredFiles,
UncertifiedCropMode,
PullHint,
ResumeRecompute,
CheckpointFailed,
ChartUnverified,
}
pub const RUN_WARNING_KINDS: &[&str] = &[
"v1_truncated",
"v1_page_merge",
"v1_extra_pages",
"ignored_lever",
"v2_length",
"v2_budget",
"v2_page_cap",
"v2_page_reread",
"leading_text",
"ignored_files",
"uncertified_crop_mode",
"pull_hint",
"resume_recompute",
"checkpoint_failed",
"chart_unverified",
];
#[must_use]
pub fn warning_recovery(kind: WarningKind) -> &'static str {
match kind {
WarningKind::V1TruncatedPositions | WarningKind::PromptTooLong => {
"split the document with `--pages`, parse pages independently (omit `--multi-page`), \
or read it with Unlimited-OCR-2, which reads a document of any length in windows: \
`focr pull unlimited-ocr-2`, then `--model unlimited-ocr-2`"
}
WarningKind::V1TruncatedUserCap => "raise `--max-length`",
WarningKind::V1PageMerge => {
"parse pages independently (omit `--multi-page`) when page boundaries matter, or read \
it with Unlimited-OCR-2, which writes exactly one page marker per page: \
`focr pull unlimited-ocr-2`, then `--model unlimited-ocr-2`"
}
WarningKind::V1ExtraPages => "parse pages independently (omit `--multi-page`)",
WarningKind::V2PromptTooLong => {
"read it in windows (`--page-window 10`, the default, or `auto`), or split the \
document with `--pages`"
}
WarningKind::IgnoredLever => "unset it for this model's runs",
WarningKind::V2Length => {
"the completed pages were written; split the document with `--pages`, or parse \
pages independently (omit `--multi-page`)"
}
WarningKind::V2Budget => {
"the completed pages were written; re-run the same command to continue from the \
last completed window, raise FOCR_STAGE_BUDGET_FORWARD_MS (0 = unlimited), or \
select fewer pages with `--pages`"
}
WarningKind::V2PageCap => {
"the other pages were written; check the listed pages, whose text probably \
repeats, or lower `--max-page-tokens` so a runaway page closes sooner"
}
WarningKind::V2PageReread => {
"a replaced page needs nothing; check a kept page, whose capped text probably \
repeats"
}
WarningKind::LeadingText => {
"check the start of page 1: the model wrote text before its first page marker"
}
WarningKind::IgnoredFiles => {
"only page images are read (PNG, JPEG, TIFF, WebP, BMP, GIF, PNM); convert any of \
these files that belong to the document"
}
WarningKind::UncertifiedCropMode => {
"use `--crop-mode base` (the default) for certified output; check gundam output \
against the page"
}
WarningKind::PullHint => {
"install the default with `focr pull <model_id>` (this warning's `model_id`), or \
choose a model with `--model`"
}
WarningKind::ResumeRecompute => {
"for output identical to an uninterrupted run, remove the journal and checkpoint \
and parse the document from its first page"
}
WarningKind::CheckpointFailed => {
"the output is complete, but this run could not have resumed; free space or fix \
the permissions where its checkpoint is written"
}
WarningKind::ChartUnverified => {
"do not trust the text; `chart.pred_locs` in `--json` is the chart's number head, \
each bar's height from 0 (smallest) to 1 (largest), and slots past the last bar \
mean nothing"
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RunWarning {
pub kind: &'static str,
pub message: String,
pub recovery: &'static str,
pub fields: serde_json::Map<String, serde_json::Value>,
}
impl RunWarning {
#[must_use]
pub fn to_json(&self) -> serde_json::Value {
let mut obj = serde_json::Map::new();
obj.insert("kind".into(), self.kind.into());
obj.insert("message".into(), self.message.clone().into());
obj.insert("recovery".into(), self.recovery.into());
obj.extend(self.fields.clone());
serde_json::Value::Object(obj)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelIdentity {
pub model_id: &'static str,
pub source_sha256: Option<String>,
}
impl ModelIdentity {
#[must_use]
pub fn version_tag(&self) -> String {
match &self.source_sha256 {
Some(sha) => format!("{}@{sha}", self.model_id),
None => self.model_id.to_owned(),
}
}
pub fn merge_into(&self, value: &mut serde_json::Value) {
value["model_id"] = self.model_id.into();
value["source_sha256"] = self.source_sha256.clone().into();
}
}
fn thousands(n: usize) -> String {
let digits = n.to_string();
let mut out = String::with_capacity(digits.len() + digits.len() / 3);
for (i, c) in digits.chars().enumerate() {
if i > 0 && (digits.len() - i).is_multiple_of(3) {
out.push(',');
}
out.push(c);
}
out
}
#[must_use]
pub fn decode_warnings(report: &DecodeReport) -> Vec<RunWarning> {
let mut warnings = report.admission_warnings.clone();
warnings.extend(match &report.document {
Some(stats) => document_warnings(report, stats),
None => v1_page_warnings(report),
});
warnings
}
fn v1_page_warnings(report: &DecodeReport) -> Vec<RunWarning> {
let Some(PageBodies { expected, bodies }) = report.pages else {
return Vec::new();
};
if bodies == expected {
return Vec::new();
}
let mut fields = serde_json::Map::new();
fields.insert("expected".into(), expected.into());
fields.insert("got".into(), bodies.into());
if bodies > expected {
return vec![RunWarning {
kind: "v1_extra_pages",
message: format!(
"v1 produced {bodies} page bodies for {expected} pages: it repeated or invented \
pages, so this output is unreliable"
),
recovery: warning_recovery(WarningKind::V1ExtraPages),
fields,
}];
}
let warning = match (report.finish_reason, report.cap) {
(FinishReason::Length, Some(LengthCap::MaxLength)) => {
fields.insert("cause".into(), "user_cap".into());
RunWarning {
kind: "v1_truncated",
message: format!(
"stopped after {bodies} of {expected} pages: the --max-length cap of {} \
generated tokens was reached",
thousands(report.max_length)
),
recovery: warning_recovery(WarningKind::V1TruncatedUserCap),
fields,
}
}
(FinishReason::Length, _) => {
fields.insert("cause".into(), "positions".into());
let dense = if report.max_positions == MAX_POSITION_EMBEDDINGS {
" (≈29 dense pages)"
} else {
""
};
RunWarning {
kind: "v1_truncated",
message: format!(
"stopped after {bodies} of {expected} pages: v1 shares {} positions between \
prompt and output{dense}",
thousands(report.max_positions)
),
recovery: warning_recovery(WarningKind::V1TruncatedPositions),
fields,
}
}
(FinishReason::Stop, _) => RunWarning {
kind: "v1_page_merge",
message: format!(
"v1 produced {bodies} page bodies for {expected} pages; page boundaries in this \
output are unreliable"
),
recovery: warning_recovery(WarningKind::V1PageMerge),
fields,
},
(FinishReason::Budget | FinishReason::Cancelled | FinishReason::Runaway, _) => {
return Vec::new();
}
};
vec![warning]
}
fn resume_recompute_warning(from_page: usize) -> RunWarning {
let mut fields = serde_json::Map::new();
fields.insert("from_page".into(), from_page.into());
RunWarning {
kind: "resume_recompute",
message: format!(
"resumed with a recomputed window at page {from_page}: pages from there on can \
differ from an uninterrupted run's"
),
recovery: warning_recovery(WarningKind::ResumeRecompute),
fields,
}
}
fn checkpoint_failed_warning(cause: &str) -> RunWarning {
let mut fields = serde_json::Map::new();
fields.insert("cause".into(), cause.into());
RunWarning {
kind: "checkpoint_failed",
message: format!("the run stopped writing its journal and checkpoint: {cause}"),
recovery: warning_recovery(WarningKind::CheckpointFailed),
fields,
}
}
fn page_reread_warning(replaced: &[usize], kept: &[(usize, String)]) -> Option<RunWarning> {
if replaced.is_empty() && kept.is_empty() {
return None;
}
let mut parts = Vec::new();
if !replaced.is_empty() {
let list = replaced
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
let (noun, verb) = if replaced.len() == 1 {
("page", "was")
} else {
("pages", "were")
};
parts.push(format!(
"{noun} {list} {verb} read again alone, and the re-read replaced the capped text"
));
}
parts.extend(
kept.iter()
.map(|(page, why)| format!("page {page} kept its capped text: {why}")),
);
let mut fields = serde_json::Map::new();
fields.insert("pages".into(), replaced.into());
fields.insert(
"kept".into(),
kept.iter()
.map(|(page, _)| *page)
.collect::<Vec<_>>()
.into(),
);
Some(RunWarning {
kind: "v2_page_reread",
message: parts.join("; "),
recovery: warning_recovery(WarningKind::V2PageReread),
fields,
})
}
fn reread_capped_pages(
document: DocumentPages,
mut reread: impl FnMut(&PageResult) -> Result<PageResult, String>,
) -> (DocumentPages, Option<RunWarning>) {
let DocumentPages {
markdown,
raw,
mut pages,
} = document;
let mut replaced = Vec::new();
let mut kept = Vec::new();
for page in pages
.iter_mut()
.filter(|page| matches!(page.finish, Some(PageFinish::PageCap | PageFinish::Runaway)))
{
let fresh = if crate::shutdown_requested() {
Err("a shutdown was requested before its re-read".to_owned())
} else {
reread(page)
};
match fresh {
Ok(fresh) => {
replaced.push(page.run_index);
*page = fresh;
}
Err(why) => kept.push((page.run_index, why)),
}
}
let warning = page_reread_warning(&replaced, &kept);
let document = if replaced.is_empty() {
DocumentPages {
markdown,
raw,
pages,
}
} else {
DocumentPages::joined(pages)
};
(document, warning)
}
fn document_warnings(report: &DecodeReport, stats: &DocumentStats) -> Vec<RunWarning> {
let mut warnings = Vec::new();
if stats.gundam_view {
warnings.push(RunWarning {
kind: "uncertified_crop_mode",
message: "Unlimited-OCR-2 read the page in gundam mode, which no oracle has \
certified yet"
.into(),
recovery: warning_recovery(WarningKind::UncertifiedCropMode),
fields: serde_json::Map::new(),
});
}
if stats.leading_text {
warnings.push(RunWarning {
kind: "leading_text",
message: "text the model wrote before its first <PAGE> was joined to page 1".into(),
recovery: warning_recovery(WarningKind::LeadingText),
fields: serde_json::Map::new(),
});
}
let (expected, got) = report.pages.map_or((1, 0), |p| (p.expected, p.bodies));
let closed = |how: PageFinish| -> Vec<usize> {
(1..)
.zip(&stats.page_finish)
.filter_map(|(page, finish)| (*finish == how).then_some(page))
.collect()
};
let (capped, runaway) = (closed(PageFinish::PageCap), closed(PageFinish::Runaway));
if !capped.is_empty() || !runaway.is_empty() {
let listed = |pages: &[usize]| {
let list = pages
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
if pages.len() == 1 {
(format!("page {list}"), "was")
} else {
(format!("pages {list}"), "were")
}
};
let mut said = Vec::new();
if !capped.is_empty() {
let (list, verb) = listed(&capped);
said.push(format!(
"{list} of {expected} reached the per-page token cap and {verb} closed"
));
}
if !runaway.is_empty() {
let (list, verb) = listed(&runaway);
said.push(format!(
"{list} of {expected} {verb} closed by the runaway guard"
));
}
let mut pages = [capped.as_slice(), runaway.as_slice()].concat();
pages.sort_unstable();
let mut fields = serde_json::Map::new();
fields.insert("pages".into(), pages.into());
if !runaway.is_empty() {
fields.insert("runaway".into(), runaway.into());
}
warnings.push(RunWarning {
kind: "v2_page_cap",
message: format!(
"{}: the model probably repeated itself, so that text is unreliable",
said.join("; ")
),
recovery: warning_recovery(WarningKind::V2PageCap),
fields,
});
}
let place = if got == 0 || stats.page_finish.len() > got {
format!("before page {} of {expected}", got + 1)
} else {
format!("inside page {got} of {expected}")
};
let mut fields = serde_json::Map::new();
fields.insert("expected".into(), expected.into());
fields.insert("got".into(), got.into());
match report.finish_reason {
FinishReason::Stop | FinishReason::Cancelled | FinishReason::Runaway => {}
FinishReason::Length => warnings.push(RunWarning {
kind: "v2_length",
message: format!(
"stopped {place}: {} Unlimited-OCR-2 shares {} positions between prompt and \
output",
if stats.windows == 0 {
"unwindowed"
} else {
"each window of"
},
thousands(report.max_positions)
),
recovery: warning_recovery(WarningKind::V2Length),
fields,
}),
FinishReason::Budget => warnings.push(RunWarning {
kind: "v2_budget",
message: format!("stopped {place}: the forward stage budget ran out"),
recovery: warning_recovery(WarningKind::V2Budget),
fields,
}),
}
warnings
}
#[must_use]
pub fn partial_output(
report: &DecodeReport,
warnings: &[RunWarning],
model: &ModelIdentity,
) -> Option<FocrError> {
let finish = report.run_finish_reason();
let runaway = report.document.is_none() && finish == FinishReason::Runaway;
if finish == FinishReason::Stop || (report.document.is_none() && !runaway) {
return None;
}
let (expected, pages_out) = match report.pages {
Some(p) => (p.expected, p.bodies),
None if runaway => (report.decodes, report.decodes),
None => (1, 0),
};
let message = if !runaway {
format!(
"the document stopped early ({}) after {pages_out} of {expected} page bodies; the \
pages produced were written",
finish.as_str()
)
} else if report.decodes > 1 {
format!(
"the runaway guard stopped {} of {} decodes because their output kept repeating; \
the text before each stop was written",
report.runaway_stops, report.decodes
)
} else {
"the runaway guard stopped the decode because its output kept repeating; the text \
before that point was written"
.into()
};
Some(FocrError::Partial(Box::new(crate::error::PartialOutput {
finish_reason: finish.as_str(),
pages_out,
warnings: warnings.iter().map(RunWarning::to_json).collect(),
model_id: model.model_id,
source_sha256: model.source_sha256.clone(),
message,
checkpoint: None,
resumed_from: None,
})))
}
type DocumentSink<'a> = &'a mut dyn FnMut(&DocumentEvent<'_>);
struct ClosedPage<'a> {
index: usize,
body: &'a str,
tokens: usize,
finish: PageFinish,
elapsed: std::time::Duration,
}
#[derive(Debug)]
struct PageFrame<'a> {
sizes: Vec<(u32, u32)>,
origins: &'a [PageOrigin],
model_id: &'static str,
}
impl PageFrame<'_> {
fn page(&self, index: usize, body: &str) -> PageResult {
let k = index - 1;
PageResult::read(index, body, self.sizes[k], self.origins[k], self.model_id)
}
fn closed_page(&self, page: &ClosedPage<'_>) -> PageResult {
PageResult {
tokens: Some(page.tokens),
finish: Some(page.finish),
elapsed_ms: Some(millis(page.elapsed)),
..self.page(page.index, page.body)
}
}
fn token_pages(&self, decoded: &DecodedDocument) -> Vec<PageResult> {
let split = &decoded.split;
split
.bodies
.iter()
.enumerate()
.map(|(k, body)| PageResult {
tokens: split.tokens.get(k).copied(),
finish: decoded.finish.get(k).copied(),
elapsed_ms: decoded.elapsed.get(k).copied().map(millis),
..self.page(k + 1, body)
})
.collect()
}
fn text_pages(&self, text: &str) -> Vec<PageResult> {
let bodies: Vec<&str> = postprocess::text_page_bodies(text).collect();
if bodies.len() != self.sizes.len() {
return Vec::new();
}
bodies
.iter()
.enumerate()
.map(|(k, body)| self.page(k + 1, body))
.collect()
}
}
#[derive(Debug)]
struct DecodedDocument {
split: postprocess::TokenPages,
finish: Vec<PageFinish>,
elapsed: Vec<std::time::Duration>,
}
const MAX_NEW_TOKENS_ENV: &str = "FOCR_MAX_NEW_TOKENS";
const DECODE_STATELESS_ENV: &str = "FOCR_DECODE_STATELESS";
const DECODE_F32_ENV: &str = "FOCR_DECODE_F32";
const UNLIMITED_VISION_CACHE_ENV: &str = "FOCR_UNLIMITED_VISION_CACHE";
const STREAMED_VIEW_CHUNK: usize = 8;
fn unlimited_vision_cache_enabled_for(value: Option<&str>) -> bool {
!matches!(
value
.map(|value| value.trim().to_ascii_lowercase())
.as_deref(),
Some("0" | "off" | "false" | "no")
)
}
fn unlimited_vision_cache_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| {
let value = std::env::var(UNLIMITED_VISION_CACHE_ENV).ok();
unlimited_vision_cache_enabled_for(value.as_deref())
})
}
fn stream_vision_tower() -> bool {
static STREAM: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*STREAM.get_or_init(|| match std::env::var("FOCR_STREAM_VISION").ok() {
Some(value) => matches!(
value.trim().to_ascii_lowercase().as_str(),
"1" | "on" | "true" | "yes"
),
None => cfg!(target_os = "ios"),
})
}
const DECODE_INT8_ENV: &str = "FOCR_DECODE_INT8";
static FORCE_INT8_DECODE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn force_int8_decode(on: bool) -> FocrResult<()> {
if on {
require_experimental_full_int8_recipe()?;
}
FORCE_INT8_DECODE.store(on, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
fn int8_decode_requested() -> bool {
FORCE_INT8_DECODE.load(std::sync::atomic::Ordering::Relaxed)
|| std::env::var(DECODE_INT8_ENV).is_ok_and(|value| is_truthy(&value))
}
#[must_use]
pub fn experimental_full_int8_decode_requested() -> bool {
int8_decode_requested()
}
fn require_experimental_full_int8_recipe() -> FocrResult<()> {
let recipe = Recipe::from_env();
if recipe.attn_int8() && recipe.lmhead_int8() {
return Ok(());
}
Err(FocrError::Usage(format!(
"{DECODE_INT8_ENV}=1 requests the experimental all-int8 decoder; it also requires \
{}=1 and {}=1 because attention q/k/v/o and lm_head are outside the validated \
default recipe",
crate::quant::recipe::FOCR_INT8_ATTN_ENV,
crate::quant::recipe::FOCR_INT8_LMHEAD_ENV,
)))
}
pub fn validate_experimental_full_int8_decode() -> FocrResult<()> {
if int8_decode_requested() {
require_experimental_full_int8_recipe()?;
}
Ok(())
}
const GOT_FORMAT_ENV: &str = "FOCR_GOT_FORMAT";
static FORCE_GOT_FORMAT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn force_got_format(on: bool) {
FORCE_GOT_FORMAT.store(on, std::sync::atomic::Ordering::Relaxed);
}
fn got_format_requested() -> bool {
FORCE_GOT_FORMAT.load(std::sync::atomic::Ordering::Relaxed)
|| std::env::var_os(GOT_FORMAT_ENV).is_some()
}
const REREAD_CAPPED_ENV: &str = "FOCR_REREAD_CAPPED_PAGES";
#[must_use]
pub fn reread_capped_requested() -> bool {
std::env::var(REREAD_CAPPED_ENV).is_ok_and(|value| is_truthy(&value))
}
static FORCE_GOT_SQUARE_PAD: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn force_got_square_pad(on: bool) {
FORCE_GOT_SQUARE_PAD.store(on, std::sync::atomic::Ordering::Relaxed);
}
static GOT_REGION: std::sync::Mutex<Option<got::Region>> = std::sync::Mutex::new(None);
pub fn set_got_region(region: Option<got::Region>) {
*GOT_REGION.lock().unwrap_or_else(|e| e.into_inner()) = region;
}
fn got_region_prefix(width: u32, height: u32) -> String {
GOT_REGION
.lock()
.unwrap_or_else(|e| e.into_inner())
.map(|region| region.prefix(width, height))
.unwrap_or_default()
}
fn got_input(img: &image::DynamicImage) -> std::borrow::Cow<'_, image::DynamicImage> {
if FORCE_GOT_SQUARE_PAD.load(std::sync::atomic::Ordering::Relaxed) {
std::borrow::Cow::Owned(crate::preprocess::pad_to_white_square(img))
} else {
std::borrow::Cow::Borrowed(img)
}
}
const SMOLVLM2_QUESTION_ENV: &str = "FOCR_SMOLVLM2_QUESTION";
static SMOLVLM2_QUESTION: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub fn set_smolvlm2_question(question: Option<String>) {
*SMOLVLM2_QUESTION
.lock()
.expect("smolvlm2 question mutex poisoned") = question;
}
fn smolvlm2_question() -> String {
if let Some(q) = SMOLVLM2_QUESTION
.lock()
.expect("smolvlm2 question mutex poisoned")
.clone()
{
return q;
}
std::env::var(SMOLVLM2_QUESTION_ENV).unwrap_or_else(|_| smolvlm2::DESCRIBE_QUESTION.to_string())
}
static FORWARDS_LIVE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static FORWARDS_MAX: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static FORWARD_UNDER_GUARD: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
thread_local! {
static CACHE_GUARD_HELD_HERE: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
pub(crate) struct ForwardPass(());
impl Drop for ForwardPass {
fn drop(&mut self) {
FORWARDS_LIVE.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
}
}
pub(crate) fn enter_forward() -> ForwardPass {
let live = FORWARDS_LIVE.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
FORWARDS_MAX.fetch_max(live, std::sync::atomic::Ordering::SeqCst);
if CACHE_GUARD_HELD_HERE.with(std::cell::Cell::get) > 0 {
FORWARD_UNDER_GUARD.store(true, std::sync::atomic::Ordering::SeqCst);
}
ForwardPass(())
}
#[doc(hidden)]
pub fn forward_gauge_take() -> (usize, bool) {
let max = FORWARDS_MAX.swap(0, std::sync::atomic::Ordering::SeqCst);
let under = FORWARD_UNDER_GUARD.swap(false, std::sync::atomic::Ordering::SeqCst);
(max, under)
}
const BATCH_VISION_ENV: &str = "FOCR_BATCH_VISION";
fn batch_vision_enabled() -> bool {
!matches!(
std::env::var(BATCH_VISION_ENV)
.ok()
.map(|v| v.trim().to_ascii_lowercase())
.as_deref(),
Some("0" | "off" | "false" | "no")
)
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DecodeOverrides {
pub max_length: Option<usize>,
pub temperature: Option<f32>,
pub no_repeat_ngram: Option<usize>,
pub ngram_window: Option<usize>,
pub max_page_tokens: Option<usize>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct PreprocessOverrides {
pub base_size: Option<usize>,
pub image_size: Option<usize>,
pub gundam: Option<bool>,
}
static PREPROCESS_OVERRIDES: Mutex<PreprocessOverrides> = Mutex::new(PreprocessOverrides {
base_size: None,
image_size: None,
gundam: None,
});
pub fn set_preprocess_overrides(overrides: PreprocessOverrides) {
*PREPROCESS_OVERRIDES
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = overrides;
}
fn resolve_preprocess_mode(o: PreprocessOverrides) -> preprocess::PreprocessMode {
let base_size = o.base_size.unwrap_or(1024);
if o.gundam == Some(true) {
preprocess::PreprocessMode::Gundam {
base_size,
tile_size: o.image_size.unwrap_or(640),
}
} else {
preprocess::PreprocessMode::Base { base_size }
}
}
fn paged_document_view(
mode: preprocess::PreprocessMode,
model: &str,
) -> FocrResult<preprocess::PreprocessMode> {
match mode {
preprocess::PreprocessMode::Base { .. } => Ok(mode),
preprocess::PreprocessMode::Gundam { .. } => Err(FocrError::Usage(format!(
"--crop-mode gundam reads a single page; a {model} document reads every page as a \
base view. Omit --crop-mode, or parse the pages one at a time without --multi-page"
))),
}
}
fn preprocess_mode() -> preprocess::PreprocessMode {
resolve_preprocess_mode(
*PREPROCESS_OVERRIDES
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
)
}
fn preprocess_profile(arch: &dyn model_arch::ModelArch) -> preprocess::PreprocessProfile {
match arch.unlimited_variant() {
Some(model_arch::UnlimitedVariant::V2SrSwa) => preprocess::PreprocessProfile::V2,
Some(model_arch::UnlimitedVariant::V1Rswa) | None => preprocess::PreprocessProfile::V1,
}
}
static DECODE_OVERRIDES: Mutex<DecodeOverrides> = Mutex::new(DecodeOverrides {
max_length: None,
temperature: None,
no_repeat_ngram: None,
ngram_window: None,
max_page_tokens: None,
});
pub fn set_decode_overrides(overrides: DecodeOverrides) {
*DECODE_OVERRIDES
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = overrides;
}
fn decode_overrides() -> DecodeOverrides {
*DECODE_OVERRIDES
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn apply_decode_overrides(params: &mut DecodeParams, overrides: DecodeOverrides) {
if let Some(n) = overrides.max_length {
params.max_length = n;
}
if let Some(t) = overrides.temperature {
params.temperature = t;
}
if let Some(n) = overrides.no_repeat_ngram {
params.no_repeat_ngram_size = n;
}
if let Some(w) = overrides.ngram_window {
params.ngram_window = w;
}
}
const SPEC_DECODE_ENV: &str = "FOCR_SPEC_DECODE";
fn spec_decode_enabled() -> bool {
static FLAG: OnceLock<bool> = OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os(SPEC_DECODE_ENV).is_some())
}
pub(crate) fn timing_log(msg: &str) {
if crate::timing_enabled() {
crate::progress::stderr_message(format_args!("[focr-timing] {msg}"));
}
}
fn decode_params_from_env(overrides: DecodeOverrides) -> DecodeParams {
let mut p = DecodeParams::single_image();
if let Some(raw) = std::env::var_os(MAX_NEW_TOKENS_ENV)
&& let Some(s) = raw.to_str()
&& let Ok(n) = s.trim().parse::<usize>()
&& n > 0
{
p.max_length = n;
}
if let Ok(Some(n)) = test_max_positions() {
p.max_positions = *n;
}
apply_decode_overrides(&mut p, overrides);
p
}
fn decode_params_for(arch: &dyn model_arch::ModelArch, overrides: DecodeOverrides) -> DecodeParams {
let mut p = decode_params_from_env(overrides);
if let Some(pages) = arch.decode_contract().pages {
if !matches!(test_max_positions(), Ok(Some(_))) {
p.max_positions = sampler::UOCR2_MAX_POSITIONS;
}
p.max_page_tokens = overrides.max_page_tokens.unwrap_or(pages.max_page_tokens);
if let Ok(domain) = srswa::entmax_domain() {
p.entmax_domain = *domain;
}
}
p
}
fn model_cache() -> &'static ModelCache {
static CACHE: OnceLock<ModelCache> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(None))
}
fn model_load_admission() -> &'static ExclusiveGate {
static ADMISSION: ExclusiveGate = ExclusiveGate::new();
&ADMISSION
}
fn forward_admission() -> &'static ExclusiveGate {
static ADMISSION: ExclusiveGate = ExclusiveGate::new();
&ADMISSION
}
fn model_cache_guard() -> FocrResult<TrackedCacheGuard> {
let guard = model_cache()
.lock()
.map_err(|_| FocrError::Other(anyhow::anyhow!("model cache mutex poisoned")))?;
CACHE_GUARD_HELD_HERE.with(|c| c.set(c.get() + 1));
Ok(TrackedCacheGuard(guard))
}
struct TrackedCacheGuard(MutexGuard<'static, ModelCacheEntry>);
impl std::ops::Deref for TrackedCacheGuard {
type Target = ModelCacheEntry;
fn deref(&self) -> &ModelCacheEntry {
&self.0
}
}
impl std::ops::DerefMut for TrackedCacheGuard {
fn deref_mut(&mut self) -> &mut ModelCacheEntry {
&mut self.0
}
}
impl Drop for TrackedCacheGuard {
fn drop(&mut self) {
CACHE_GUARD_HELD_HERE.with(|c| c.set(c.get() - 1));
}
}
fn resolve_existing_model_artifact(path: &Path) -> Option<PathBuf> {
if path.is_dir() {
let index = path.join(shards::SHARD_INDEX_NAME);
if index.is_file() {
return Some(index);
}
raw_single_shard_names()
.map(|name| path.join(name))
.find(|shard| shard.is_file())
} else if path.exists() {
Some(path.to_path_buf())
} else {
None
}
}
fn raw_single_shard_names() -> impl Iterator<Item = &'static str> {
model_arch::registry()
.iter()
.filter_map(|arch| arch.tensor_census())
.filter_map(|census| match unlimited_ocr_census::source_shards(census) {
[(name, _)] => Some(*name),
_ => None,
})
}
fn is_short_model_spec(path: &Path) -> bool {
let mut components = path.components();
matches!(components.next(), Some(Component::Normal(_))) && components.next().is_none()
}
fn registry_spec(spec: &Path) -> Option<&'static dyn model_arch::ModelArch> {
if !is_short_model_spec(spec) {
return None;
}
spec.to_str().and_then(model_arch::arch_by_id)
}
fn artifact_holds(resolved: &Path, arch: &dyn model_arch::ModelArch) -> bool {
validate_model_header_file(resolved).is_ok_and(|found| found.id() == arch.id())
}
fn check_named_artifact(resolved: &Path, arch: &dyn model_arch::ModelArch) -> FocrResult<()> {
match validate_model_header_file(resolved) {
Ok(found) if found.id() != arch.id() => Err(FocrError::ModelNotFound(format!(
"{} holds the {:?} model, not the requested {:?}",
resolved.display(),
found.id(),
arch.id()
))),
_ => Ok(()),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ModelQuantPreference {
Int8,
Int4,
}
impl ModelQuantPreference {
const ALL: [ModelQuantPreference; 2] = [Self::Int8, Self::Int4];
fn as_str(self) -> &'static str {
match self {
Self::Int8 => "int8",
Self::Int4 => "int4",
}
}
}
fn model_quant_preference_from_os(raw: Option<&OsStr>) -> Option<ModelQuantPreference> {
let value = raw?.to_str()?.trim().to_ascii_lowercase();
match value.as_str() {
"int8" | "q8" => Some(ModelQuantPreference::Int8),
"int4" | "q4" => Some(ModelQuantPreference::Int4),
_ => None,
}
}
fn model_quant_preference() -> Option<ModelQuantPreference> {
let raw = std::env::var_os(MODEL_QUANT_ENV);
model_quant_preference_from_os(raw.as_deref())
}
#[cfg(feature = "native")]
fn dist_cache_root() -> Option<PathBuf> {
crate::dist::cache_root()
}
#[cfg(not(feature = "native"))]
fn dist_cache_root() -> Option<PathBuf> {
None
}
fn model_search_dirs() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(raw) = std::env::var_os(MODEL_DIR_ENV)
&& !raw.is_empty()
{
dirs.extend(std::env::split_paths(&raw));
}
if let Some(root) = dist_cache_root() {
dirs.extend(cache_search_dirs(root.join("models")));
}
dirs
}
fn cache_search_dirs(models: PathBuf) -> Vec<PathBuf> {
let mut subdirs: Vec<PathBuf> = std::fs::read_dir(&models)
.into_iter()
.flatten()
.flatten()
.filter(|e| !e.file_name().as_encoded_bytes().starts_with(b"."))
.map(|e| e.path())
.filter(|p| p.is_dir())
.collect();
subdirs.sort();
subdirs.push(models);
subdirs
}
#[must_use]
pub fn model_resolution_search_dirs() -> Vec<PathBuf> {
model_search_dirs()
}
#[derive(Debug, Clone)]
pub struct MusicPageMeta {
pub staves: Vec<(usize, tromr::StaffBBox)>,
pub skips: Vec<tromr::StaffSkip>,
pub warnings: Vec<tromr::MusicWarning>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChartPageMeta {
pub pred_locs: Vec<f32>,
pub reliable_distance: Option<f64>,
pub reliable: Option<bool>,
}
impl ChartPageMeta {
fn from_result(res: &onechart::ChartResult) -> Self {
Self {
pred_locs: res.pred_locs.clone().unwrap_or_default(),
reliable_distance: res.reliable_distance,
reliable: res.reliable,
}
}
}
#[must_use]
pub fn chart_warning(meta: &ChartPageMeta) -> Option<RunWarning> {
let message = match meta.reliable {
Some(true) => return None,
Some(false) => "OneChart's text disagrees with its number head: the values are wrong",
None => "OneChart's text gave no values to check: it cannot be verified",
};
Some(RunWarning {
kind: "chart_unverified",
message: message.into(),
recovery: warning_recovery(WarningKind::ChartUnverified),
fields: serde_json::Map::new(),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LayoutSpan {
pub label: String,
pub boxes: Vec<[i64; 4]>,
pub level: Option<u8>,
pub text: Option<String>,
}
impl LayoutSpan {
#[must_use]
pub fn to_json(&self) -> serde_json::Value {
let mut value = serde_json::json!({ "label": self.label, "boxes": self.boxes });
if let Some(level) = self.level {
value["level"] = level.into();
}
if let Some(text) = &self.text {
value["text"] = text.as_str().into();
}
value
}
#[must_use]
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
let label = value.get("label")?.as_str()?.to_owned();
let boxes = value
.get("boxes")?
.as_array()?
.iter()
.filter_map(|b| {
let coords = b.as_array()?;
if coords.len() != 4 {
return None;
}
let mut out = [0_i64; 4];
for (slot, coord) in out.iter_mut().zip(coords) {
*slot = coord.as_i64()?;
}
Some(out)
})
.collect();
let level = value
.get("level")
.and_then(serde_json::Value::as_u64)
.and_then(|l| u8::try_from(l).ok());
let text = value
.get("text")
.and_then(serde_json::Value::as_str)
.map(str::to_owned);
Some(Self {
label,
boxes,
level,
text,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Half {
Left,
Right,
}
impl Half {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Half::Left => "left",
Half::Right => "right",
}
}
#[must_use]
pub fn initial(self) -> char {
match self {
Half::Left => 'l',
Half::Right => 'r',
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PageOrigin {
pub source_page: usize,
pub half: Option<Half>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageResult {
pub run_index: usize,
pub source_page: usize,
pub half: Option<Half>,
pub window: Option<usize>,
pub text: String,
pub raw: String,
pub size: (u32, u32),
pub layout: Vec<LayoutSpan>,
pub figures: Vec<postprocess::FigureRef>,
pub tokens: Option<usize>,
pub finish: Option<PageFinish>,
pub elapsed_ms: Option<u64>,
pub model_id: &'static str,
}
impl PageResult {
fn read(
run_index: usize,
body: &str,
size: (u32, u32),
origin: PageOrigin,
model_id: &'static str,
) -> Self {
let postprocess::PageOutput {
text,
layout,
figures,
} = postprocess::finalize_page(body, run_index - 1, size);
Self {
run_index,
source_page: origin.source_page,
half: origin.half,
window: None,
text,
raw: body.trim().to_owned(),
size,
layout,
figures,
tokens: None,
finish: None,
elapsed_ms: None,
model_id,
}
}
#[must_use]
pub fn to_json(&self) -> serde_json::Value {
let mut value = serde_json::json!({ "page": self.source_page });
if let Some(half) = self.half {
value["half"] = half.as_str().into();
}
value["run_index"] = self.run_index.into();
if let Some(window) = self.window {
value["window"] = window.into();
}
value["width"] = self.size.0.into();
value["height"] = self.size.1.into();
value["text"] = self.text.as_str().into();
value["layout"] = self.layout.iter().map(LayoutSpan::to_json).collect();
if let Some(tokens) = self.tokens {
value["tokens"] = tokens.into();
}
if let Some(finish) = self.finish {
value["finish"] = finish.as_str().into();
}
value
}
}
#[derive(Debug, Clone, Copy)]
pub struct StreamedPage<'a> {
pub index: usize,
pub body: &'a str,
pub page: Option<&'a PageResult>,
}
#[derive(Debug, Clone, Copy)]
pub enum DocumentEvent<'a> {
Page(StreamedPage<'a>),
Window(&'a window::WindowStats),
}
#[derive(Debug)]
struct DocumentPages {
markdown: String,
raw: String,
pages: Vec<PageResult>,
}
impl DocumentPages {
fn joined(pages: Vec<PageResult>) -> Self {
Self {
markdown: postprocess::join_page_texts(pages.iter().map(|p| &p.text)),
raw: postprocess::join_page_texts(pages.iter().map(|p| &p.raw)),
pages,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageWindow {
Pages(usize),
Auto,
Off,
}
impl PageWindow {
fn refusal(self) -> Option<String> {
match self {
Self::Pages(0) => Some("a page window needs at least one page".into()),
Self::Pages(pages) if pages > UOCR2_UNWINDOWED_PAGE_LIMIT => Some(format!(
"a page window holds at most {UOCR2_UNWINDOWED_PAGE_LIMIT} pages, not {pages}"
)),
_ => None,
}
}
}
impl std::str::FromStr for PageWindow {
type Err = String;
fn from_str(value: &str) -> Result<Self, String> {
let window = match value {
"auto" => Self::Auto,
"off" => Self::Off,
pages => Self::Pages(pages.parse().map_err(|_| {
format!("expected a number of pages, `auto` or `off`, not `{pages}`")
})?),
};
window.refusal().map_or(Ok(window), Err)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ResumePolicy {
#[default]
Never,
IfMatching,
Recompute,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct DocumentOptions {
pub page_window: Option<PageWindow>,
pub resume: ResumePolicy,
pub run: Option<checkpoint::RunFiles>,
pub reread_capped: bool,
}
impl DocumentOptions {
#[must_use]
pub fn page_window(mut self, window: PageWindow) -> Self {
self.page_window = Some(window);
self
}
#[must_use]
pub fn resume(mut self, resume: ResumePolicy) -> Self {
self.resume = resume;
self
}
#[must_use]
pub fn run(mut self, files: checkpoint::RunFiles) -> Self {
self.run = Some(files);
self
}
#[must_use]
pub fn reread_capped(mut self, on: bool) -> Self {
self.reread_capped = on;
self
}
fn plan(
&self,
model: &str,
windowed: bool,
pages: usize,
auto: impl FnOnce() -> FocrResult<usize>,
) -> FocrResult<DocumentPlan> {
let plan = self.window_plan(model, windowed, pages, auto)?;
if self.resume != ResumePolicy::Never {
if self.run.is_none() {
return Err(FocrError::Usage(
"a document resume needs the interrupted run's files (RunFiles)".into(),
));
}
if plan == DocumentPlan::OnePass {
return Err(FocrError::Usage(format!(
"{model} reads this document in one pass, which has no window to resume \
from"
)));
}
}
Ok(plan)
}
fn window_plan(
&self,
model: &str,
windowed: bool,
pages: usize,
auto: impl FnOnce() -> FocrResult<usize>,
) -> FocrResult<DocumentPlan> {
if let Some(refusal) = self.page_window.and_then(PageWindow::refusal) {
return Err(FocrError::Usage(refusal));
}
match self.page_window {
Some(PageWindow::Auto) if windowed => Ok(DocumentPlan::Windows(auto()?)),
Some(PageWindow::Auto | PageWindow::Off) => Ok(DocumentPlan::OnePass),
None if windowed => Ok(DocumentPlan::Windows(window::DEFAULT_PAGE_WINDOW)),
Some(PageWindow::Pages(n)) if windowed => Ok(DocumentPlan::Windows(n)),
None => Ok(DocumentPlan::OnePass),
Some(PageWindow::Pages(n)) if n >= pages => Ok(DocumentPlan::OnePass),
Some(PageWindow::Pages(n)) => Err(FocrError::Usage(format!(
"{model} has no windowed decoding, so it cannot read {pages} pages in windows \
of {n}; parse without a page window, or with PageWindow::Off"
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DocumentPlan {
OnePass,
Windows(usize),
}
struct WindowedStream<'a> {
model_id: &'static str,
pages: Vec<PageResult>,
on_event: Option<DocumentSink<'a>>,
}
impl WindowedStream<'_> {
fn add_page(&mut self, page: &checkpoint::JournalPage, body: &str) {
let result = PageResult {
window: Some(page.window),
tokens: page.tokens,
finish: page.finish,
..PageResult::read(
page.run_index,
body,
page.read.size,
page.read.origin,
self.model_id,
)
};
if let Some(sink) = self.on_event.as_deref_mut() {
sink(&DocumentEvent::Page(StreamedPage {
index: page.run_index,
body: body.trim(),
page: Some(&result),
}));
}
self.pages.push(result);
}
fn window_ended(&mut self, window: &window::WindowStats) {
if let Some(sink) = self.on_event.as_deref_mut() {
sink(&DocumentEvent::Window(window));
}
}
}
enum DocumentParse {
OnePass(Vec<Preprocessed>),
Windowed(DocumentPages, Vec<window::WindowStats>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DocumentResult {
pub markdown: String,
pub raw: String,
pub pages: Vec<PageResult>,
pub pages_out: usize,
pub skipped: Vec<crate::page_source::SkippedPage>,
pub finish_reason: FinishReason,
pub extra_markers: usize,
pub windows: Vec<window::WindowStats>,
pub resumed_from: Option<usize>,
pub warnings: Vec<RunWarning>,
pub model: ModelIdentity,
}
#[derive(Debug, Clone)]
pub struct RecognizedDocument {
pub markdown: String,
pub raw: String,
pub layout: Vec<LayoutSpan>,
}
#[derive(Debug, Clone)]
pub struct ExtractedFigure {
pub index: usize,
pub label: String,
pub bbox: [i64; 4],
pub markdown_ref: String,
pub image: image::DynamicImage,
}
fn push_unique_path(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.iter().any(|p| p == &path) {
paths.push(path);
}
}
fn versioned_quant_path(direct: &Path, quant: ModelQuantPreference) -> PathBuf {
let version = direct
.file_stem()
.and_then(|stem| stem.to_str())
.and_then(model_arch::arch_by_id)
.map_or(crate::UNLIMITED_OCR_ARTIFACT_VERSION, |arch| {
arch.artifact_version()
});
direct.with_extension(format!("v{version}.{}.focrq", quant.as_str()))
}
fn short_model_candidates(
search_dir: &Path,
spec: &Path,
quant: Option<ModelQuantPreference>,
) -> Vec<PathBuf> {
let direct = search_dir.join(spec);
let mut candidates = Vec::new();
let is_focrq_or_bare = match spec.extension() {
None => true,
Some(ext) => ext.eq_ignore_ascii_case("focrq"),
};
if is_focrq_or_bare {
if let Some(quant) = quant {
push_unique_path(&mut candidates, versioned_quant_path(&direct, quant));
push_unique_path(
&mut candidates,
direct.with_extension(format!("{}.focrq", quant.as_str())),
);
}
push_unique_path(&mut candidates, direct.clone());
push_unique_path(&mut candidates, direct.with_extension("focrq"));
for quant in ModelQuantPreference::ALL {
push_unique_path(&mut candidates, versioned_quant_path(&direct, quant));
}
for quant in ModelQuantPreference::ALL {
push_unique_path(
&mut candidates,
direct.with_extension(format!("{}.focrq", quant.as_str())),
);
}
} else {
push_unique_path(&mut candidates, direct);
}
candidates
}
fn is_searchable_model_spec(path: &Path) -> bool {
path.is_relative() && !path.components().any(|c| matches!(c, Component::ParentDir))
}
fn model_search_specs(spec: &Path) -> Vec<PathBuf> {
let mut specs = vec![spec.to_path_buf()];
if !is_short_model_spec(spec)
&& let Some(file_name) = spec.file_name()
{
let basename = PathBuf::from(file_name);
if basename.as_path() != spec {
specs.push(basename);
}
}
specs
}
fn focrq_declares_wasm_recipe(path: &Path) -> bool {
let Ok(mut file) = std::fs::File::open(path) else {
return false;
};
let mut preamble = [0u8; FOCRQ_PREAMBLE_LEN];
if file.read_exact(&mut preamble).is_err()
|| &preamble[..weights::FOCRQ_MAGIC.len()] != weights::FOCRQ_MAGIC
{
return false;
}
let header_len = u64::from_le_bytes(
preamble[43..51]
.try_into()
.expect("fixed .focrq header length slice"),
);
let Ok(header_len) = usize::try_from(header_len) else {
return false;
};
if header_len == 0 || header_len > MODEL_HEADER_VALIDATION_MAX_BYTES {
return false;
}
let mut header_bytes = vec![0u8; header_len];
if file.read_exact(&mut header_bytes).is_err() {
return false;
}
#[derive(serde::Deserialize)]
struct RecipeProbe {
#[serde(default)]
model_id: Option<String>,
#[serde(default)]
packing_manifest: Option<FocrqPackingManifestProbe>,
}
let Ok(probe) = serde_json::from_slice::<RecipeProbe>(&header_bytes) else {
return false;
};
let v1 = probe
.model_id
.as_deref()
.is_none_or(|id| id == model_arch::UNLIMITED_OCR_V1_ID);
v1 && probe
.packing_manifest
.and_then(|manifest| manifest.quant_recipe)
.as_deref()
== Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID)
}
fn resolve_model_from_search_dirs_with_quant(
spec: &Path,
search_dirs: &[PathBuf],
quant: Option<ModelQuantPreference>,
) -> FocrResult<PathBuf> {
let specs = model_search_specs(spec);
let wanted = registry_spec(spec);
for dir in search_dirs {
if let Some(resolved) = resolve_existing_model_artifact(dir)
&& !focrq_declares_wasm_recipe(&resolved)
&& wanted.is_none_or(|arch| artifact_holds(&resolved, arch))
{
return Ok(resolved);
}
for search_spec in &specs {
for candidate in short_model_candidates(dir, search_spec, quant) {
if let Some(resolved) = resolve_existing_model_artifact(&candidate)
&& !focrq_declares_wasm_recipe(&resolved)
{
if let Some(arch) = wanted {
check_named_artifact(&resolved, arch)?;
}
return Ok(resolved);
}
}
}
}
let searched = if search_dirs.is_empty() {
"<none>".into()
} else {
search_dirs
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ")
};
Err(FocrError::ModelNotFound(format!(
"no model artifact named {} (searched directories: {searched}; set {MODEL_DIR_ENV} \
or pass an explicit path)",
spec.display()
)))
}
#[cfg(test)]
fn resolve_model_from_search_dirs(spec: &Path, search_dirs: &[PathBuf]) -> FocrResult<PathBuf> {
resolve_model_from_search_dirs_with_quant(spec, search_dirs, None)
}
#[derive(serde::Deserialize)]
struct FocrqHeaderProbe {
tensors: std::collections::BTreeMap<String, weights::TensorRecord>,
#[serde(default)]
arch_target: u8,
#[serde(default)]
format_version: Option<u32>,
#[serde(default)]
source_sha256: String,
#[serde(default)]
license_notice: String,
#[serde(default)]
model_id: String,
#[serde(default)]
packing_manifest: Option<FocrqPackingManifestProbe>,
#[serde(default)]
provenance: Option<shards::FocrqProvenance>,
}
#[derive(serde::Deserialize)]
struct FocrqPackingManifestProbe {
#[serde(default)]
quant_recipe: Option<String>,
}
fn compatible_arch(declared: &str) -> FocrResult<&'static dyn model_arch::ModelArch> {
model_arch::arch_for_declared_id(declared)
.ok_or_else(|| FocrError::FormatMismatch(format!("unknown model_id {declared:?}")))
}
fn validate_header_license(notice: &str, model_id: &str) -> FocrResult<()> {
if model_id == model_arch::UNLIMITED_OCR_V1_ID {
if notice == crate::FOCR_MODEL_LICENSE_NOTICE
|| (notice.contains("Copyright (c) 2026 Baidu") && notice.contains("MIT License"))
{
return Ok(());
}
return Err(FocrError::FormatMismatch(
"Unlimited-OCR .focrq has an incompatible license notice".into(),
));
}
match model_arch::arch_by_id(model_id) {
Some(arch) if notice == arch.license_notice() => Ok(()),
_ => Err(FocrError::FormatMismatch(format!(
".focrq license notice does not match model {model_id:?}"
))),
}
}
fn validate_header_source_sha256(source_sha256: &str) -> FocrResult<()> {
if source_sha256.len() == 64
&& source_sha256
.bytes()
.all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f'))
{
return Ok(());
}
Err(FocrError::FormatMismatch(
".focrq source_sha256 is not 64 lowercase hex characters".into(),
))
}
fn lowercase_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut encoded = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
encoded.push(char::from(HEX[usize::from(byte >> 4)]));
encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
encoded
}
fn header_record_expected_bytes(
name: &str,
record: &weights::TensorRecord,
arch_target: u8,
) -> FocrResult<usize> {
let numel = record.shape.iter().copied().try_fold(1usize, |acc, dim| {
acc.checked_mul(dim).ok_or_else(|| {
FocrError::FormatMismatch(format!("tensor {name:?} shape element count overflows"))
})
})?;
if arch_target == 1 && record.dtype == DType::QInt8PerChan && record.shape.len() == 2 {
return record.shape[0]
.div_ceil(2)
.checked_mul(record.shape[1].div_ceil(8))
.and_then(|panels| panels.checked_mul(16))
.ok_or_else(|| {
FocrError::FormatMismatch(format!(
"tensor {name:?} SMMLA packed byte length overflows"
))
});
}
match record.dtype {
DType::F32 => numel.checked_mul(4),
DType::F16 | DType::BF16 => numel.checked_mul(2),
DType::QInt8PerChan => Some(numel),
DType::QInt4PerGroup if numel.is_multiple_of(2) => Some(numel / 2),
DType::QInt4PerGroup => {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?} has an odd int4 element count"
)));
}
}
.ok_or_else(|| FocrError::FormatMismatch(format!("tensor {name:?} byte length overflows")))
}
fn validate_header_quant_metadata(name: &str, record: &weights::TensorRecord) -> FocrResult<()> {
match record.dtype {
DType::F32 | DType::F16 | DType::BF16 => {
if record.scales_offset != 0
|| record.scales_len != 0
|| record.group_size != 0
|| record.tier != 0
{
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: non-quantized dtype has stray quantization metadata"
)));
}
}
DType::QInt8PerChan => {
let [rows, _cols] = record.shape.as_slice() else {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt8 shape must be rank-2 [rows, cols]"
)));
};
if record.group_size != 0 || record.tier != 0 {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt8 group_size and tier must be zero"
)));
}
let expected_scales = rows.checked_mul(4).ok_or_else(|| {
FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt8 scale byte length overflows"
))
})?;
if record.scales_len != expected_scales {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt8 scales_len {} != rows*f32 {expected_scales}",
record.scales_len
)));
}
}
DType::QInt4PerGroup => {
let [rows, cols] = record.shape.as_slice() else {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt4 shape must be rank-2 [rows, cols]"
)));
};
if !crate::quant::int4::VALID_GROUP_SIZES.contains(&record.group_size)
|| !cols.is_multiple_of(record.group_size)
{
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt4 group_size {} is invalid for {cols} columns",
record.group_size
)));
}
let expected_scales = rows
.checked_mul(cols / record.group_size)
.and_then(|groups| groups.checked_mul(4))
.ok_or_else(|| {
FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt4 scale byte length overflows"
))
})?;
if record.scales_len != expected_scales {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?}: QInt4 scales_len {} != rows*groups*f32 {expected_scales}",
record.scales_len
)));
}
}
}
Ok(())
}
fn validate_header_directory(
directory: &std::collections::BTreeMap<String, weights::TensorRecord>,
payload_len: usize,
arch_target: u8,
) -> FocrResult<()> {
for (name, record) in directory {
validate_header_quant_metadata(name, record)?;
let end = record
.byte_offset
.checked_add(record.byte_len)
.ok_or_else(|| FocrError::FormatMismatch(format!("tensor {name:?} range overflows")))?;
if end > payload_len {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?} overruns the model payload"
)));
}
let expected = header_record_expected_bytes(name, record, arch_target)?;
if record.byte_len != expected {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?} byte_len {} != expected {expected}",
record.byte_len
)));
}
let scales_end = record
.scales_offset
.checked_add(record.scales_len)
.ok_or_else(|| {
FocrError::FormatMismatch(format!("tensor {name:?} scales range overflows"))
})?;
if scales_end > payload_len {
return Err(FocrError::FormatMismatch(format!(
"tensor {name:?} scales overrun the model payload"
)));
}
}
weights::validate_non_overlapping_ranges(directory)?;
Ok(())
}
fn validate_safetensors_header(
header: &[u8],
payload_len: usize,
) -> FocrResult<&'static dyn model_arch::ModelArch> {
let header = shards::parse_safetensors_header(header)?;
if header.len() == 0 {
return Err(FocrError::FormatMismatch(
"safetensors tensor directory is empty; no native forward can use this artifact".into(),
));
}
validate_header_directory(&header.records, payload_len, 0)?;
shards::validate_int64_records(&header, payload_len)?;
identify_checkpoint(&header)
}
fn identify_checkpoint(
header: &shards::SafetensorsHeader,
) -> FocrResult<&'static dyn model_arch::ModelArch> {
let arch = unlimited_ocr_census::identify_raw(
header
.records
.iter()
.map(|(name, record)| (name.as_str(), record)),
)?;
let rules = shards::int64_rules(Some(arch));
shards::check_int64_entries(&header.int64, rules, arch.id())?;
shards::check_int64_rules_present(&header.int64, rules, arch.id())?;
Ok(arch)
}
fn read_validated_safetensors_header(
mut reader: impl Read,
file_len: u64,
) -> FocrResult<(shards::SafetensorsHeader, Vec<u8>, usize)> {
let mut prefix = [0u8; 8];
reader.read_exact(&mut prefix).map_err(|error| {
FocrError::FormatMismatch(format!("safetensors header truncated: {error}"))
})?;
let (header_json, payload_len) = read_safetensors_header_json(reader, prefix, file_len)?;
let header = shards::parse_safetensors_header(&header_json)?;
validate_header_directory(&header.records, payload_len, 0)?;
shards::validate_int64_records(&header, payload_len)?;
let payload_base = prefix.len() + header_json.len();
let mut raw = prefix.to_vec();
raw.extend_from_slice(&header_json);
Ok((header, raw, payload_base))
}
fn read_safetensors_header_json(
mut reader: impl Read,
prefix: [u8; 8],
file_len: u64,
) -> FocrResult<(Vec<u8>, usize)> {
let file_len = usize::try_from(file_len).map_err(|_| {
FocrError::FormatMismatch("model file exceeds this platform's addressable size".into())
})?;
let header_len_u64 = u64::from_le_bytes(prefix);
let header_len = usize::try_from(header_len_u64)
.map_err(|_| FocrError::FormatMismatch("safetensors header length exceeds usize".into()))?;
if header_len == 0 || header_len > MODEL_HEADER_VALIDATION_MAX_BYTES {
return Err(FocrError::FormatMismatch(format!(
"safetensors header length {header_len} is outside the bounded validation limit"
)));
}
let payload_base = 8usize
.checked_add(header_len)
.ok_or_else(|| FocrError::FormatMismatch("safetensors header length overflows".into()))?;
if payload_base > file_len {
return Err(FocrError::FormatMismatch(
"safetensors header overruns the file".into(),
));
}
let mut header = vec![0u8; header_len];
reader.read_exact(&mut header).map_err(|error| {
FocrError::FormatMismatch(format!("safetensors header truncated: {error}"))
})?;
Ok((header, file_len - payload_base))
}
fn validate_sharded_model(
index_path: &Path,
) -> FocrResult<(&'static dyn model_arch::ModelArch, shards::OpenedShards)> {
let opened = shards::open_shards(index_path)?;
let merged = read_opened_shard_headers(&opened)?;
let arch = identify_checkpoint(&merged)?;
Ok((arch, opened))
}
fn read_opened_shard_headers(
opened: &shards::OpenedShards,
) -> FocrResult<shards::SafetensorsHeader> {
let mut headers = Vec::with_capacity(opened.shards.len());
let mut start = 0u64;
for shard in &opened.shards {
let (header, _, payload_base) = read_validated_safetensors_header(&shard.file, shard.len)
.map_err(|error| shards::in_shard(&shard.name, error))?;
headers.push((header, start + payload_base as u64));
start = start.saturating_add(shard.len);
}
shards::merge_shard_headers(&opened.index, headers)
}
#[cfg_attr(not(feature = "native"), allow(dead_code))] pub(crate) fn read_source_headers(
source: &shards::SourceShards,
) -> FocrResult<(shards::SafetensorsHeader, Vec<Vec<u8>>)> {
let mut headers = Vec::with_capacity(source.files().len());
let mut prefixes = Vec::with_capacity(source.files().len());
let mut start = 0u64;
for (name, path) in source.files() {
let (file, file_len) = open_model_file(path)?;
let (header, raw, payload_base) = read_validated_safetensors_header(&file, file_len)
.map_err(|error| shards::in_shard(name, error))?;
headers.push((header, start + payload_base as u64));
prefixes.push(raw);
start = start.saturating_add(file_len);
}
let merged = match source.index() {
Some(index) => shards::merge_shard_headers(index, headers)?,
None => headers
.pop()
.map(|(header, _)| header)
.ok_or_else(|| FocrError::Other(anyhow::anyhow!("a source has one file")))?,
};
Ok((merged, prefixes))
}
#[cfg_attr(not(feature = "native"), allow(dead_code))] pub(crate) fn conversion_arch(
explicit: Option<&'static dyn model_arch::ModelArch>,
header: &shards::SafetensorsHeader,
) -> FocrResult<&'static dyn model_arch::ModelArch> {
let identified = unlimited_ocr_census::identify_raw(
header
.records
.iter()
.map(|(name, record)| (name.as_str(), record)),
);
match (explicit, identified) {
(Some(arch), Ok(found)) if found.id() != arch.id() => Err(FocrError::Usage(format!(
"--model-id {:?} disagrees with the source: its tensor census is {:?}'s. Pass \
--model-id {:?}, or omit it to use the census",
arch.id(),
found.id(),
found.id()
))),
(Some(arch), Ok(_)) => Ok(arch),
(Some(arch), Err(FocrError::FormatMismatch(_))) if arch.tensor_census().is_none() => {
Ok(arch)
}
(Some(_), Err(error)) => Err(error),
(None, Ok(found)) => Ok(found),
(None, Err(FocrError::FormatMismatch(diff))) => Err(FocrError::Usage(format!(
"the source matches no registered model's tensor census; pass --model-id (see \
`focr models`). {diff}"
))),
(None, Err(error)) => Err(error),
}
}
const FOCRQ_PREAMBLE_LEN: usize = 6 + 4 + 1 + 32 + 8;
struct FocrqHeaderRead {
preamble: [u8; FOCRQ_PREAMBLE_LEN],
version: u32,
header: FocrqHeaderProbe,
payload_base: usize,
}
fn read_focrq_header(
reader: &mut impl Read,
prefix: [u8; 8],
file_len: usize,
) -> FocrResult<FocrqHeaderRead> {
let mut preamble = [0u8; FOCRQ_PREAMBLE_LEN];
preamble[..prefix.len()].copy_from_slice(&prefix);
reader
.read_exact(&mut preamble[prefix.len()..])
.map_err(|error| {
FocrError::FormatMismatch(format!(".focrq preamble truncated: {error}"))
})?;
let version = u32::from_le_bytes(
preamble[6..10]
.try_into()
.expect("fixed .focrq version slice"),
);
if version != weights::FOCRQ_FORMAT_VERSION {
return Err(FocrError::FormatMismatch(format!(
".focrq format version {version} is unsupported; this binary requires exactly {}",
weights::FOCRQ_FORMAT_VERSION,
)));
}
let header_len_u64 = u64::from_le_bytes(
preamble[43..51]
.try_into()
.expect("fixed .focrq header length slice"),
);
let header_len = usize::try_from(header_len_u64)
.map_err(|_| FocrError::FormatMismatch(".focrq header length exceeds usize".into()))?;
if header_len == 0 || header_len > MODEL_HEADER_VALIDATION_MAX_BYTES {
return Err(FocrError::FormatMismatch(format!(
".focrq header length {header_len} is outside the bounded validation limit"
)));
}
let payload_base = FOCRQ_PREAMBLE_LEN
.checked_add(header_len)
.ok_or_else(|| FocrError::FormatMismatch(".focrq header length overflows".into()))?;
if payload_base > file_len {
return Err(FocrError::FormatMismatch(
".focrq header overruns the file".into(),
));
}
let mut header_bytes = vec![0u8; header_len];
reader
.read_exact(&mut header_bytes)
.map_err(|error| FocrError::FormatMismatch(format!(".focrq header truncated: {error}")))?;
let header: FocrqHeaderProbe = serde_json::from_slice(&header_bytes).map_err(|error| {
FocrError::FormatMismatch(format!(".focrq header JSON invalid: {error}"))
})?;
Ok(FocrqHeaderRead {
preamble,
version,
header,
payload_base,
})
}
fn validate_model_header_from_reader(
mut reader: impl Read,
file_len: u64,
) -> FocrResult<&'static dyn model_arch::ModelArch> {
let file_len = usize::try_from(file_len).map_err(|_| {
FocrError::FormatMismatch("model file exceeds this platform's addressable size".into())
})?;
let mut prefix = [0u8; 8];
reader
.read_exact(&mut prefix)
.map_err(|error| FocrError::FormatMismatch(format!("model header truncated: {error}")))?;
if &prefix[..weights::FOCRQ_MAGIC.len()] == weights::FOCRQ_MAGIC {
let FocrqHeaderRead {
preamble,
version,
header,
payload_base,
} = read_focrq_header(&mut reader, prefix, file_len)?;
match header.format_version {
Some(header_version) if header_version == version => {}
Some(header_version) => {
return Err(FocrError::FormatMismatch(format!(
".focrq header format_version {header_version} disagrees with preamble {version}"
)));
}
None => {
return Err(FocrError::FormatMismatch(
".focrq header is missing required format_version".into(),
));
}
}
let preamble_arch_target = preamble[10];
if header.arch_target != preamble_arch_target {
return Err(FocrError::FormatMismatch(format!(
".focrq header arch_target {} disagrees with preamble {preamble_arch_target}",
header.arch_target
)));
}
weights::validate_arch_target(header.arch_target)?;
validate_header_source_sha256(&header.source_sha256)?;
let preamble_source_sha256 = lowercase_hex(&preamble[11..43]);
if header.source_sha256 != preamble_source_sha256 {
return Err(FocrError::FormatMismatch(format!(
".focrq header source_sha256 {} disagrees with preamble {preamble_source_sha256}",
header.source_sha256
)));
}
shards::validate_source_shards(header.provenance.as_ref(), &header.source_sha256)?;
if header.tensors.is_empty() {
return Err(FocrError::FormatMismatch(
".focrq tensor directory is empty; no native forward can use this artifact".into(),
));
}
let arch = compatible_arch(&header.model_id)?;
validate_header_license(&header.license_notice, arch.id())?;
let declared_recipe = header
.packing_manifest
.as_ref()
.and_then(|manifest| manifest.quant_recipe.as_deref());
validate_header_directory(&header.tensors, file_len - payload_base, header.arch_target)?;
if let Some(census) = arch.tensor_census() {
unlimited_ocr_census::validate_focrq_header(
census,
&header.tensors,
&header.source_sha256,
declared_recipe,
)?;
}
validate_unlimited_ocr_quant_records(
true,
arch.id(),
declared_recipe,
header
.tensors
.iter()
.map(|(name, record)| (name.as_str(), record.dtype)),
)?;
return Ok(arch);
}
let (header, payload_len) = read_safetensors_header_json(reader, prefix, file_len as u64)?;
validate_safetensors_header(&header, payload_len)
}
fn open_model_file(path: &Path) -> FocrResult<(std::fs::File, u64)> {
let file = std::fs::File::open(path).map_err(|error| {
FocrError::ModelNotFound(format!(
"cannot open model header at {}: {error}",
path.display()
))
})?;
let file_len = file.metadata().map_err(|error| {
FocrError::ModelNotFound(format!(
"cannot stat model artifact at {}: {error}",
path.display()
))
})?;
if !file_len.is_file() {
return Err(FocrError::FormatMismatch(format!(
"model artifact at {} is not a regular file",
path.display()
)));
}
Ok((file, file_len.len()))
}
fn validate_model_header_file(path: &Path) -> FocrResult<&'static dyn model_arch::ModelArch> {
if shards::is_index_path(path) {
return validate_sharded_model(path).map(|(arch, _)| arch);
}
let (file, file_len) = open_model_file(path)?;
validate_model_header_from_reader(&file, file_len)
}
#[must_use]
pub fn native_model_available(path: &Path) -> bool {
let Ok(resolved) = OcrModel::resolve_model(path) else {
return false;
};
validate_model_header_file(&resolved).is_ok()
}
#[must_use]
pub fn model_id_at(path: &Path) -> Option<&'static str> {
let resolved = OcrModel::resolve_model(path).ok()?;
validate_model_header_file(&resolved)
.ok()
.map(model_arch::ModelArch::id)
}
pub fn preflight_model(path: &Path) -> FocrResult<(PathBuf, &'static str)> {
let resolved = OcrModel::resolve_model(path)?;
let arch = validate_model_header_file(&resolved)?;
admit_for_inference(arch)?;
eager_sidecar_tokenizer(arch, &resolved)?;
Ok((resolved, arch.id()))
}
struct OpenedModel {
arch: &'static dyn model_arch::ModelArch,
tokenizer: Option<crate::tokenizer::Tokenizer>,
weights: Weights,
}
impl std::fmt::Debug for OpenedModel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OpenedModel")
.field("arch", &self.arch.id())
.field("tokenizer", &self.tokenizer.is_some())
.field("tensors", &self.weights.len())
.finish()
}
}
#[cfg(test)]
fn load_weights_from_resolved_model(resolved: &Path) -> FocrResult<OpenedModel> {
load_weights_from_resolved_model_after_validation(resolved, |_| Ok(()), || {})
}
fn load_weights_from_resolved_model_after_validation(
resolved: &Path,
admit: impl FnOnce(&'static dyn model_arch::ModelArch) -> FocrResult<()>,
after_validation: impl FnOnce(),
) -> FocrResult<OpenedModel> {
let (arch, tokenizer, weights) = if shards::is_index_path(resolved) {
let (arch, opened) = validate_sharded_model(resolved)?;
admit(arch)?;
let tokenizer = eager_sidecar_tokenizer(arch, resolved)?;
after_validation();
(arch, tokenizer, Weights::load_opened_shards(opened)?)
} else {
let (file, file_len) = open_model_file(resolved)?;
let arch = validate_model_header_from_reader(&file, file_len)?;
admit(arch)?;
let tokenizer = eager_sidecar_tokenizer(arch, resolved)?;
after_validation();
(arch, tokenizer, Weights::load_opened(file, resolved)?)
};
validate_unlimited_ocr_quant_recipe(&weights)?;
if weights.model_id() != arch.id() {
return Err(FocrError::FormatMismatch(format!(
"{}: the loaded weights identify as {:?} but the header probe resolved {:?}",
resolved.display(),
weights.model_id(),
arch.id()
)));
}
Ok(OpenedModel {
arch,
tokenizer,
weights,
})
}
fn admit_for_inference(arch: &'static dyn model_arch::ModelArch) -> FocrResult<()> {
let admission = lever_policy::admit_from_process(arch);
print_admission_warnings(&admission.warnings);
admission.verdict?;
check_entmax_domain(arch)?;
note_experimental(arch);
Ok(())
}
fn print_admission_warnings(warnings: &[RunWarning]) {
for warning in warnings {
crate::progress::stderr_message(format_args!(
"[focr] warning: {} — {}",
warning.message, warning.recovery
));
}
}
fn check_entmax_domain(arch: &dyn model_arch::ModelArch) -> FocrResult<()> {
if arch.decode_contract().pages.is_some() {
srswa::entmax_domain()
.as_ref()
.map_err(|why| FocrError::Usage(why.clone()))?;
}
Ok(())
}
fn note_experimental(arch: &dyn model_arch::ModelArch) {
if arch.maturity() == model_arch::Maturity::Experimental {
crate::progress::stderr_message(format_args!(
"[focr] note: {} is experimental: not yet certified against its reference",
arch.id()
));
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ForwardRoute {
UnlimitedV1,
UnlimitedV2,
GotOcr2,
SmolVlm2,
OneChart,
Tromr,
}
fn forward_route(arch: &dyn model_arch::ModelArch) -> FocrResult<ForwardRoute> {
use model_arch::{Decoder, UnlimitedVariant};
let route = match (arch.decoder(), arch.unlimited_variant()) {
(Decoder::DeepSeekV2MoeRswa, Some(UnlimitedVariant::V1Rswa)) => {
Some(ForwardRoute::UnlimitedV1)
}
(Decoder::DeepSeekV2MoeSrSwa, Some(UnlimitedVariant::V2SrSwa)) => {
OcrModel::ensure_arch_implemented(arch)?;
Some(ForwardRoute::UnlimitedV2)
}
(Decoder::Qwen2Dense, None) if arch.id() == "got-ocr2" => Some(ForwardRoute::GotOcr2),
(Decoder::LlamaDense, None) if arch.id() == "smolvlm2" => Some(ForwardRoute::SmolVlm2),
(Decoder::OptDense, None) if arch.id() == "onechart" => Some(ForwardRoute::OneChart),
(Decoder::Seq2SeqDense, None) if arch.id() == "tromr" => Some(ForwardRoute::Tromr),
(
Decoder::DeepSeekV2MoeRswa
| Decoder::DeepSeekV2MoeSrSwa
| Decoder::Qwen2Dense
| Decoder::LlamaDense
| Decoder::OptDense
| Decoder::Seq2SeqDense,
_,
) => None,
};
if let Some(route) = route {
return Ok(route);
}
OcrModel::ensure_arch_implemented(arch)?;
Err(FocrError::NotImplemented(format!(
"model architecture '{}' has no forward wired in this build",
arch.id()
)))
}
#[must_use]
pub fn crop_figure_refs(
figures: &[postprocess::FigureRef],
source: &image::DynamicImage,
) -> Vec<ExtractedFigure> {
let iw = i64::from(source.width());
let ih = i64::from(source.height());
figures
.iter()
.filter_map(|fr| {
let &[x1, y1, x2, y2] = fr.boxes.first()?;
let cx1 = x1.min(x2).clamp(0, iw);
let cy1 = y1.min(y2).clamp(0, ih);
let cx2 = x1.max(x2).clamp(0, iw);
let cy2 = y1.max(y2).clamp(0, ih);
let w = u32::try_from(cx2 - cx1).unwrap_or(0);
let h = u32::try_from(cy2 - cy1).unwrap_or(0);
if w == 0 || h == 0 {
return None; }
#[allow(clippy::cast_sign_loss)] let image = source.crop_imm(cx1 as u32, cy1 as u32, w, h);
Some(ExtractedFigure {
index: fr.index,
label: fr.label.clone(),
bbox: [cx1, cy1, cx2, cy2],
markdown_ref: fr.markdown_ref.clone(),
image,
})
})
.collect()
}
fn millis(elapsed: std::time::Duration) -> u64 {
u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)
}
fn multi_page_route(arch: &dyn model_arch::ModelArch) -> FocrResult<()> {
match arch.unlimited_variant() {
Some(model_arch::UnlimitedVariant::V1Rswa | model_arch::UnlimitedVariant::V2SrSwa) => {
OcrModel::ensure_arch_implemented(arch)
}
None => Err(FocrError::NotImplemented(format!(
"multi-page cross-page parsing (infer_multi) is the Unlimited-OCR contract; model \
{:?} parses pages independently (use the plain multi-input/batch path)",
arch.id()
))),
}
}
pub fn load_if_policed(spec: &Path) -> FocrResult<Option<Arc<OcrModel>>> {
let Ok(resolved) = OcrModel::resolve_model(spec) else {
return Ok(None);
};
match validate_model_header_file(&resolved) {
Ok(arch) if lever_policy::policy_for(arch).is_some() => OcrModel::load(&resolved).map(Some),
Ok(_) | Err(_) => Ok(None),
}
}
pub struct ServedAdmission {
arch: &'static dyn model_arch::ModelArch,
pub warnings: Vec<RunWarning>,
}
impl ServedAdmission {
pub fn announce(&self) {
print_admission_warnings(&self.warnings);
note_experimental(self.arch);
}
}
#[must_use]
pub fn resident_admission(path: &Path) -> Option<ServedAdmission> {
let arch = validate_model_header_file(path).ok()?;
let admission = lever_policy::admit_from_process(arch);
admission.verdict.ok()?;
check_entmax_domain(arch).ok()?;
Some(ServedAdmission {
arch,
warnings: admission.warnings,
})
}
fn pins_tokenizer(arch: &dyn model_arch::ModelArch) -> bool {
arch.tokenizer_sha256().is_some() || !arch.required_token_ids().is_empty()
}
fn checked_tokenizer(
arch: &dyn model_arch::ModelArch,
bytes: &[u8],
origin: &str,
) -> FocrResult<crate::tokenizer::Tokenizer> {
use sha2::Digest as _;
let id = arch.id();
let remedy = match arch.tokenizer_source() {
Some(source) => format!("fetch the pinned file from {source}"),
None => format!("re-install its sidecars with `focr pull {id}`"),
};
if let Some(expected) = arch.tokenizer_sha256() {
let found = format!("{:x}", sha2::Sha256::digest(bytes));
if found != expected {
return Err(FocrError::FormatMismatch(format!(
"{origin} has sha256 {found}, but {id} requires {expected}; {remedy}"
)));
}
}
let tokenizer = crate::tokenizer::Tokenizer::from_json_bytes(bytes)?;
for &(token_id, content) in arch.required_token_ids() {
let found = tokenizer.id_to_token(token_id);
if found != Some(content) {
return Err(FocrError::FormatMismatch(format!(
"{origin} maps token id {token_id} to {found:?}, but {id} requires {content:?}; \
{remedy}"
)));
}
}
Ok(tokenizer)
}
fn eager_sidecar_tokenizer(
arch: &dyn model_arch::ModelArch,
model_path: &Path,
) -> FocrResult<Option<crate::tokenizer::Tokenizer>> {
if !pins_tokenizer(arch) {
return Ok(None);
}
let path = model_path
.parent()
.unwrap_or_else(|| Path::new("."))
.join("tokenizer.json");
match std::fs::read(&path) {
Ok(bytes) => checked_tokenizer(arch, &bytes, &path.display().to_string()).map(Some),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(FocrError::ModelNotFound(format!(
"tokenizer.json at {}: {error}",
path.display()
))),
}
}
#[cfg(any(feature = "native", test))]
fn focrq_declared_arch(path: &Path) -> FocrResult<&'static dyn model_arch::ModelArch> {
let (mut file, file_len) = open_model_file(path)?;
let file_len = usize::try_from(file_len).map_err(|_| {
FocrError::FormatMismatch("model file exceeds this platform's addressable size".into())
})?;
let mut prefix = [0u8; 8];
file.read_exact(&mut prefix)
.map_err(|error| FocrError::FormatMismatch(format!("model header truncated: {error}")))?;
if &prefix[..weights::FOCRQ_MAGIC.len()] != weights::FOCRQ_MAGIC {
return Err(FocrError::FormatMismatch(format!(
"{} is not a .focrq artifact",
path.display()
)));
}
let read = read_focrq_header(&mut file, prefix, file_len)?;
compatible_arch(&read.header.model_id)
}
#[cfg(any(feature = "native", test))]
#[derive(Debug)]
pub(crate) struct SidecarFault {
pub model_id: &'static str,
pub tokenizer_path: PathBuf,
pub refusal: Option<String>,
pub owner: Option<&'static str>,
pub recommended_command: String,
}
#[cfg(any(feature = "native", test))]
pub(crate) fn sidecar_tokenizer_fault(model_path: &Path) -> Option<SidecarFault> {
use sha2::Digest as _;
let arch = focrq_declared_arch(model_path).ok()?;
if !pins_tokenizer(arch) {
return None;
}
let tokenizer_path = model_path
.parent()
.unwrap_or_else(|| Path::new("."))
.join("tokenizer.json");
let (refusal, owner) = match std::fs::read(&tokenizer_path) {
Ok(bytes) => {
let origin = tokenizer_path.display().to_string();
let refusal = match checked_tokenizer(arch, &bytes, &origin).err()? {
FocrError::FormatMismatch(detail) => detail,
other => other.to_string(),
};
let owner = tokenizer_owner(&format!("{:x}", sha2::Sha256::digest(&bytes)));
(Some(refusal), owner.filter(|&owner| owner != arch.id()))
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => (None, None),
Err(_) => return None,
};
let id = arch.id();
Some(SidecarFault {
model_id: id,
tokenizer_path,
refusal,
owner,
recommended_command: match arch.tokenizer_source() {
Some(source) => format!("fetch the pinned tokenizer.json from {source}"),
None => format!("focr pull {id}"),
},
})
}
#[cfg(any(feature = "native", test))]
fn tokenizer_owner(sha256: &str) -> Option<&'static str> {
model_arch::registry()
.iter()
.find(|arch| arch.tokenizer_sha256() == Some(sha256))
.map(|arch| arch.id())
}
#[cfg(any(feature = "native", test))]
#[derive(Debug)]
pub(crate) struct InstalledModelFault {
pub artifact: Option<PathBuf>,
pub refusal: String,
}
#[cfg(any(feature = "native", test))]
pub(crate) fn installed_model_fault(models: &Path, id: &str) -> Option<InstalledModelFault> {
let arch = model_arch::arch_by_id(id)?;
let resolved = match resolve_model_from_search_dirs_with_quant(
Path::new(arch.id()),
&cache_search_dirs(models.to_path_buf()),
model_quant_preference(),
) {
Ok(resolved) => resolved,
Err(error) => {
return Some(InstalledModelFault {
artifact: None,
refusal: error.to_string(),
});
}
};
let refusal = validate_model_header_file(&resolved).err()?.to_string();
Some(InstalledModelFault {
artifact: Some(resolved),
refusal,
})
}
fn validate_unlimited_ocr_quant_recipe(weights: &Weights) -> FocrResult<()> {
validate_unlimited_ocr_quant_records(
weights.is_focrq(),
weights.model_id(),
weights.quant_recipe(),
weights.names().map(|name| {
let dtype = weights
.record(name)
.expect("name iterator and tensor directory must agree")
.dtype;
(name, dtype)
}),
)?;
unlimited_ocr_census::validate_loaded_weights(weights)
}
fn validate_unlimited_ocr_quant_records<'a>(
is_focrq: bool,
model_id: &str,
declared_recipe: Option<&str>,
records: impl IntoIterator<Item = (&'a str, DType)>,
) -> FocrResult<()> {
if !is_focrq {
return Ok(());
}
let Some(variant) = model_arch::arch_by_id(model_id).and_then(|arch| arch.unlimited_variant())
else {
return Ok(());
};
let v2 = variant == model_arch::UnlimitedVariant::V2SrSwa;
if v2 && declared_recipe != Some(crate::quant::convert::UNLIMITED_OCR_2_INT8_RECIPE_ID) {
return Err(FocrError::FormatMismatch(format!(
"{model_id} .focrq packing_manifest.quant_recipe must be exactly {:?}, got \
{declared_recipe:?}; {model_id} has no int4 or wasm recipe",
crate::quant::convert::UNLIMITED_OCR_2_INT8_RECIPE_ID
)));
}
let wasm_recipe =
!v2 && declared_recipe == Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID);
let recipe = Recipe::validated_default();
let mut violations = Vec::new();
for (name, dtype) in records {
let expected: (&str, bool) = if wasm_recipe {
match crate::quant::recipe::classify_wasm_experts_int4(name) {
crate::quant::recipe::WasmInt4Policy::ExpertInt4 => {
("QInt4PerGroup", dtype == DType::QInt4PerGroup)
}
crate::quant::recipe::WasmInt4Policy::Int8 => {
("QInt8PerChan", dtype == DType::QInt8PerChan)
}
crate::quant::recipe::WasmInt4Policy::KeepHighPrecision => {
("BF16 or F32", matches!(dtype, DType::BF16 | DType::F32))
}
}
} else if recipe.is_quantized(name) {
("QInt8PerChan", dtype == DType::QInt8PerChan)
} else {
("BF16 or F32", matches!(dtype, DType::BF16 | DType::F32))
};
let (expected, valid) = expected;
if !valid {
violations.push(format!("{name} is {dtype:?}, expected {expected}"));
}
}
if violations.is_empty() {
return Ok(());
}
let shown = violations
.iter()
.take(8)
.cloned()
.collect::<Vec<_>>()
.join("; ");
let omitted = violations.len().saturating_sub(8);
let suffix = if omitted == 0 {
String::new()
} else {
format!("; and {omitted} more")
};
let (model, recipe_id) = if v2 {
(
"Unlimited-OCR-2",
crate::quant::convert::UNLIMITED_OCR_2_INT8_RECIPE_ID,
)
} else if wasm_recipe {
(
"Unlimited-OCR",
crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID,
)
} else {
(
"Unlimited-OCR",
crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID,
)
};
Err(FocrError::FormatMismatch(format!(
"{model} .focrq violates quant recipe {recipe_id}: {shown}{suffix}. Re-convert the \
original safetensors with this build; legacy full-int8 artifacts are not accepted",
)))
}
pub enum EmbedTable<'w> {
F32(Mat),
QInt8 {
data: &'w [u8],
scales: Vec<f32>,
rows: usize,
cols: usize,
},
}
impl EmbedTable<'_> {
pub fn rows(&self) -> usize {
match self {
EmbedTable::F32(m) => m.rows,
EmbedTable::QInt8 { rows, .. } => *rows,
}
}
pub fn cols(&self) -> usize {
match self {
EmbedTable::F32(m) => m.cols,
EmbedTable::QInt8 { cols, .. } => *cols,
}
}
#[must_use]
pub fn row_f32(&self, idx: usize) -> Vec<f32> {
match self {
EmbedTable::F32(m) => m.data[idx * m.cols..(idx + 1) * m.cols].to_vec(),
EmbedTable::QInt8 {
data, scales, cols, ..
} => {
let scale = scales[idx];
data[idx * cols..(idx + 1) * cols]
.iter()
.map(|&b| f32::from(b as i8) * scale)
.collect()
}
}
}
pub fn embed_ids(&self, ids: &[u32]) -> FocrResult<Mat> {
match self {
EmbedTable::F32(m) => decoder::embed_tokens(&m.data, m.rows, m.cols, ids),
EmbedTable::QInt8 { rows, cols, .. } => {
let mut out = Vec::with_capacity(ids.len() * cols);
for &id in ids {
let row = id as usize;
if row >= *rows {
return Err(FocrError::Other(anyhow::anyhow!(
"embed_tokens: id {row} out of range (vocab {rows})"
)));
}
out.extend_from_slice(&self.row_f32(row));
}
Ok(Mat::from_vec(ids.len(), *cols, out))
}
}
}
}
const EMBED_TOKENS: &str = "model.embed_tokens.weight";
fn embed_table_from(weights: &Weights) -> FocrResult<EmbedTable<'_>> {
const NAME: &str = EMBED_TOKENS;
if weights.arch_target() == 0
&& matches!(
weights.record(NAME).map(|rec| rec.dtype),
Some(DType::QInt8PerChan)
)
{
let view = weights.tensor(NAME)?;
let [rows, cols] = view.shape else {
return Err(FocrError::FormatMismatch(format!(
"tensor {NAME:?} has rank {}; expected 2 ([vocab, hidden])",
view.shape.len()
)));
};
let (rows, cols) = (*rows, *cols);
let expected = rows
.checked_mul(cols)
.ok_or_else(|| FocrError::FormatMismatch(format!("tensor {NAME:?} shape overflows")))?;
if view.data.len() != expected {
return Err(FocrError::FormatMismatch(format!(
"QInt8 tensor {NAME:?}: {} payload bytes != vocab*hidden {expected}",
view.data.len()
)));
}
if view.scales.len() != rows * 4 {
return Err(FocrError::FormatMismatch(format!(
"QInt8 tensor {NAME:?}: {} scale bytes != vocab*f32 {}",
view.scales.len(),
rows * 4
)));
}
let scales: Vec<f32> = view
.scales
.as_chunks::<4>()
.0
.iter()
.map(|c| f32::from_le_bytes(*c))
.collect();
return Ok(EmbedTable::QInt8 {
data: view.data,
scales,
rows,
cols,
});
}
Ok(EmbedTable::F32(weights.mat(NAME)?))
}
struct PagePrefill {
prefill_len: usize,
prompt_ids: Vec<u32>,
last_hidden: Mat,
caches: Vec<rswa::RingCache>,
image_w: u32,
image_h: u32,
}
impl OcrModel {
pub fn resolve_model(path: &Path) -> FocrResult<PathBuf> {
if registry_spec(path).is_some() {
return resolve_model_from_search_dirs_with_quant(
path,
&model_search_dirs(),
model_quant_preference(),
);
}
if let Some(resolved) = resolve_existing_model_artifact(path) {
return Ok(resolved);
}
if path.is_dir() {
return Err(FocrError::ModelNotFound(format!(
"no model artifact at {} (expected {} or a single-shard checkpoint ({}) inside \
the safetensors directory)",
path.display(),
shards::SHARD_INDEX_NAME,
raw_single_shard_names().collect::<Vec<_>>().join(", ")
)));
}
if is_searchable_model_spec(path) {
resolve_model_from_search_dirs_with_quant(
path,
&model_search_dirs(),
model_quant_preference(),
)
} else {
Err(FocrError::ModelNotFound(format!(
"no model artifact at {} (resolver lands in Phase 0/1, bd-223.7)",
path.display()
)))
}
}
pub fn load(path: &Path) -> FocrResult<Arc<Self>> {
Self::load_observed(path, || {})
}
pub(crate) fn load_observed(path: &Path, before_read: impl FnOnce()) -> FocrResult<Arc<Self>> {
let resolved = Self::resolve_model(path)?;
let _load_permit = model_load_admission().acquire();
{
let guard = model_cache_guard()?;
if let Some((cached_path, weak)) = guard.as_ref()
&& *cached_path == resolved
&& let Some(strong) = weak.upgrade()
{
lever_policy::admit_from_process(strong.arch).verdict?;
return Ok(strong);
}
}
let opened = load_weights_from_resolved_model_after_validation(
&resolved,
admit_for_inference,
before_read,
)?;
let model = Arc::new(Self::assemble(
resolved.clone(),
SidecarBundle::default(),
opened.arch,
opened.weights,
opened.tokenizer,
));
let mut guard = model_cache_guard()?;
*guard = Some((resolved, Arc::downgrade(&model)));
Ok(model)
}
pub fn from_weights(weights: Weights, sidecars: SidecarBundle) -> FocrResult<Arc<Self>> {
let arch = match model_arch::arch_by_id(weights.model_id()) {
Some(arch) => arch,
None => {
let records: Vec<(&str, &weights::TensorRecord)> = weights
.names()
.filter_map(|name| weights.record(name).map(|record| (name, record)))
.collect();
unlimited_ocr_census::identify_raw(records.iter().copied())?
}
};
admit_for_inference(arch)?;
let tokenizer = match &sidecars.tokenizer_json {
Some(bytes) if pins_tokenizer(arch) => Some(checked_tokenizer(
arch,
bytes,
"the in-memory tokenizer.json sidecar",
)?),
_ => None,
};
validate_unlimited_ocr_quant_recipe(&weights)?;
Ok(Arc::new(Self::assemble(
PathBuf::from("<in-memory>"),
sidecars,
arch,
weights,
tokenizer,
)))
}
fn assemble(
path: PathBuf,
sidecars: SidecarBundle,
arch: &'static dyn model_arch::ModelArch,
weights: Weights,
tokenizer: Option<crate::tokenizer::Tokenizer>,
) -> Self {
memory::log_peak_rss("weights loaded");
Self {
path,
sidecars,
arch,
weights,
decode_params: decode_params_for(arch, decode_overrides()),
decoder_cache_i8: FallibleOnce::new(),
decoder_cache: FallibleOnce::new(),
decoder_cache_f32: FallibleOnce::new(),
clip_cache: FallibleOnce::new(),
unlimited_vision: FallibleOnce::new(),
got_statics: FallibleOnce::new(),
onechart_statics: FallibleOnce::new(),
smol_statics: FallibleOnce::new(),
tokenizer: FallibleOnce::prefilled(tokenizer),
got_tokenizer: FallibleOnce::new(),
tromr_tokenizer: FallibleOnce::new(),
music_meta: std::sync::Mutex::new(None),
chart_meta: std::sync::Mutex::new(None),
last_decode: std::sync::Mutex::new(None),
batch_decodes: std::sync::Mutex::new(Vec::new()),
batch_charts: std::sync::Mutex::new(Vec::new()),
}
}
#[must_use]
pub fn decode_params(&self) -> &DecodeParams {
&self.decode_params
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn forward(&self, image_path: &Path) -> FocrResult<(String, u32, u32)> {
let _admission = forward_admission().acquire();
match forward_route(self.arch())? {
ForwardRoute::GotOcr2 => {
return self.forward_got(&preprocess::decode_path(image_path)?);
}
ForwardRoute::SmolVlm2 => {
return self.forward_smolvlm2(&preprocess::decode_path(image_path)?);
}
ForwardRoute::OneChart => {
return self.forward_onechart(&preprocess::decode_path(image_path)?);
}
ForwardRoute::Tromr => {
return self.forward_tromr(&preprocess::decode_path(image_path)?);
}
ForwardRoute::UnlimitedV1 | ForwardRoute::UnlimitedV2 => {}
}
let t = Instant::now();
progress::emit("preprocess", 0, 0);
let pre = preprocess::preprocess_image(
image_path,
preprocess_mode(),
preprocess_profile(self.arch()),
)?;
progress::emit("preprocess", 1, 1);
timing_log(&format!("preprocess {:.2}s", t.elapsed().as_secs_f64()));
self.forward_pre(pre)
}
pub fn forward_dynamic(&self, img: image::DynamicImage) -> FocrResult<(String, u32, u32)> {
let _admission = forward_admission().acquire();
match forward_route(self.arch())? {
ForwardRoute::GotOcr2 => return self.forward_got(&img),
ForwardRoute::SmolVlm2 => return self.forward_smolvlm2(&img),
ForwardRoute::OneChart => return self.forward_onechart(&img),
ForwardRoute::Tromr => return self.forward_tromr(&img),
ForwardRoute::UnlimitedV1 | ForwardRoute::UnlimitedV2 => {}
}
let t = Instant::now();
progress::emit("preprocess", 0, 0);
let pre = preprocess::preprocess_dynamic(
img,
preprocess_mode(),
preprocess_profile(self.arch()),
)?;
progress::emit("preprocess", 1, 1);
timing_log(&format!("preprocess {:.2}s", t.elapsed().as_secs_f64()));
self.forward_pre(pre)
}
fn forward_got(&self, img: &image::DynamicImage) -> FocrResult<(String, u32, u32)> {
use image::GenericImageView;
let t = Instant::now();
let (w, h) = img.dimensions();
let tk = self.got_tokenizer()?;
let format = got_format_requested();
let max_new = self.decode_params.max_length.min(got::MAX_NEW_TOKENS);
let region = got_region_prefix(w, h);
let image = got_input(img);
let text = got::recognize(
&self.weights,
self.got_statics()?,
tk,
&image,
max_new,
format,
®ion,
)?;
timing_log(&format!("got forward {:.2}s", t.elapsed().as_secs_f64()));
Ok((text, w, h))
}
fn forward_smolvlm2(&self, img: &image::DynamicImage) -> FocrResult<(String, u32, u32)> {
use image::GenericImageView;
let t = Instant::now();
let (w, h) = img.dimensions();
let tk = self.tokenizer()?;
let question = smolvlm2_question();
let max_new = self.decode_params.max_length;
let text = smolvlm2::recognize(
&self.weights,
self.smol_statics()?,
tk,
img,
&question,
max_new,
)?;
timing_log(&format!(
"smolvlm2 forward {:.2}s",
t.elapsed().as_secs_f64()
));
Ok((text, w, h))
}
fn forward_onechart(&self, img: &image::DynamicImage) -> FocrResult<(String, u32, u32)> {
use image::GenericImageView;
let t = Instant::now();
let (w, h) = img.dimensions();
let tk = self.tokenizer()?;
let max_new = self.decode_params.max_length;
let res = onechart::recognize(&self.weights, self.onechart_statics()?, tk, img, max_new)?;
timing_log(&format!(
"onechart forward {:.2}s (reliable_distance {:?} reliable {:?})",
t.elapsed().as_secs_f64(),
res.reliable_distance,
res.reliable
));
if let Ok(mut slot) = self.chart_meta.lock()
&& slot.as_ref().is_none_or(|held| held.reliable == Some(true))
{
*slot = Some(ChartPageMeta::from_result(&res));
}
Ok((res.json_text, w, h))
}
pub fn take_music_meta(&self) -> Option<MusicPageMeta> {
self.music_meta.lock().ok().and_then(|mut slot| slot.take())
}
pub fn take_chart_meta(&self) -> Option<ChartPageMeta> {
self.chart_meta.lock().ok().and_then(|mut slot| slot.take())
}
pub fn take_last_decode(&self) -> Option<DecodeReport> {
self.last_decode
.lock()
.ok()
.and_then(|mut slot| slot.take())
}
pub fn take_batch_decodes(&self) -> Vec<Option<DecodeReport>> {
self.batch_decodes
.lock()
.map(|mut slot| std::mem::take(&mut *slot))
.unwrap_or_default()
}
fn set_batch_decodes(&self, reports: Vec<Option<DecodeReport>>) {
if let Ok(mut slot) = self.batch_decodes.lock() {
*slot = reports;
}
}
pub fn take_batch_charts(&self) -> Vec<Option<ChartPageMeta>> {
self.batch_charts
.lock()
.map(|mut slot| std::mem::take(&mut *slot))
.unwrap_or_default()
}
fn set_batch_charts(&self, charts: Vec<Option<ChartPageMeta>>) {
if let Ok(mut slot) = self.batch_charts.lock() {
*slot = charts;
}
}
fn forward_tromr(&self, img: &image::DynamicImage) -> FocrResult<(String, u32, u32)> {
use image::GenericImageView;
let t = Instant::now();
let (w, h) = img.dimensions();
let tk = self.tromr_tokenizer()?;
let page = tromr::recognize_page(&self.weights, tk, img)?;
let total = page.staves.len() + page.skips.len();
for skip in &page.skips {
crate::progress::stderr_message(format_args!(
"[focr] staff {}/{} skipped: {} (bbox x{} y{} w{} h{})",
skip.index + 1,
total,
skip.reason,
skip.bbox.0,
skip.bbox.1,
skip.bbox.2,
skip.bbox.3
));
}
timing_log(&format!(
"tromr forward {:.2}s ({}/{} staves recognized, semantic {} chars total)",
t.elapsed().as_secs_f64(),
page.staves.len(),
total,
page.staves
.iter()
.map(|(_, r, _)| r.semantic.len())
.sum::<usize>()
));
let semantics: Vec<String> = page
.staves
.iter()
.map(|(_, r, _)| r.semantic.clone())
.collect();
let warnings = tromr::sanity_warnings(&semantics);
for w in &warnings {
timing_log(&format!(
" tromr.sanity {} part {} measure {}: {}",
w.kind, w.part, w.measure, w.detail
));
}
if let Ok(mut slot) = self.music_meta.lock() {
*slot = Some(MusicPageMeta {
staves: page.staves.iter().map(|(i, _, b)| (*i, *b)).collect(),
skips: page.skips.clone(),
warnings,
});
}
let xml = if page.staves.len() == 1 {
page.staves
.into_iter()
.next()
.expect("one staff")
.1
.musicxml
} else {
let semantics: Vec<String> = page
.staves
.into_iter()
.map(|(_, r, _)| r.semantic)
.collect();
tromr::staves_to_musicxml(&semantics)?
};
Ok((xml, w, h))
}
fn tromr_tokenizer(&self) -> FocrResult<&crate::tokenizer::music::MusicTokenizer> {
self.tromr_tokenizer.get_or_try_init(|| {
if let Some([rhythm, pitch, lift, note]) = &self.sidecars.music_tables {
return crate::tokenizer::music::MusicTokenizer::from_json_tables(
[rhythm, pitch, lift, note],
[
"tokenizer_rhythm.json (in-memory)",
"tokenizer_pitch.json (in-memory)",
"tokenizer_lift.json (in-memory)",
"tokenizer_note.json (in-memory)",
],
);
}
let dir = self.path.parent().unwrap_or_else(|| Path::new("."));
crate::tokenizer::music::MusicTokenizer::from_dir(dir)
})
}
fn got_tokenizer(&self) -> FocrResult<&crate::tokenizer::tiktoken::Tiktoken> {
self.got_tokenizer.get_or_try_init(|| {
if let Some(bytes) = &self.sidecars.qwen_tiktoken {
return crate::tokenizer::tiktoken::Tiktoken::from_qwen_tiktoken(bytes);
}
let dir = self.path.parent().unwrap_or_else(|| Path::new("."));
let path = dir.join("qwen.tiktoken");
let bytes = std::fs::read(&path).map_err(|e| {
FocrError::ModelNotFound(format!(
"GOT-OCR2 tokenizer qwen.tiktoken not found beside the model at {}: {e}",
path.display()
))
})?;
crate::tokenizer::tiktoken::Tiktoken::from_qwen_tiktoken(&bytes)
})
}
#[must_use]
pub fn arch(&self) -> &'static dyn model_arch::ModelArch {
self.arch
}
#[must_use]
pub fn identity(&self) -> ModelIdentity {
let sha = self.weights.source_sha256();
ModelIdentity {
model_id: self.arch.id(),
source_sha256: (!sha.is_empty()).then(|| sha.to_owned()),
}
}
fn ensure_arch_implemented(arch: &dyn model_arch::ModelArch) -> FocrResult<()> {
if arch.implemented() {
Ok(())
} else {
Err(FocrError::NotImplemented(format!(
"model architecture '{}' ({}) forward is not yet implemented \
(franken_ocr model zoo, epic bd-3jo6)",
arch.id(),
arch.display_name()
)))
}
}
fn forward_pre(&self, pre: Preprocessed) -> FocrResult<(String, u32, u32)> {
Self::ensure_arch_implemented(self.arch())?;
let (image_w, image_h) = Self::image_dims(&pre);
if self.arch().decode_contract().pages.is_some() {
let split = self
.decode_document(std::slice::from_ref(&pre), None)?
.split;
progress::emit("postprocess", 0, 0);
let body = split.bodies.into_iter().next().unwrap_or_default();
return Ok((body, image_w, image_h));
}
let tv = Instant::now();
let vision_features = self.vision_tower(&pre)?;
timing_log(&format!("vision_tower {:.2}s", tv.elapsed().as_secs_f64()));
let (inputs_embeds, prompt_ids) = self.build_inputs_embeds(&pre, &vision_features)?;
let generated = self.generate(inputs_embeds, &prompt_ids)?;
progress::emit("postprocess", 0, 0);
let decoded = self.tokenizer()?.decode(&generated)?;
Ok((decoded, image_w, image_h))
}
pub fn recognize(&self, image_path: &Path) -> FocrResult<String> {
let (decoded, image_w, image_h) = self.forward(image_path)?;
postprocess::finalize(&decoded, image_w, image_h)
}
pub fn recognize_dynamic(&self, img: image::DynamicImage) -> FocrResult<String> {
let (decoded, image_w, image_h) = self.forward_dynamic(img)?;
postprocess::finalize(&decoded, image_w, image_h)
}
pub fn recognize_with_layout(&self, image_path: &Path) -> FocrResult<RecognizedDocument> {
let (decoded, image_w, image_h) = self.forward(image_path)?;
Self::finalize_document(&decoded, image_w, image_h)
}
pub fn recognize_dynamic_with_layout(
&self,
img: image::DynamicImage,
) -> FocrResult<RecognizedDocument> {
let (decoded, image_w, image_h) = self.forward_dynamic(img)?;
Self::finalize_document(&decoded, image_w, image_h)
}
fn finalize_document(
decoded: &str,
image_w: u32,
image_h: u32,
) -> FocrResult<RecognizedDocument> {
Ok(RecognizedDocument {
markdown: postprocess::finalize(decoded, image_w, image_h)?,
raw: postprocess::strip_eos(decoded),
layout: postprocess::parse_layout(decoded, image_w, image_h),
})
}
pub fn recognize_with_figures(
&self,
image_path: &Path,
) -> FocrResult<(RecognizedDocument, Vec<ExtractedFigure>)> {
let (decoded, image_w, image_h) = self.forward(image_path)?;
let document = Self::finalize_document(&decoded, image_w, image_h)?;
let figures = if postprocess::figure_refs(&decoded, image_w, image_h, "").is_empty() {
Vec::new()
} else {
let source = preprocess::decode_path(image_path)?;
Self::crop_figures(&decoded, &source, image_w, image_h, "")
};
Ok((document, figures))
}
pub fn recognize_dynamic_with_figures(
&self,
img: image::DynamicImage,
) -> FocrResult<(RecognizedDocument, Vec<ExtractedFigure>)> {
let source = img.clone();
let (decoded, image_w, image_h) = self.forward_dynamic(img)?;
let document = Self::finalize_document(&decoded, image_w, image_h)?;
let figures = Self::crop_figures(&decoded, &source, image_w, image_h, "");
Ok((document, figures))
}
fn crop_figures(
decoded: &str,
source: &image::DynamicImage,
image_w: u32,
image_h: u32,
img_base: &str,
) -> Vec<ExtractedFigure> {
crop_figure_refs(
&postprocess::figure_refs(decoded, image_w, image_h, img_base),
source,
)
}
pub fn recognize_document(
&self,
source: &mut crate::page_source::PageSource,
options: &DocumentOptions,
on_item: &mut dyn FnMut(&crate::page_source::PageItem),
on_event: Option<DocumentSink<'_>>,
) -> FocrResult<DocumentResult> {
multi_page_route(self.arch())?;
let plan = options.plan(
self.arch().id(),
self.decodes_windows(),
source.len(),
|| self.fitting_page_window(),
)?;
let (mut run, resumed) = match (plan, &options.run) {
(DocumentPlan::Windows(window), Some(files)) => {
let key = self.resume_key(window, &files.key)?;
let (run, resumed) = checkpoint::DocumentRun::open(files, key, options.resume)?;
(Some(run), resumed)
}
_ => (None, None),
};
let resumed_from = resumed.as_ref().map(|r| r.from.next_page() + 1);
let recompute_from = resumed
.as_ref()
.filter(|r| r.recomputed)
.map(|r| r.from.next_page() + 1);
let read_before = resumed
.as_ref()
.map_or_else(Vec::new, checkpoint::Resumed::read);
if let Some(last) = read_before.last() {
let map = read_before
.iter()
.flat_map(checkpoint::ReadPage::map_slots)
.collect();
source.seek(last.walk, map)?;
}
let mut skipped: Vec<crate::page_source::SkippedPage> = read_before
.iter()
.flat_map(|page| page.skipped.iter().cloned())
.collect();
let read = std::cell::RefCell::new(read_before);
let mut skipped_since_read = Vec::new();
let mut first_error: Option<FocrError> = None;
let mut pages = std::iter::from_fn(|| {
for item in source.by_ref() {
on_item(&item);
match item.raster {
Ok(img) => {
let origin = PageOrigin {
source_page: item.source_page,
half: item.half,
};
let pre = self.document_page(img);
if let Ok(pre) = &pre {
read.borrow_mut().push(checkpoint::ReadPage {
origin,
size: pre.original_size,
walk: item.walk,
skipped: std::mem::take(&mut skipped_since_read),
});
}
return Some(pre);
}
Err(e) => {
let page = crate::page_source::SkippedPage {
page: item.source_page,
reason: e.to_string(),
};
skipped_since_read.push(page.clone());
skipped.push(page);
first_error.get_or_insert(e);
}
}
}
None
});
let mut on_event = on_event;
let parsed = match plan {
DocumentPlan::OnePass => {
DocumentParse::OnePass(pages.by_ref().collect::<FocrResult<Vec<_>>>()?)
}
DocumentPlan::Windows(window) => {
let sink = on_event
.as_mut()
.map(|sink| &mut **sink as DocumentSink<'_>);
self.parse_windowed(&mut pages, window, &read, resumed, run.as_mut(), sink)?
}
};
let (document, windows) = match parsed {
DocumentParse::OnePass(pres) => {
if pres.is_empty() {
return Err(first_error.unwrap_or_else(|| {
FocrError::InputDecode("the page source yielded no pages".into())
}));
}
options.plan(self.arch().id(), self.decodes_windows(), pres.len(), || {
self.fitting_page_window()
})?;
let origins: Vec<PageOrigin> = read.borrow().iter().map(|p| p.origin).collect();
(self.parse_document(pres, &origins, on_event)?, Vec::new())
}
DocumentParse::Windowed(pages, windows) => {
let unread = source.unread();
if unread > 0
&& let Ok(mut slot) = self.last_decode.lock()
&& let Some(bodies) = slot.as_mut().and_then(|r| r.pages.as_mut())
{
bodies.expected += unread;
}
(pages, windows)
}
};
let report = self.last_decode.lock().ok().and_then(|slot| slot.clone());
let (
DocumentPages {
markdown,
raw,
pages,
},
reread,
) = if options.reread_capped {
let reread = reread_capped_pages(document, |page| self.reread_page(source, page));
if let Ok(mut slot) = self.last_decode.lock() {
slot.clone_from(&report);
}
reread
} else {
(document, None)
};
let pages_read = read.borrow().len();
timing_log(&format!(
"document: {pages_read} pages read, {} skipped, {} windows, {} vision encodes, {} \
fallback recomputes",
skipped.len(),
windows.len(),
report
.as_ref()
.and_then(|r| r.document.as_ref())
.map_or(pages_read, |stats| stats.vision_encodes),
windows
.iter()
.filter(|w| w.fallback_reason.is_some())
.count(),
));
let mut warnings = source.warnings();
warnings.extend(report.as_ref().map(decode_warnings).unwrap_or_default());
warnings.extend(reread);
warnings.extend(recompute_from.map(resume_recompute_warning));
warnings.extend(
run.as_ref()
.and_then(checkpoint::DocumentRun::failure)
.map(checkpoint_failed_warning),
);
self.log_memory_summary(plan, pages.len());
Ok(DocumentResult {
markdown,
raw,
pages_out: report
.as_ref()
.and_then(|r| r.pages)
.map_or(pages.len(), |p| p.bodies),
pages,
skipped,
finish_reason: report
.as_ref()
.map_or(FinishReason::Stop, |r| r.finish_reason),
extra_markers: report
.as_ref()
.and_then(|r| r.document.as_ref())
.map_or(0, |stats| stats.extra_markers),
windows,
resumed_from,
warnings,
model: self.identity(),
})
}
fn log_memory_summary(&self, plan: DocumentPlan, pages: usize) {
if let DocumentPlan::Windows(window) = plan
&& let Ok(estimate) =
self.estimate_peak_bytes(window.min(pages), memory::DEFAULT_TOKENS_PER_PAGE)
{
memory::log_against_estimate(&estimate);
}
}
fn resume_key(
&self,
window: usize,
caller: &checkpoint::ResumeKey,
) -> FocrResult<checkpoint::ResumeKey> {
let params = self.document_params(window + 1);
Ok(caller
.clone()
.with("binary_version", env!("CARGO_PKG_VERSION"))
.with("target_arch", std::env::consts::ARCH)
.with("model_id", self.arch().id())
.with("artifact_sha256", self.weights.source_sha256())
.with(
"quant_recipe",
self.weights.quant_recipe().unwrap_or("none"),
)
.with("window", window)
.with("temperature", params.temperature.to_bits())
.with("max_length", params.max_length)
.with("max_page_tokens", params.max_page_tokens)
.with("max_positions", params.max_positions)
.with("no_repeat_ngram_size", params.no_repeat_ngram_size)
.with("ngram_window", params.ngram_window)
.with("entmax_domain", format!("{:?}", params.entmax_domain))
.with("crop_mode", format!("{:?}", preprocess_mode()))
.with("retirement", srswa::retire_enabled())
.with("precision", DocumentPrecision::from_env()?.name())
.with("kv_dtype", "f32"))
}
fn parse_windowed(
&self,
pages: &mut dyn Iterator<Item = FocrResult<Preprocessed>>,
window: usize,
read: &std::cell::RefCell<Vec<checkpoint::ReadPage>>,
resumed: Option<checkpoint::Resumed>,
mut run: Option<&mut checkpoint::DocumentRun>,
on_event: Option<DocumentSink<'_>>,
) -> FocrResult<DocumentParse> {
let opts = window::WindowedOptions {
window,
..window::WindowedOptions::default()
};
let mut stream = WindowedStream {
model_id: self.arch().id(),
pages: Vec::new(),
on_event,
};
let from = resumed.map(|resumed| {
for page in &resumed.pages {
if let Some(body) = &page.body {
stream.add_page(page, body);
}
}
resumed.from
});
let mut sink = |record: &window::WindowRecord,
boundary: Option<&window::WindowBoundary<'_>>| {
let w = &record.stats;
let read = read.borrow();
let own: Vec<checkpoint::JournalPage> = w
.pages
.clone()
.enumerate()
.map(|(k, page)| checkpoint::JournalPage {
run_index: page + 1,
window: w.index,
read: read[page].clone(),
body: record.bodies.get(k).cloned(),
tokens: record.tokens.get(k).copied(),
finish: record.finish.get(k).copied(),
})
.collect();
for page in &own {
if let Some(body) = &page.body {
stream.add_page(page, body);
}
}
if let (Some(run), Some(boundary)) = (run.as_deref_mut(), boundary) {
run.window_end(own, record, boundary, &read[boundary.next_page()]);
}
stream.window_ended(w);
};
let _admission = forward_admission().acquire();
let decoded =
match from {
Some(from) => window::Windowing::Windowed(Box::new(
self.resume_document_windowed(pages, from, &opts, Some(&mut sink))?,
)),
None => self.decode_document_windowed(pages, &opts, Some(&mut sink))?,
};
Ok(match decoded {
window::Windowing::Fits(pres) => DocumentParse::OnePass(pres),
window::Windowing::Windowed(doc) => {
DocumentParse::Windowed(DocumentPages::joined(stream.pages), doc.windows)
}
})
}
fn document_page(&self, img: image::DynamicImage) -> FocrResult<Preprocessed> {
if self.arch().decode_contract().pages.is_some() {
preprocess::preprocess_dynamic(
img,
paged_document_view(preprocess_mode(), self.arch().id())?,
preprocess_profile(self.arch()),
)
} else {
preprocess::preprocess_dynamic_squash(img, MULTI_PAGE_BASE_SIZE)
}
}
fn reread_page(
&self,
source: &crate::page_source::PageSource,
page: &PageResult,
) -> Result<PageResult, String> {
let origin = PageOrigin {
source_page: page.source_page,
half: page.half,
};
let failed = |e: FocrError| format!("its re-read failed: {e}");
let img = source
.fetch(origin.source_page, origin.half)
.map_err(failed)?;
let pre = self.document_page(img).map_err(failed)?;
let size = pre.original_size;
let decoded = {
let _admission = forward_admission().acquire();
self.decode_document(std::slice::from_ref(&pre), None)
.map_err(failed)?
};
let finish = decoded.finish.first().copied();
timing_log(&format!(
"reread page {}: {} tokens, finish {}",
page.run_index,
decoded.split.tokens.first().copied().unwrap_or(0),
finish.map_or("none", PageFinish::as_str),
));
match (finish, decoded.split.bodies.first()) {
(Some(PageFinish::Eos), Some(body)) => Ok(PageResult {
tokens: decoded.split.tokens.first().copied(),
finish,
elapsed_ms: decoded.elapsed.first().copied().map(millis),
..PageResult::read(page.run_index, body, size, origin, page.model_id)
}),
(Some(PageFinish::PageCap), _) => Err("its re-read reached the cap again".to_owned()),
(Some(finish), Some(_)) => Err(format!("its re-read ended `{}`", finish.as_str())),
_ => Err("its re-read wrote no page".to_owned()),
}
}
fn parse_document(
&self,
pres: Vec<Preprocessed>,
origins: &[PageOrigin],
mut on_event: Option<DocumentSink<'_>>,
) -> FocrResult<DocumentPages> {
let _admission = forward_admission().acquire();
let frame = PageFrame {
sizes: pres.iter().map(|p| p.original_size).collect(),
origins,
model_id: self.arch().id(),
};
if self.arch().decode_contract().pages.is_some() {
let mut closed = |page: &ClosedPage<'_>| {
if let Some(sink) = on_event.as_deref_mut() {
let result = frame.closed_page(page);
sink(&DocumentEvent::Page(StreamedPage {
index: page.index,
body: page.body,
page: Some(&result),
}));
}
};
let decoded = self.decode_document(&pres, Some(&mut closed))?;
return Ok(DocumentPages::joined(frame.token_pages(&decoded)));
}
let (prompt_ids, images_seq_mask) = self.build_prompt_multi(&pres)?;
let max_positions = self.decode_params.max_positions;
if prompt_ids.len() + 1 > max_positions {
return Err(FocrError::Other(anyhow::anyhow!(
"document: the assembled {}-page prefix is {} tokens — over the \
{max_positions} position budget by {} with no room to generate; {}",
pres.len(),
prompt_ids.len(),
(prompt_ids.len() + 1).saturating_sub(max_positions),
warning_recovery(WarningKind::PromptTooLong),
)));
}
let tv = Instant::now();
let mut globals: Vec<Mat> = Vec::with_capacity(pres.len());
for pre in &pres {
let mut feats = self.vision_tower(pre)?;
if feats.len() != 1 {
return Err(FocrError::Other(anyhow::anyhow!(
"document: a Base-mode page produced {} vision blocks; expected 1",
feats.len()
)));
}
globals.push(feats.remove(0));
}
timing_log(&format!(
"multi_page.vision ({} pages) {:.2}s",
pres.len(),
tv.elapsed().as_secs_f64()
));
let mut inputs_embeds = self.embed_prompt(&prompt_ids)?;
let image_newline = Self::image_newline(&self.weights)?;
let view_seperator = Self::view_seperator(&self.weights)?;
let grid = preprocess::num_queries(MULTI_PAGE_BASE_SIZE);
connector::fuse_no_crop(
&self.weights,
&mut inputs_embeds,
&globals,
grid,
grid,
&image_newline,
&view_seperator,
&images_seq_mask,
)?;
let position_room = max_positions - prompt_ids.len();
let params = sampler::DecodeParams {
ngram_window: sampler::NGRAM_WINDOW_MULTI,
max_length: self.decode_params.max_length.min(position_room),
..self.decode_params.clone()
};
let td = Instant::now();
let generated = match on_event {
None => self.generate_with(inputs_embeds, &prompt_ids, ¶ms)?,
Some(on_event) => {
const STREAM_CHECK_EVERY: usize = 8;
let tok = self.tokenizer()?;
let mut stream = postprocess::PageStream::new();
let mut decoder = crate::tokenizer::StreamDecoder::new();
let mut text = String::new();
let mut seen = 0usize;
let mut on_body = |index: usize, body: &str| {
on_event(&DocumentEvent::Page(StreamedPage {
index,
body,
page: None,
}));
};
let mut observer = |id: u32| {
seen += 1;
let _ = decoder.push(tok, id, &mut text);
if seen.is_multiple_of(STREAM_CHECK_EVERY) {
stream.feed(&text, &mut on_body);
}
};
let generated = self.generate_with_observer(
inputs_embeds,
&prompt_ids,
¶ms,
Some(&mut observer),
false,
)?;
decoder.flush(&mut text);
let text = postprocess::strip_eos(&text);
stream.feed(&text, &mut on_body);
stream.finish(&text, &mut on_body);
generated
}
};
timing_log(&format!(
"multi_page.decode {} tokens {:.2}s",
generated.len(),
td.elapsed().as_secs_f64()
));
let decoded = self.tokenizer()?.decode(&generated)?;
let stripped = postprocess::strip_eos(&decoded);
if let Ok(mut slot) = self.last_decode.lock()
&& let Some(report) = slot.as_mut()
{
if report.finish_reason == FinishReason::Length
&& position_room <= self.decode_params.max_length
{
report.cap = Some(LengthCap::Positions);
}
report.pages = Some(PageBodies {
expected: pres.len(),
bodies: postprocess::page_body_count(&stripped),
});
}
Ok(DocumentPages {
markdown: postprocess::finalize_multi(&decoded, pres.len())?,
pages: frame.text_pages(&stripped),
raw: stripped,
})
}
fn decode_document(
&self,
pres: &[Preprocessed],
mut on_page: Option<&mut dyn FnMut(&ClosedPage<'_>)>,
) -> FocrResult<DecodedDocument> {
let Some(paging) = self.arch().decode_contract().pages else {
return Err(FocrError::Other(anyhow::anyhow!(
"{} has no page contract, so no document loop",
self.arch().id()
)));
};
let deadline = crate::forward_stage_budget().and_then(|per_page| {
let pages = u32::try_from(pres.len().max(1)).unwrap_or(u32::MAX);
Instant::now().checked_add(per_page.checked_mul(pages)?)
});
let params = self.document_params(pres.len());
let (layout, _) = self.build_prompt_multi(pres)?;
if layout.len() + 1 > params.max_positions {
return Err(FocrError::Other(anyhow::anyhow!(
"the {}-page prompt is {} tokens, over the {} position limit with no room to \
generate; {}",
pres.len(),
thousands(layout.len()),
thousands(params.max_positions),
warning_recovery(WarningKind::V2PromptTooLong),
)));
}
let runaway = self.page_runaway()?;
let (inputs_embeds, prompt_ids) = self.document_inputs(pres)?;
let tok = self.tokenizer()?;
let mut stream = postprocess::TokenPageStream::new(tok, paging.marker_id, pres.len());
let markers = prompt_ids
.iter()
.filter(|&&id| id == paging.marker_id)
.count();
let mut mirror = document_guard(
if paging.guard { markers } else { 0 },
paging.marker_id,
¶ms,
)
.with_runaway(runaway);
let mut elapsed = Vec::with_capacity(pres.len());
let mut opened: Option<Instant> = None;
let mut observer = |id: u32| {
let now = Instant::now();
let since = now - *opened.get_or_insert(now);
let finish = forced_finish(&mirror).unwrap_or(PageFinish::Marker);
mirror.observe(id);
stream.push(id, |index, body, tokens| {
elapsed.push(since);
opened = Some(now);
if let Some(sink) = on_page.as_deref_mut() {
sink(&ClosedPage {
index,
body,
tokens,
finish,
elapsed: since,
});
}
});
};
let decode = self.generate_document(
&inputs_embeds,
&prompt_ids,
¶ms,
deadline,
Some(&mut observer),
)?;
let since = opened.map_or_else(std::time::Duration::default, |t| t.elapsed());
let last = match decode.finish_reason {
FinishReason::Stop => forced_finish(&mirror).unwrap_or(PageFinish::Eos),
FinishReason::Length => PageFinish::Length,
FinishReason::Budget => PageFinish::Budget,
FinishReason::Cancelled => PageFinish::Cancelled,
FinishReason::Runaway => PageFinish::Runaway,
};
stream.finish(|index, body, tokens| {
elapsed.push(since);
if let Some(sink) = on_page.as_deref_mut() {
sink(&ClosedPage {
index,
body,
tokens,
finish: last,
elapsed: since,
});
}
});
let split =
postprocess::split_pages_by_token(tok, &decode.ids, paging.marker_id, pres.len())?;
let mut stats = decode.stats;
stats.page_tokens.clone_from(&split.tokens);
stats.leading_text = split.leading_text;
stats.extra_markers = split.extra_markers;
stats.gundam_view = pres
.iter()
.any(|p| matches!(p.mode, preprocess::PreprocessMode::Gundam { .. }));
stats.vision_encodes = pres.len();
let finish = stats.page_finish.clone();
timing_log(&format!(
"document {} pages: {} tokens, finish {}, pages {:?}, forced {}, hf-id-0 {}, \
retired {} blocks, peak kv {} bytes, retire fallbacks {}",
pres.len(),
decode.ids.len(),
decode.finish_reason.as_str(),
stats
.page_finish
.iter()
.map(|f| f.as_str())
.collect::<Vec<_>>(),
stats.forced_steps,
stats.hf_all_neg_inf_steps,
stats.retired_blocks,
stats.peak_kv_bytes,
stats.retire_fallbacks,
));
if let Ok(mut slot) = self.last_decode.lock() {
let (decodes, length_stops) = slot
.as_ref()
.map_or((0, 0), |prior| (prior.decodes, prior.length_stops));
let length = decode.finish_reason == FinishReason::Length;
let stopped = decode.finish_reason != FinishReason::Stop;
*slot = Some(DecodeReport {
ids: decode.ids,
finish_reason: decode.finish_reason,
cap: length.then_some(LengthCap::Positions),
pages: Some(PageBodies {
expected: pres.len(),
bodies: split.bodies.len(),
}),
max_length: params.max_length,
max_positions: params.max_positions,
decodes: decodes + 1,
length_stops: length_stops + usize::from(stopped),
runaway_stops: 0,
document: Some(stats),
admission_warnings: lever_policy::admit_from_process(self.arch()).warnings,
});
}
Ok(DecodedDocument {
split,
finish,
elapsed,
})
}
fn document_inputs(&self, pres: &[Preprocessed]) -> FocrResult<(Mat, Vec<u32>)> {
let tv = Instant::now();
if let [pre] = pres {
let features = self.vision_tower(pre)?;
timing_log(&format!("vision_tower {:.2}s", tv.elapsed().as_secs_f64()));
return self.build_inputs_embeds(pre, &features);
}
let Some(first) = pres.first() else {
return Err(FocrError::Other(anyhow::anyhow!(
"a document needs at least one page"
)));
};
let mut globals = Vec::with_capacity(pres.len());
for (i, pre) in pres.iter().enumerate() {
let mut features = self.vision_tower(pre)?;
if pre.crop_grid.is_tiled() || features.len() != 1 {
return Err(FocrError::Other(anyhow::anyhow!(
"document page {} is not one base view ({} vision blocks)",
i + 1,
features.len()
)));
}
globals.push(features.remove(0));
}
timing_log(&format!(
"document.vision ({} pages) {:.2}s",
pres.len(),
tv.elapsed().as_secs_f64()
));
let (prompt_ids, images_seq_mask) = self.build_prompt_multi(pres)?;
let mut inputs_embeds = self.embed_prompt(&prompt_ids)?;
connector::fuse_no_crop(
&self.weights,
&mut inputs_embeds,
&globals,
Self::global_grid_h(first),
Self::global_grid_w(first),
&Self::image_newline(&self.weights)?,
&Self::view_seperator(&self.weights)?,
&images_seq_mask,
)?;
Ok((inputs_embeds, prompt_ids))
}
fn document_params(&self, pages: usize) -> DecodeParams {
let default_window = if pages > 1 {
sampler::NGRAM_WINDOW_MULTI
} else {
sampler::NGRAM_WINDOW_SINGLE
};
DecodeParams {
ngram_window: decode_overrides().ngram_window.unwrap_or(default_window),
..self.decode_params.clone()
}
}
fn generate_document(
&self,
inputs_embeds: &Mat,
prompt_ids: &[u32],
params: &DecodeParams,
deadline: Option<Instant>,
observer: Option<&mut dyn FnMut(u32)>,
) -> FocrResult<DocumentDecode> {
let precision = DocumentPrecision::from_env()?;
decode_with_retire_fallback(
srswa::retire_enabled(),
observer,
|retire, budget_floor, ids, observer| {
let pass = DocumentPass {
precision,
retire,
deadline,
budget_floor,
};
self.generate_document_once(inputs_embeds, prompt_ids, params, pass, ids, observer)
},
)
}
#[cfg(feature = "native")]
pub(crate) fn checks_forward_budget(&self) -> bool {
self.arch().decode_contract().pages.is_some()
}
fn document_logits(&self, decoder_state: &DocumentDecoder<'_>) -> FocrResult<Mat> {
match decoder_state {
DocumentDecoder::Cached { wc, hidden, .. } => decoder::lm_head_cached(wc, hidden),
DocumentDecoder::Stateless {
embeds,
ids,
domain,
} => {
let coords = srswa::PageCoords::build(ids)?;
let h = decoder::forward_srswa(&self.weights, embeds, &coords, *domain)?;
decoder::lm_head(&self.weights, &Self::last_hidden_row(&h)?)
}
}
}
fn generate_document_once(
&self,
inputs_embeds: &Mat,
prompt_ids: &[u32],
params: &DecodeParams,
pass: DocumentPass,
ids: &mut Vec<u32>,
observer: Option<&mut dyn FnMut(u32)>,
) -> FocrResult<DocumentDecode> {
let Some(paging) = self.arch().decode_contract().pages else {
return Err(FocrError::Other(anyhow::anyhow!(
"{} has no page contract",
self.arch().id()
)));
};
let pages = prompt_ids
.iter()
.filter(|&&id| id == paging.marker_id)
.count();
let prompt_len = prompt_ids.len();
let mut guard = document_guard(
if paging.guard { pages } else { 0 },
paging.marker_id,
params,
)
.with_runaway(self.page_runaway()?);
ids.clear();
if pass.budget_spent(0) {
timing_log("forward stage budget ran out before the prefill");
return Ok(DocumentDecode {
ids: Vec::new(),
finish_reason: FinishReason::Budget,
stats: DocumentStats {
page_finish: vec![PageFinish::Budget],
..DocumentStats::default()
},
});
}
let tp = Instant::now();
progress::emit("prefill", 0, prompt_len as u64);
timing_log(&format!("precision {}", pass.precision.name()));
let mut decoder_state = if pass.precision == DocumentPrecision::Stateless {
DocumentDecoder::Stateless {
embeds: inputs_embeds.clone(),
ids: prompt_ids.to_vec(),
domain: params.entmax_domain,
}
} else {
let wc = if pass.precision == DocumentPrecision::CachedF32 {
self.decoder_cache_f32()?
} else {
self.decoder_cache()?
};
let coords = srswa::PageCoords::build(prompt_ids)?;
let (hidden, mut state) = decoder::prefill_with_cache_srswa(
wc,
inputs_embeds,
&coords,
params.entmax_domain,
)?;
state.set_retire(pass.retire && guard.enabled());
DocumentDecoder::Cached {
wc,
state,
hidden: Self::last_hidden_row(&hidden)?,
}
};
let logits = self.document_logits(&decoder_state)?;
progress::emit("prefill", prompt_len as u64, prompt_len as u64);
timing_log(&format!(
"prefill {:.2}s ({prompt_len} tokens)",
tp.elapsed().as_secs_f64()
));
let spec = DocumentLoop {
params,
pass,
marker: paging.marker_id,
prompt_len,
pages,
forced_prefix: 0,
};
let (finish_reason, stats) =
self.document_loop(spec, &mut decoder_state, logits, &mut guard, ids, observer)?;
Ok(DocumentDecode {
ids: ids.clone(),
finish_reason,
stats,
})
}
fn document_loop(
&self,
spec: DocumentLoop<'_>,
decoder_state: &mut DocumentDecoder<'_>,
mut logits: Mat,
guard: &mut sampler::PageGuard,
ids: &mut Vec<u32>,
mut observer: Option<&mut dyn FnMut(u32)>,
) -> FocrResult<(FinishReason, DocumentStats)> {
let DocumentLoop {
params,
pass,
marker,
prompt_len,
pages,
forced_prefix,
} = spec;
let table = self.embed_table()?;
let hidden_dim = table.cols();
let room = params
.max_positions
.saturating_sub(prompt_len + forced_prefix);
let mut stats = DocumentStats::default();
let mut finish_reason = FinishReason::Length;
let td = Instant::now();
decoder::prof::reset();
loop {
if crate::shutdown_requested() && ids.len() >= pass.budget_floor {
if stats.page_finish.len() < pages.max(1) {
stats.page_finish.push(PageFinish::Cancelled);
}
finish_reason = FinishReason::Cancelled;
timing_log(&format!(
"cancelled after {} generated tokens",
ids.len() - forced_prefix
));
break;
}
if pass.budget_spent(ids.len()) {
if stats.page_finish.len() < pages.max(1) {
stats.page_finish.push(PageFinish::Budget);
}
finish_reason = FinishReason::Budget;
timing_log(&format!(
"forward stage budget ran out after {} generated tokens",
ids.len() - forced_prefix
));
break;
}
let forced = forced_finish(guard);
let step = sampler::decode_step_guarded(&logits, ids, guard, params)?;
stats.forced_steps += usize::from(step.forced);
stats.hf_all_neg_inf_steps += usize::from(step.hf_all_neg_inf);
if step.is_eos {
if stats.page_finish.len() < pages.max(1) {
stats.page_finish.push(forced.unwrap_or(PageFinish::Eos));
}
finish_reason = FinishReason::Stop;
break;
}
let id = step.token_id;
if id as usize >= table.rows() {
return Err(FocrError::Other(anyhow::anyhow!(
"decoded token id {id} outside the embed vocab {}",
table.rows()
)));
}
ids.push(id);
guard.observe(id);
if id == marker && guard.markers() >= 2 && stats.page_finish.len() < pages {
stats.page_finish.push(forced.unwrap_or(PageFinish::Marker));
}
progress::emit("decode", (ids.len() - forced_prefix) as u64, room as u64);
if let Some(f) = observer.as_deref_mut() {
f(id);
}
if prompt_len + ids.len() >= params.max_positions {
if stats.page_finish.len() < pages.max(1) {
stats.page_finish.push(PageFinish::Length);
}
break;
}
let row = table.row_f32(id as usize);
match decoder_state {
DocumentDecoder::Cached { wc, state, hidden } => {
let row = Mat::from_vec(1, hidden_dim, row);
let forced_next = matches!(guard.verdict(), sampler::GuardVerdict::Force(_));
let h =
decoder::decode_step_with_cache_srswa(wc, state, id, &row, forced_next)?;
*hidden = Self::last_hidden_row(&h)?;
}
DocumentDecoder::Stateless {
embeds, ids: grown, ..
} => {
let mut data = std::mem::take(&mut embeds.data);
data.extend(row);
*embeds = Mat::from_vec(embeds.rows + 1, hidden_dim, data);
grown.push(id);
}
}
logits = self.document_logits(decoder_state)?;
}
if let DocumentDecoder::Cached { state, .. } = decoder_state {
stats.retired_blocks = state.dead_blocks();
stats.peak_kv_bytes = state.peak_bytes();
}
let generated = ids.len() - forced_prefix;
timing_log(&format!(
"decode {:.2}s ({generated} tokens, {:.3}s/tok)",
td.elapsed().as_secs_f64(),
td.elapsed().as_secs_f64() / (generated.max(1) as f64)
));
if decoder::prof::enabled() {
let (lmhead, attn, experts, route) = decoder::prof::snapshot_ms();
timing_log(&format!(
"decode phases (ms): lm_head {lmhead:.0} attn {attn:.0} experts {experts:.0} route {route:.0}"
));
let [project, append, sieve, attend, retire] = decoder::prof::srswa_snapshot_ms();
timing_log(&format!(
"srswa phases (ms): project {project:.0} append {append:.0} route {sieve:.0} attend {attend:.0} retire {retire:.0}"
));
}
Ok((finish_reason, stats))
}
#[must_use]
pub fn recognize_batch(&self, image_paths: &[&Path]) -> Vec<FocrResult<String>> {
let spine = batch_scheduler::spine_enabled()
&& (int8_decode_requested() || self.artifact_stores_gated_int8())
&& std::env::var_os(DECODE_STATELESS_ENV).is_none()
&& std::env::var_os(DECODE_F32_ENV).is_none()
&& self.arch().unlimited_variant() == Some(model_arch::UnlimitedVariant::V1Rswa);
self.set_batch_decodes(vec![None; image_paths.len()]);
self.set_batch_charts(vec![None; image_paths.len()]);
let _ = self.take_last_decode();
let _ = self.take_chart_meta();
if !spine
&& batch_scheduler::spine_enabled()
&& matches!(self.arch().id(), "got-ocr2" | "smolvlm2" | "onechart")
{
let _admission = forward_admission().acquire();
match self.recognize_batch_dense(image_paths) {
Ok(results) => return results,
Err(err) => {
let msg = err.to_string();
return image_paths
.iter()
.map(|_| Err(FocrError::Other(anyhow::anyhow!("{msg}"))))
.collect();
}
}
}
if !spine {
let mut reports = Vec::with_capacity(image_paths.len());
let mut charts = Vec::with_capacity(image_paths.len());
let results: Vec<FocrResult<String>> = image_paths
.iter()
.map(|p| {
let result = self.recognize(p);
reports.push(self.take_last_decode());
charts.push(self.take_chart_meta());
result
})
.collect();
self.set_batch_decodes(reports);
self.set_batch_charts(charts);
return results;
}
let _admission = forward_admission().acquire();
match self.recognize_batch_spine(image_paths) {
Ok(results) => results,
Err(err) => {
let msg = err.to_string();
image_paths
.iter()
.map(|_| Err(FocrError::Other(anyhow::anyhow!("{msg}"))))
.collect()
}
}
}
fn recognize_batch_dense(&self, image_paths: &[&Path]) -> FocrResult<Vec<FocrResult<String>>> {
let arch_id = self.arch().id();
let max_new = match arch_id {
"got-ocr2" => self.decode_params.max_length.min(got::MAX_NEW_TOKENS),
_ => self.decode_params.max_length,
};
let t = Instant::now();
let mut out: Vec<Option<FocrResult<String>>> =
(0..image_paths.len()).map(|_| None).collect();
let mut live: Vec<usize> = Vec::new();
let mut imgs: Vec<image::DynamicImage> = Vec::new();
for (i, p) in image_paths.iter().enumerate() {
crate::cancel_checkpoint()?;
match preprocess::decode_path(p) {
Ok(img) => {
live.push(i);
imgs.push(img);
}
Err(e) => out[i] = Some(Err(e)),
}
}
if !imgs.is_empty() {
use image::GenericImageView;
let dims: Vec<(u32, u32)> = imgs.iter().map(|im| im.dimensions()).collect();
let refs: Vec<&image::DynamicImage> = imgs.iter().collect();
let mut charts: Vec<ChartPageMeta> = Vec::new();
let texts: Vec<String> = match arch_id {
"got-ocr2" => {
let inputs: Vec<_> = refs.iter().map(|img| got_input(img)).collect();
let views: Vec<&image::DynamicImage> =
inputs.iter().map(std::borrow::Cow::as_ref).collect();
let regions: Vec<String> =
dims.iter().map(|&(w, h)| got_region_prefix(w, h)).collect();
got::recognize_batch(
&self.weights,
self.got_statics()?,
self.got_tokenizer()?,
&views,
max_new,
got_format_requested(),
®ions,
)?
}
"smolvlm2" => smolvlm2::recognize_batch(
&self.weights,
self.smol_statics()?,
self.tokenizer()?,
&refs,
&smolvlm2_question(),
max_new,
)?,
"onechart" => onechart::recognize_batch(
&self.weights,
self.onechart_statics()?,
self.tokenizer()?,
&refs,
max_new,
)?
.into_iter()
.map(|r| {
charts.push(ChartPageMeta::from_result(&r));
r.json_text
})
.collect(),
other => {
return Err(FocrError::Other(anyhow::anyhow!(
"dense batch spine: unrouted arch {other}"
)));
}
};
let mut charts = charts.into_iter();
let mut page_charts = vec![None; image_paths.len()];
for ((slot, text), (iw, ih)) in live.into_iter().zip(texts).zip(dims) {
out[slot] = Some(postprocess::finalize(&text, iw, ih));
page_charts[slot] = charts.next();
}
self.set_batch_charts(page_charts);
}
timing_log(&format!(
"{arch_id} forward(batch of {}) {:.2}s",
image_paths.len(),
t.elapsed().as_secs_f64()
));
Ok(out
.into_iter()
.map(|r| r.unwrap_or_else(|| Err(FocrError::Other(anyhow::anyhow!("page skipped")))))
.collect())
}
fn recognize_batch_spine(&self, image_paths: &[&Path]) -> FocrResult<Vec<FocrResult<String>>> {
let n = image_paths.len();
let wc = self.decoder_cache_i8()?;
let embed_table = self.embed_table()?;
let tokenizer = self.tokenizer()?;
let runaway_guard = sampler::RunawayGuard::from_env(true)?;
let mut out: Vec<Option<FocrResult<String>>> = (0..n).map(|_| None).collect();
let mut reports: Vec<Option<DecodeReport>> = (0..n).map(|_| None).collect();
let (mode, profile) = (preprocess_mode(), preprocess_profile(self.arch()));
let mut pres: Vec<Option<Preprocessed>> = (0..n).map(|_| None).collect();
for (gi, &path) in image_paths.iter().enumerate() {
match preprocess::preprocess_image(path, mode, profile) {
Ok(p) => pres[gi] = Some(p),
Err(e) => out[gi] = Some(Err(e)),
}
}
let admitted: Vec<usize> = (0..n).filter(|&gi| pres[gi].is_some()).collect();
let mut feats: Vec<Option<Vec<Mat>>> = (0..n).map(|_| None).collect();
if !admitted.is_empty() {
let mut batched_ok = false;
if batch_vision_enabled() {
let prefs: Vec<&Preprocessed> = admitted
.iter()
.map(|&gi| pres[gi].as_ref().expect("admitted page has a preprocess"))
.collect();
match self.vision_tower_batched_pages(&prefs) {
Ok(all) => {
for (k, f) in all.into_iter().enumerate() {
feats[admitted[k]] = Some(f);
}
batched_ok = true;
}
Err(e) => {
crate::progress::stderr_message(format_args!(
"[focr] batched vision failed ({e}); using the per-page tower"
));
}
}
}
if !batched_ok {
for &gi in &admitted {
let pre = pres[gi].as_ref().expect("admitted page has a preprocess");
match self.vision_tower(pre) {
Ok(f) => feats[gi] = Some(f),
Err(e) => out[gi] = Some(Err(e)),
}
}
}
}
let mut streams: Vec<batch_scheduler::PageStream> = Vec::new();
let mut stream_caches: Vec<Vec<rswa::RingCache>> = Vec::new();
let mut scheduled: Vec<(usize, u32, u32)> = Vec::new();
for gi in 0..n {
let (Some(pre), Some(vf)) = (pres[gi].take(), feats[gi].take()) else {
continue;
};
match self.prefill_from_features(wc, &pre, &vf) {
Ok(p) => {
streams.push(
batch_scheduler::PageStream::new(
gi,
p.prefill_len,
&p.prompt_ids,
p.last_hidden,
)
.with_runaway_guard(runaway_guard.clone()),
);
stream_caches.push(p.caches);
scheduled.push((gi, p.image_w, p.image_h));
}
Err(e) => out[gi] = Some(Err(e)),
}
}
if !streams.is_empty() {
let mut batched = rswa::BatchedRingCache::from_streams(stream_caches)?;
let mut step = batch_scheduler::DecoderBatchStep {
wc,
caches: &mut batched,
embed_table: &embed_table,
params: &self.decode_params,
};
let mut scheduler =
batch_scheduler::BatchScheduler::from_env(self.decode_params.max_length);
let token_lists = scheduler.run(streams, &mut step)?;
for (k, tokens) in token_lists.into_iter().enumerate() {
let (gi, w, h) = scheduled[k];
let finalized = tokenizer
.decode(&tokens)
.and_then(|decoded| postprocess::finalize(&decoded, w, h));
let ran_away = scheduler.ran_away().binary_search(&gi).is_ok();
reports[gi] = Some(DecodeReport::v1_decode(
tokens,
&self.decode_params,
ran_away,
));
out[gi] = Some(finalized);
}
}
self.set_batch_decodes(reports);
Ok(out
.into_iter()
.map(|slot| {
slot.unwrap_or_else(|| {
Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::recognize_batch: page produced no result"
)))
})
})
.collect())
}
fn decoder_cache_i8(&self) -> FocrResult<&decoder::DecoderWeightCacheI8> {
self.require_full_int8_authorization()?;
self.decoder_cache_i8
.get_or_try_init(|| decoder::DecoderWeightCacheI8::build(&self.weights))
}
fn artifact_stores_gated_int8(&self) -> bool {
self.arch().unlimited_variant() == Some(model_arch::UnlimitedVariant::V1Rswa)
&& self.weights.quant_recipe()
== Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID)
}
fn require_full_int8_authorization(&self) -> FocrResult<()> {
if self.artifact_stores_gated_int8() {
return Ok(());
}
require_experimental_full_int8_recipe()
}
fn model_memory(&self) -> FocrResult<memory::ModelMemory> {
let artifact = std::fs::metadata(&self.path)
.map_err(|e| FocrError::Other(anyhow::anyhow!("{}: {e}", self.path.display())))?
.len();
Ok(memory::ModelMemory::of(&self.weights, artifact))
}
pub fn estimate_peak_bytes(
&self,
page_window: usize,
tokens_per_page: usize,
) -> FocrResult<memory::Estimate> {
Ok(memory::estimate_peak_bytes(
&self.model_memory()?,
memory::EstimateAssumptions {
page_window,
tokens_per_page,
retirement: srswa::retire_enabled(),
},
))
}
fn fitting_page_window(&self) -> FocrResult<usize> {
let budget = memory::memory_budget()?;
let model = self.model_memory()?;
let at = |page_window| {
memory::estimate_peak_bytes(
&model,
memory::EstimateAssumptions {
page_window,
tokens_per_page: memory::DEFAULT_TOKENS_PER_PAGE,
retirement: srswa::retire_enabled(),
},
)
};
memory::fitting_window(window::DEFAULT_PAGE_WINDOW, budget, at)
.map_err(|estimate| memory::refusal(&estimate, budget))
}
fn decoder_cache(&self) -> FocrResult<&decoder::DecoderWeightCache> {
self.decoder_cache.get_or_try_init(|| {
let built = match self.arch().unlimited_variant() {
Some(model_arch::UnlimitedVariant::V2SrSwa) => {
decoder::DecoderWeightCache::build_srswa(&self.weights)
}
Some(model_arch::UnlimitedVariant::V1Rswa) | None => {
decoder::DecoderWeightCache::build(&self.weights)
}
};
memory::log_peak_rss("decoder weights");
built
})
}
fn decoder_cache_f32(&self) -> FocrResult<&decoder::DecoderWeightCache> {
let Some(variant) = self.arch().unlimited_variant() else {
return Err(FocrError::Usage(format!(
"{DECODE_F32_ENV} decodes Unlimited-OCR models; {} has no all-f32 cache",
self.arch().id()
)));
};
self.decoder_cache_f32.get_or_try_init(|| {
let t = Instant::now();
let built = match variant {
model_arch::UnlimitedVariant::V1Rswa => {
decoder::DecoderWeightCache::build_f32(&self.weights)?
}
model_arch::UnlimitedVariant::V2SrSwa => {
decoder::DecoderWeightCache::build_srswa_f32(&self.weights)?
}
};
timing_log(&format!(
"decoder f32 cache {:.2}s",
t.elapsed().as_secs_f64()
));
Ok(built)
})
}
fn clip_weights(&self) -> FocrResult<&vision_clip::ClipWeights> {
self.clip_cache.get_or_try_init(|| {
let th = Instant::now();
let built = vision_clip::clip_weights_from(&self.weights)?;
timing_log(&format!(
" clip.hydrate(cached) {:.2}s",
th.elapsed().as_secs_f64()
));
memory::log_peak_rss("clip weights");
Ok(built)
})
}
fn unlimited_vision_statics(&self) -> FocrResult<&UnlimitedVisionStatics> {
self.unlimited_vision.get_or_try_init(|| {
let started = Instant::now();
let built = UnlimitedVisionStatics {
sam: vision_sam::sam_weights_from(&self.weights, "model.sam_model")?,
projector: vision_bridge::projector_weights_from(&self.weights)?,
};
timing_log(&format!(
" unlimited_vision.hydrate(cached) {:.2}s",
started.elapsed().as_secs_f64()
));
memory::log_peak_rss("vision statics");
Ok(built)
})
}
fn got_statics(&self) -> FocrResult<&got::GotStatics> {
self.got_statics.get_or_try_init(|| {
got::hydrate_statics(
&self.weights,
self.arch().vision_tower_prefix(),
stream_vision_tower(),
)
})
}
fn onechart_statics(&self) -> FocrResult<&onechart::OnechartStatics> {
self.onechart_statics.get_or_try_init(|| {
onechart::hydrate_statics(
&self.weights,
self.arch().vision_tower_prefix(),
stream_vision_tower(),
)
})
}
fn smol_statics(&self) -> FocrResult<&smolvlm2::SmolStatics> {
self.smol_statics
.get_or_try_init(|| smolvlm2::hydrate_statics(&self.weights, stream_vision_tower()))
}
fn prefill_from_features(
&self,
wc: &decoder::DecoderWeightCacheI8,
pre: &Preprocessed,
vision_features: &[Mat],
) -> FocrResult<PagePrefill> {
let (image_w, image_h) = Self::image_dims(pre);
let (inputs_embeds, prompt_ids) = self.build_inputs_embeds(pre, vision_features)?;
let prefill_len = inputs_embeds.rows;
let _fwd = enter_forward();
let (hidden, caches) = decoder::prefill_with_cache_i8(wc, &inputs_embeds)?;
let last_hidden = Self::last_hidden_row(&hidden)?;
Ok(PagePrefill {
prefill_len,
prompt_ids,
last_hidden,
caches,
image_w,
image_h,
})
}
pub fn tokenizer(&self) -> FocrResult<&crate::tokenizer::Tokenizer> {
self.tokenizer.get_or_try_init(|| {
if let Some(bytes) = &self.sidecars.tokenizer_json {
return checked_tokenizer(self.arch, bytes, "the in-memory tokenizer.json sidecar");
}
if let Some([vocab, merges, added]) = &self.sidecars.opt_triple {
return crate::tokenizer::Tokenizer::from_opt_files(vocab, merges, added);
}
let dir = self.path.parent().unwrap_or_else(|| Path::new("."));
if self.arch().id() == "onechart" {
return crate::tokenizer::Tokenizer::from_opt_dir(dir);
}
let path = dir.join("tokenizer.json");
let bytes = std::fs::read(&path).map_err(|error| {
FocrError::ModelNotFound(format!("tokenizer.json at {}: {error}", path.display()))
})?;
checked_tokenizer(self.arch, &bytes, &path.display().to_string())
})
}
fn vision_tower(&self, pre: &Preprocessed) -> FocrResult<Vec<Mat>> {
let _fwd = enter_forward();
let wasm_residency = self.artifact_stores_gated_int8();
let statics = if !wasm_residency && unlimited_vision_cache_enabled() {
Some(self.unlimited_vision_statics()?)
} else {
None
};
let mut features = Vec::new();
let views = Self::views(pre);
progress::vision_begin(
views.len() as u64
* (vision_sam::DEPTH as u64 + vision_clip::ClipConfig::default().num_layers as u64),
);
if wasm_residency {
return self.vision_tower_streamed_views(&views);
}
for view in views {
let ts = Instant::now();
let sam = if let Some(statics) = statics {
let side = (view.cols as f64).sqrt() as usize;
if side * side != view.cols {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::vision_tower: image.cols {} is not a perfect square",
view.cols
)));
}
vision_sam::forward_with(&statics.sam, &view, side, side)?
} else if wasm_residency {
vision_sam::forward_streamed(&self.weights, &view, "model.sam_model")?
} else {
vision_sam::forward(&self.weights, &view)?
};
timing_log(&format!(" vision.sam {:.2}s", ts.elapsed().as_secs_f64()));
memory::log_peak_rss("sam forward");
let tc = Instant::now();
let clip = if wasm_residency {
vision_clip::forward_from_sam_streamed(
&vision_clip::ClipConfig::default(),
&self.weights,
&sam,
)?
} else {
vision_clip::forward_from_sam(
&vision_clip::ClipConfig::default(),
self.clip_weights()?,
&sam,
)?
};
timing_log(&format!(" vision.clip {:.2}s", tc.elapsed().as_secs_f64()));
memory::log_peak_rss("clip forward");
let tb = Instant::now();
let projected = if let Some(statics) = statics {
vision_bridge::forward_with(&statics.projector, &clip, &sam)?
} else {
vision_bridge::forward(&self.weights, &clip, &sam)?
};
timing_log(&format!(
" vision.bridge {:.2}s",
tb.elapsed().as_secs_f64()
));
features.push(projected);
}
Ok(features)
}
fn vision_tower_streamed_views(&self, views: &[Mat]) -> FocrResult<Vec<Mat>> {
let clip_cfg = vision_clip::ClipConfig::default();
let mut features = Vec::with_capacity(views.len());
for chunk in views.chunks(STREAMED_VIEW_CHUNK) {
let view_refs: Vec<&Mat> = chunk.iter().collect();
let ts = Instant::now();
let sams =
vision_sam::forward_streamed_views(&self.weights, &view_refs, "model.sam_model")?;
timing_log(&format!(
" vision.sam(streamed x{}) {:.2}s",
view_refs.len(),
ts.elapsed().as_secs_f64()
));
let tc = Instant::now();
let sam_refs: Vec<&Mat> = sams.iter().collect();
let clips =
vision_clip::forward_from_sam_streamed_views(&clip_cfg, &self.weights, &sam_refs)?;
timing_log(&format!(
" vision.clip(streamed x{}) {:.2}s",
sam_refs.len(),
tc.elapsed().as_secs_f64()
));
let tb = Instant::now();
let projector = vision_bridge::projector_weights_from(&self.weights)?;
for (clip, sam) in clips.iter().zip(sams.iter()) {
features.push(vision_bridge::forward_with(&projector, clip, sam)?);
}
timing_log(&format!(
" vision.bridge(streamed x{}) {:.2}s",
clips.len(),
tb.elapsed().as_secs_f64()
));
}
Ok(features)
}
fn vision_tower_batched_pages(&self, pres: &[&Preprocessed]) -> FocrResult<Vec<Vec<Mat>>> {
let _fwd = enter_forward();
let mut page_views: Vec<Vec<Mat>> = pres.iter().map(|pre| Self::views(pre)).collect();
let th = Instant::now();
let retain_statics = unlimited_vision_cache_enabled();
let statics_owner = retained_or_owned(
retain_statics,
&self.unlimited_vision,
|| -> FocrResult<UnlimitedVisionStatics> {
let started = Instant::now();
let built = UnlimitedVisionStatics {
sam: vision_sam::sam_weights_from(&self.weights, "model.sam_model")?,
projector: vision_bridge::projector_weights_from(&self.weights)?,
};
timing_log(&format!(
" unlimited_vision.hydrate({}) {:.2}s",
if retain_statics {
"cached"
} else {
"batch-local"
},
started.elapsed().as_secs_f64()
));
Ok(built)
},
)?;
let statics = statics_owner.as_ref();
let clip_cfg = vision_clip::ClipConfig::default();
let clip_w = self.clip_weights()?;
timing_log(&format!(
" vision.hydrate(batch) {:.2}s",
th.elapsed().as_secs_f64()
));
let mut groups: std::collections::BTreeMap<usize, Vec<(usize, usize)>> =
std::collections::BTreeMap::new();
for (p, views) in page_views.iter().enumerate() {
for (v, view) in views.iter().enumerate() {
let side = (view.cols as f64).sqrt() as usize;
if side * side != view.cols {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::vision_tower_batched_pages: page {p} view {v} \
cols {} is not a perfect square",
view.cols
)));
}
groups.entry(side).or_default().push((p, v));
}
}
let mut features: Vec<Vec<Option<Mat>>> = page_views
.iter()
.map(|views| views.iter().map(|_| None).collect())
.collect();
for (side, slots) in &groups {
let view_refs: Vec<&Mat> = slots.iter().map(|&(p, v)| &page_views[p][v]).collect();
let ts = Instant::now();
let sams = vision_sam::forward_with_batched(&statics.sam, &view_refs, *side, *side)?;
timing_log(&format!(
" vision.sam(batch of {}, side {side}) {:.2}s",
slots.len(),
ts.elapsed().as_secs_f64()
));
let tc = Instant::now();
let sam_refs: Vec<&Mat> = sams.iter().collect();
let clips = vision_clip::forward_batched_from_sam(&clip_cfg, clip_w, &sam_refs)?;
timing_log(&format!(
" vision.clip(batch of {}) {:.2}s",
slots.len(),
tc.elapsed().as_secs_f64()
));
let tb = Instant::now();
for ((&(p, v), sam), clip) in slots.iter().zip(&sams).zip(&clips) {
let projected = vision_bridge::forward_with(&statics.projector, clip, sam)?;
features[p][v] = Some(projected);
}
timing_log(&format!(
" vision.bridge(batch of {}) {:.2}s",
slots.len(),
tb.elapsed().as_secs_f64()
));
}
page_views.clear();
features
.into_iter()
.enumerate()
.map(|(p, views)| {
views
.into_iter()
.enumerate()
.map(|(v, slot)| {
slot.ok_or_else(|| {
FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::vision_tower_batched_pages: page {p} \
view {v} produced no feature (grouping bug)"
))
})
})
.collect()
})
.collect()
}
fn build_inputs_embeds(
&self,
pre: &Preprocessed,
vision_features: &[Mat],
) -> FocrResult<(Mat, Vec<u32>)> {
let (prompt_ids, images_seq_mask) = self.build_prompt(pre)?;
let mut inputs_embeds = self.embed_prompt(&prompt_ids)?;
let image_newline = Self::image_newline(&self.weights)?;
let view_seperator = Self::view_seperator(&self.weights)?;
if pre.crop_grid.is_tiled() {
let preprocess::PreprocessMode::Gundam { tile_size, .. } = pre.mode else {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::build_inputs_embeds: tiled crop grid requires Gundam preprocess mode"
)));
};
let local_count = pre.tiles.len();
let expected_feature_blocks = local_count.checked_add(1).ok_or_else(|| {
FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::build_inputs_embeds: local tile count overflow"
))
})?;
if vision_features.len() != expected_feature_blocks {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::build_inputs_embeds: {} vision feature blocks != {} local tiles + 1 global view",
vision_features.len(),
local_count
)));
}
let (locals, global_tail) = vision_features.split_at(local_count);
let q_local = preprocess::num_queries(tile_size);
connector::fuse_crop(
&self.weights,
&mut inputs_embeds,
locals,
pre.crop_grid.width_crop_num,
pre.crop_grid.height_crop_num,
q_local,
q_local,
&global_tail[0],
Self::global_grid_h(pre),
Self::global_grid_w(pre),
&image_newline,
&view_seperator,
&images_seq_mask,
)?;
} else {
connector::fuse_no_crop(
&self.weights,
&mut inputs_embeds,
vision_features,
Self::global_grid_h(pre),
Self::global_grid_w(pre),
&image_newline,
&view_seperator,
&images_seq_mask,
)?;
}
Ok((inputs_embeds, prompt_ids))
}
fn embed_table(&self) -> FocrResult<EmbedTable<'_>> {
embed_table_from(&self.weights)
}
fn page_runaway(&self) -> FocrResult<Option<sampler::PageRunaway>> {
if !sampler::RunawayGuard::from_env(false)?.enabled() {
return Ok(None);
}
let tok = self.tokenizer()?;
let ids = u32::try_from(tok.vocab_size()).unwrap_or(u32::MAX);
let numerals: Arc<[bool]> = (0..ids)
.map(|id| {
tok.id_to_token(id)
.is_some_and(sampler::PageRunaway::is_numeral_piece)
})
.collect();
Ok(Some(sampler::PageRunaway::new(numerals)))
}
fn embed_prompt(&self, prompt_ids: &[u32]) -> FocrResult<Mat> {
self.embed_table()?.embed_ids(prompt_ids)
}
fn generate(&self, inputs_embeds: Mat, prompt_ids: &[u32]) -> FocrResult<Vec<u32>> {
self.generate_with_observer(inputs_embeds, prompt_ids, &self.decode_params, None, true)
}
fn generate_with(
&self,
inputs_embeds: Mat,
prompt_ids: &[u32],
params: &sampler::DecodeParams,
) -> FocrResult<Vec<u32>> {
self.generate_with_observer(inputs_embeds, prompt_ids, params, None, false)
}
fn generate_with_observer(
&self,
inputs_embeds: Mat,
prompt_ids: &[u32],
params: &sampler::DecodeParams,
observer: Option<&mut dyn FnMut(u32)>,
guard_default: bool,
) -> FocrResult<Vec<u32>> {
let mut runaway_guard = sampler::RunawayGuard::from_env(guard_default)?;
let precision = DocumentPrecision::from_env()?;
let generated = if precision == DocumentPrecision::Stateless {
self.generate_stateless(
inputs_embeds,
prompt_ids,
params,
observer,
&mut runaway_guard,
)
} else if precision == DocumentPrecision::Mixed
&& (int8_decode_requested() || self.artifact_stores_gated_int8())
{
self.generate_cached_i8(
inputs_embeds,
prompt_ids,
params,
observer,
&mut runaway_guard,
)
} else {
self.generate_cached(
precision,
inputs_embeds,
prompt_ids,
params,
observer,
&mut runaway_guard,
)
}?;
let runaway = runaway_guard.evidence();
if let Some(evidence) = runaway {
timing_log(&evidence.describe());
}
if let Ok(mut slot) = self.last_decode.lock() {
let mut report = DecodeReport::v1_decode(generated.clone(), params, runaway.is_some());
if let Some(prior) = slot.as_ref() {
report.decodes += prior.decodes;
report.length_stops += prior.length_stops;
report.runaway_stops += prior.runaway_stops;
}
*slot = Some(report);
}
Ok(generated)
}
fn generate_cached(
&self,
precision: DocumentPrecision,
inputs_embeds: Mat,
prompt_ids: &[u32],
params: &sampler::DecodeParams,
mut observer: Option<&mut dyn FnMut(u32)>,
runaway_guard: &mut sampler::RunawayGuard,
) -> FocrResult<Vec<u32>> {
timing_log(&format!("precision {}", precision.name()));
let hidden_dim = inputs_embeds.cols;
let prefill_len = inputs_embeds.rows;
let table = self.embed_table()?;
let vocab = table.rows();
if table.cols() != hidden_dim {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_cached: embed table hidden {} != inputs_embeds hidden {}",
table.cols(),
hidden_dim
)));
}
let tb = Instant::now();
let wc = if precision == DocumentPrecision::CachedF32 {
self.decoder_cache_f32()?
} else {
self.decoder_cache()?
};
timing_log(&format!(
"weight_cache_build {:.2}s",
tb.elapsed().as_secs_f64()
));
let tp = Instant::now();
progress::emit("prefill", 0, prefill_len as u64);
let (hidden, mut caches) = decoder::prefill_with_cache(wc, &inputs_embeds)?;
progress::emit("prefill", prefill_len as u64, prefill_len as u64);
let mut last_hidden = Self::last_hidden_row(&hidden)?;
timing_log(&format!(
"prefill {:.2}s ({} tokens)",
tp.elapsed().as_secs_f64(),
prefill_len
));
let td = Instant::now();
decoder::prof::reset();
let mut generated: Vec<u32> = prompt_ids.to_vec();
let mut emitted: Vec<u32> = Vec::new();
while emitted.len() < params.max_length {
crate::cancel_checkpoint()?;
let logits = decoder::lm_head_cached(wc, &last_hidden)?;
let step: DecodeOutput = sampler::decode_step(&logits, &generated, params)?;
generated.push(step.token_id);
emitted.push(step.token_id);
progress::emit("decode", emitted.len() as u64, params.max_length as u64);
if let Some(f) = observer.as_deref_mut() {
f(step.token_id);
}
if step.is_eos || runaway_guard.stops_after_emit(&emitted, false) {
break;
}
let next = step.token_id as usize;
if next >= vocab {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_cached: decoded token id {next} outside embed vocab {vocab}"
)));
}
let row = table.row_f32(next);
let token_embed = Mat::from_vec(1, hidden_dim, row);
let position = prefill_len + (emitted.len() - 1);
let h = decoder::decode_step_with_cache(wc, &mut caches, &token_embed, position)?;
last_hidden = Self::last_hidden_row(&h)?;
}
timing_log(&format!(
"decode {:.2}s ({} tokens, {:.3}s/tok)",
td.elapsed().as_secs_f64(),
emitted.len(),
td.elapsed().as_secs_f64() / (emitted.len().max(1) as f64)
));
if decoder::prof::enabled() {
let (lmhead, attn, experts, route) = decoder::prof::snapshot_ms();
timing_log(&format!(
"decode phases (ms): lm_head {lmhead:.0} attn {attn:.0} experts {experts:.0} route {route:.0}"
));
}
if calib::enabled() {
calib::flush()?;
}
Ok(emitted)
}
fn generate_cached_i8(
&self,
inputs_embeds: Mat,
prompt_ids: &[u32],
params: &sampler::DecodeParams,
mut observer: Option<&mut dyn FnMut(u32)>,
runaway_guard: &mut sampler::RunawayGuard,
) -> FocrResult<Vec<u32>> {
self.require_full_int8_authorization()?;
timing_log("precision focr-full-int8");
let hidden_dim = inputs_embeds.cols;
let prefill_len = inputs_embeds.rows;
let table = self.embed_table()?;
let vocab = table.rows();
if table.cols() != hidden_dim {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_cached_i8: embed table hidden {} != inputs_embeds hidden {}",
table.cols(),
hidden_dim
)));
}
let tb = Instant::now();
let wc = self.decoder_cache_i8()?;
timing_log(&format!(
"weight_cache_build_i8 {:.2}s",
tb.elapsed().as_secs_f64()
));
let tp = Instant::now();
progress::emit("prefill", 0, prefill_len as u64);
let (hidden, mut caches) = decoder::prefill_with_cache_i8(wc, &inputs_embeds)?;
progress::emit("prefill", prefill_len as u64, prefill_len as u64);
let mut last_hidden = Self::last_hidden_row(&hidden)?;
timing_log(&format!(
"prefill_i8 {:.2}s ({} tokens)",
tp.elapsed().as_secs_f64(),
prefill_len
));
let td = Instant::now();
decoder::prof::reset();
let mut generated: Vec<u32> = prompt_ids.to_vec();
let mut emitted: Vec<u32> = Vec::new();
let fuse_ngram_lmhead = decoder::fuse_ngram_lmhead_enabled();
let spec_decode =
spec_decode_enabled() && params.matches_frozen_spec_ban() && observer.is_none();
if spec_decode {
self.spec_decode_i8(
wc,
&mut caches,
&last_hidden,
&mut generated,
&mut emitted,
&table,
vocab,
hidden_dim,
prefill_len,
params,
runaway_guard,
)?;
}
while !spec_decode && emitted.len() < params.max_length {
crate::cancel_checkpoint()?;
let step: DecodeOutput = if fuse_ngram_lmhead
&& params.no_repeat_ngram_size > 0
&& generated.len() >= params.no_repeat_ngram_size
{
let banned = sampler::collect_sliding_window_ngram_bans(
&generated,
params.no_repeat_ngram_size,
params.ngram_window,
&[],
sampler::VOCAB_SIZE,
);
let logits = decoder::lm_head_cached_i8_ngram_masked(wc, &last_hidden, &banned)?;
sampler::decode_step_premasked(&logits, params)?
} else {
let logits = decoder::lm_head_cached_i8(wc, &last_hidden)?;
sampler::decode_step(&logits, &generated, params)?
};
generated.push(step.token_id);
emitted.push(step.token_id);
progress::emit("decode", emitted.len() as u64, params.max_length as u64);
if let Some(f) = observer.as_deref_mut() {
f(step.token_id);
}
if step.is_eos || runaway_guard.stops_after_emit(&emitted, false) {
break;
}
let next = step.token_id as usize;
if next >= vocab {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_cached_i8: decoded token id {next} outside embed vocab {vocab}"
)));
}
let row = table.row_f32(next);
let token_embed = Mat::from_vec(1, hidden_dim, row);
let position = prefill_len + (emitted.len() - 1);
let h = decoder::decode_step_with_cache_i8(wc, &mut caches, &token_embed, position)?;
last_hidden = Self::last_hidden_row(&h)?;
}
timing_log(&format!(
"decode_i8 {:.2}s ({} tokens, {:.3}s/tok)",
td.elapsed().as_secs_f64(),
emitted.len(),
td.elapsed().as_secs_f64() / (emitted.len().max(1) as f64)
));
if decoder::prof::enabled() {
let (lmhead, attn, experts, route) = decoder::prof::snapshot_ms();
timing_log(&format!(
"decode_i8 phases (ms): lm_head {lmhead:.0} attn {attn:.0} experts {experts:.0} route {route:.0}"
));
}
if calib::enabled() {
calib::flush()?;
}
Ok(emitted)
}
#[allow(clippy::too_many_arguments)]
fn spec_decode_i8(
&self,
wc: &decoder::DecoderWeightCacheI8,
caches: &mut [rswa::RingCache],
last_hidden: &Mat,
generated: &mut Vec<u32>,
emitted: &mut Vec<u32>,
table: &EmbedTable<'_>,
vocab: usize,
hidden_dim: usize,
prefill_len: usize,
params: &sampler::DecodeParams,
runaway_guard: &mut sampler::RunawayGuard,
) -> FocrResult<()> {
let mut last_hidden = last_hidden.clone();
let (mut rounds, mut accepted) = (0_usize, 0_usize);
while emitted.len() < params.max_length {
crate::cancel_checkpoint()?;
progress::emit("decode", emitted.len() as u64, params.max_length as u64);
let draft = spec::draft_ngram(generated, spec::SPEC_DRAFT_MAX, spec::SPEC_DRAFT_NGRAM);
if draft.is_empty() {
let logits = decoder::lm_head_cached_i8(wc, &last_hidden)?;
let step = sampler::decode_step(&logits, generated, params)?;
generated.push(step.token_id);
emitted.push(step.token_id);
if step.is_eos || runaway_guard.stops_after_emit(emitted, false) {
break;
}
last_hidden = Self::commit_decode_token_i8(
wc,
caches,
table,
vocab,
hidden_dim,
prefill_len,
emitted.len(),
step.token_id,
)?;
continue;
}
let mut draft_embeds: Vec<Mat> = Vec::with_capacity(draft.len());
for &id in &draft {
draft_embeds.push(Self::embed_decode_token_i8(table, vocab, hidden_dim, id)?);
}
let base_position = prefill_len + emitted.len();
let verify_rows =
decoder::verify_forward_i8(wc, &*caches, &draft_embeds, base_position)?;
let mut verify_logits: Vec<Mat> = Vec::with_capacity(draft.len() + 1);
verify_logits.push(decoder::lm_head_cached_i8(wc, &last_hidden)?);
verify_logits.extend(verify_rows);
let emit = spec::resolve_round(generated, &draft, &verify_logits, params)?;
rounds += 1;
accepted += emit.accepted;
let mut stopped = false;
for &token in &draft[..emit.accepted] {
generated.push(token);
emitted.push(token);
let is_eos = params.eos_token_id == token;
if is_eos || runaway_guard.stops_after_emit(emitted, false) {
stopped = true;
break;
}
Self::commit_decode_token_i8(
wc,
caches,
table,
vocab,
hidden_dim,
prefill_len,
emitted.len(),
token,
)?;
if emitted.len() >= params.max_length {
stopped = true;
break;
}
}
if stopped {
break;
}
let Some(correction) = emit.correction else {
break;
};
generated.push(correction.token_id);
emitted.push(correction.token_id);
if correction.is_eos || runaway_guard.stops_after_emit(emitted, false) {
break;
}
last_hidden = Self::commit_decode_token_i8(
wc,
caches,
table,
vocab,
hidden_dim,
prefill_len,
emitted.len(),
correction.token_id,
)?;
}
timing_log(&format!("spec_decode rounds={rounds} accepted={accepted}"));
Ok(())
}
fn embed_decode_token_i8(
table: &EmbedTable<'_>,
vocab: usize,
hidden_dim: usize,
token: u32,
) -> FocrResult<Mat> {
let idx = token as usize;
if idx >= vocab {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_cached_i8: decoded token id {idx} outside embed vocab {vocab}"
)));
}
let row = table.row_f32(idx);
Ok(Mat::from_vec(1, hidden_dim, row))
}
#[allow(clippy::too_many_arguments)]
fn commit_decode_token_i8(
wc: &decoder::DecoderWeightCacheI8,
caches: &mut [rswa::RingCache],
table: &EmbedTable<'_>,
vocab: usize,
hidden_dim: usize,
prefill_len: usize,
emitted_len: usize,
token: u32,
) -> FocrResult<Mat> {
let token_embed = Self::embed_decode_token_i8(table, vocab, hidden_dim, token)?;
let position = prefill_len + (emitted_len - 1);
let h = decoder::decode_step_with_cache_i8(wc, caches, &token_embed, position)?;
Self::last_hidden_row(&h)
}
fn generate_stateless(
&self,
mut inputs_embeds: Mat,
prompt_ids: &[u32],
params: &sampler::DecodeParams,
mut observer: Option<&mut dyn FnMut(u32)>,
runaway_guard: &mut sampler::RunawayGuard,
) -> FocrResult<Vec<u32>> {
let hidden_dim = inputs_embeds.cols;
let table = self.embed_table()?;
let vocab = table.rows();
if table.cols() != hidden_dim {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_stateless: embed table hidden {} != inputs_embeds hidden {}",
table.cols(),
hidden_dim
)));
}
let mut generated: Vec<u32> = prompt_ids.to_vec();
let mut emitted: Vec<u32> = Vec::new();
while emitted.len() < params.max_length {
crate::cancel_checkpoint()?;
let hidden = decoder::forward(&self.weights, &inputs_embeds)?;
let last_hidden = Self::last_hidden_row(&hidden)?;
let logits = decoder::lm_head(&self.weights, &last_hidden)?;
let step: DecodeOutput = sampler::decode_step(&logits, &generated, params)?;
generated.push(step.token_id);
emitted.push(step.token_id);
progress::emit("decode", emitted.len() as u64, params.max_length as u64);
if let Some(f) = observer.as_deref_mut() {
f(step.token_id);
}
if step.is_eos || runaway_guard.stops_after_emit(&emitted, false) {
break;
}
let next = step.token_id as usize;
if next >= vocab {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate_stateless: decoded token id {next} outside embed vocab {vocab}"
)));
}
let new_rows = inputs_embeds.rows + 1;
let mut data = std::mem::take(&mut inputs_embeds.data);
data.extend_from_slice(&table.row_f32(next));
inputs_embeds = Mat::from_vec(new_rows, hidden_dim, data);
}
Ok(emitted)
}
fn last_hidden_row(hidden: &Mat) -> FocrResult<Mat> {
if hidden.rows == 0 {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate: decoder forward returned zero hidden rows"
)));
}
let expected_len = hidden.rows.checked_mul(hidden.cols).ok_or_else(|| {
FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate: decoder hidden shape product overflow for [{}, {}]",
hidden.rows,
hidden.cols
))
})?;
if hidden.data.len() != expected_len {
return Err(FocrError::Other(anyhow::anyhow!(
"native_engine::OcrModel::generate: decoder hidden data len {} != rows*cols {} for shape [{}, {}]",
hidden.data.len(),
expected_len,
hidden.rows,
hidden.cols
)));
}
Ok(Mat::from_vec(
1,
hidden.cols,
hidden.row(hidden.rows - 1).to_vec(),
))
}
fn image_dims(pre: &Preprocessed) -> (u32, u32) {
pre.original_size
}
fn views(pre: &Preprocessed) -> Vec<Mat> {
let mut views = Vec::with_capacity(pre.num_views());
views.extend(pre.tiles.iter().map(|tile| tile.pixels.clone()));
views.push(pre.global.pixels.clone());
views
}
fn build_prompt(&self, pre: &Preprocessed) -> FocrResult<(Vec<u32>, Vec<bool>)> {
if self.arch().decode_contract().pages.is_some() {
return self.build_prompt_multi(std::slice::from_ref(pre));
}
let tok = self.tokenizer()?;
let n_image = pre.placeholder_token_count();
let text = tok.encode(BASE_PROMPT_TEXT)?;
let total = 1 + n_image + text.len();
let mut ids = Vec::with_capacity(total);
let mut mask = Vec::with_capacity(total);
ids.push(tok.bos_id());
mask.push(false);
for _ in 0..n_image {
ids.push(tok.image_id());
mask.push(true);
}
for id in text {
ids.push(id);
mask.push(false);
}
Ok((ids, mask))
}
fn build_prompt_multi(&self, pres: &[Preprocessed]) -> FocrResult<(Vec<u32>, Vec<bool>)> {
let tok = self.tokenizer()?;
let text = tok.encode(MULTI_PAGE_PROMPT_TEXT)?;
if let Some(pages) = self.arch().decode_contract().pages {
let blocks: Vec<usize> = pres
.iter()
.map(Preprocessed::placeholder_token_count)
.collect();
return Ok(paged_prompt(
tok.bos_id(),
pages.marker_id,
tok.image_id(),
&blocks,
&text,
));
}
let n_image: usize = pres.iter().map(Preprocessed::placeholder_token_count).sum();
let total = 1 + n_image + text.len();
let mut ids = Vec::with_capacity(total);
let mut mask = Vec::with_capacity(total);
ids.push(tok.bos_id());
mask.push(false);
for pre in pres {
for _ in 0..pre.placeholder_token_count() {
ids.push(tok.image_id());
mask.push(true);
}
}
for id in text {
ids.push(id);
mask.push(false);
}
Ok((ids, mask))
}
fn global_grid_h(pre: &Preprocessed) -> usize {
preprocess::num_queries(pre.mode.base_size())
}
fn global_grid_w(pre: &Preprocessed) -> usize {
preprocess::num_queries(pre.mode.base_size())
}
fn image_newline(weights: &Weights) -> FocrResult<Vec<f32>> {
weights.vec("model.image_newline")
}
fn view_seperator(weights: &Weights) -> FocrResult<Vec<f32>> {
weights.vec("model.view_seperator")
}
}
#[cfg(test)]
pub(crate) fn write_sparse_v2_stand_in(dir: &Path) -> PathBuf {
let (tensors, int64) = unlimited_ocr_census::v2_source_tensors();
tests::write_sparse_two_shard_stand_in(dir, &tensors, &int64)
}
#[cfg(test)]
mod tests {
use super::*;
fn list_origins(pages: usize) -> Vec<PageOrigin> {
(1..=pages)
.map(|source_page| PageOrigin {
source_page,
half: None,
})
.collect()
}
#[test]
fn a_chart_warns_unless_its_text_passed_the_head_check() {
let meta = |distance, reliable| ChartPageMeta {
pred_locs: vec![0.25, 1.0],
reliable_distance: distance,
reliable,
};
assert_eq!(chart_warning(&meta(Some(0.01), Some(true))), None);
let unverifiable = chart_warning(&meta(None, None)).expect("no verdict warns");
assert_eq!(unverifiable.kind, "chart_unverified");
assert!(RUN_WARNING_KINDS.contains(&unverifiable.kind));
assert!(unverifiable.message.contains("cannot be verified"));
let refuted = chart_warning(&meta(Some(0.5), Some(false))).expect("a refuted text warns");
assert!(refuted.message.contains("disagrees"));
}
fn multi_page_report(
finish_reason: FinishReason,
cap: Option<LengthCap>,
expected: usize,
decoded: &str,
) -> DecodeReport {
DecodeReport {
ids: Vec::new(),
finish_reason,
cap,
pages: Some(PageBodies {
expected,
bodies: postprocess::page_body_count(decoded),
}),
max_length: 32_768,
max_positions: MAX_POSITION_EMBEDDINGS,
decodes: 1,
length_stops: usize::from(finish_reason != FinishReason::Stop),
runaway_stops: usize::from(finish_reason == FinishReason::Runaway),
document: None,
admission_warnings: Vec::new(),
}
}
#[test]
fn gundam_is_refused_for_a_v2_document_and_warns_on_a_v2_page() {
let base = preprocess::PreprocessMode::Base { base_size: 1024 };
assert_eq!(
paged_document_view(base, "unlimited-ocr-2").expect("base"),
base
);
let gundam = preprocess::PreprocessMode::Gundam {
base_size: 1024,
tile_size: 640,
};
let error = paged_document_view(gundam, "unlimited-ocr-2").expect_err("refused");
assert!(matches!(error, FocrError::Usage(_)), "{error}");
assert!(
error.to_string().contains("unlimited-ocr-2 document"),
"{error}"
);
let report = |gundam_view: bool| DecodeReport {
ids: Vec::new(),
finish_reason: FinishReason::Stop,
cap: None,
pages: Some(PageBodies {
expected: 1,
bodies: 1,
}),
max_length: 32_768,
max_positions: sampler::UOCR2_MAX_POSITIONS,
decodes: 1,
length_stops: 0,
runaway_stops: 0,
document: Some(DocumentStats {
gundam_view,
leading_text: true,
..DocumentStats::default()
}),
admission_warnings: Vec::new(),
};
let kinds = |r: &DecodeReport| {
decode_warnings(r)
.iter()
.map(|w| w.kind)
.collect::<Vec<_>>()
};
assert_eq!(kinds(&report(false)), ["leading_text"]);
assert_eq!(
kinds(&report(true)),
["uncertified_crop_mode", "leading_text"]
);
let warning = decode_warnings(&report(true)).remove(0);
assert_eq!(
warning.recovery,
warning_recovery(WarningKind::UncertifiedCropMode)
);
assert!(RUN_WARNING_KINDS.contains(&warning.kind));
}
#[test]
fn document_reports_warn_in_v2_terms_and_only_a_stopped_one_is_partial() {
let lever = RunWarning {
kind: "ignored_lever",
message: "FOCR_QKV_FUSED has no effect".into(),
recovery: warning_recovery(WarningKind::IgnoredLever),
fields: serde_json::Map::new(),
};
let report =
|finish_reason: FinishReason, bodies: usize, leading_text: bool| DecodeReport {
ids: Vec::new(),
finish_reason,
cap: (finish_reason == FinishReason::Length).then_some(LengthCap::Positions),
pages: Some(PageBodies {
expected: 3,
bodies,
}),
max_length: 32_768,
max_positions: sampler::UOCR2_MAX_POSITIONS,
decodes: 1,
length_stops: usize::from(finish_reason != FinishReason::Stop),
runaway_stops: 0,
document: Some(DocumentStats {
leading_text,
..DocumentStats::default()
}),
admission_warnings: vec![lever.clone()],
};
let model = ModelIdentity {
model_id: model_arch::UNLIMITED_OCR_V2_ID,
source_sha256: None,
};
assert_eq!(model.version_tag(), "unlimited-ocr-2");
let done = report(FinishReason::Stop, 3, false);
let warnings = decode_warnings(&done);
assert_eq!(warnings, std::slice::from_ref(&lever));
assert!(partial_output(&done, &warnings, &model).is_none());
let cut = report(FinishReason::Length, 2, true);
let warnings = decode_warnings(&cut);
let kinds: Vec<&str> = warnings.iter().map(|w| w.kind).collect();
assert_eq!(kinds, ["ignored_lever", "leading_text", "v2_length"]);
assert!(kinds.iter().all(|kind| RUN_WARNING_KINDS.contains(kind)));
let json = warnings[2].to_json();
assert_eq!(
(json["expected"].as_u64(), json["got"].as_u64()),
(Some(3), Some(2))
);
assert!(
warnings[2]
.message
.starts_with("stopped inside page 2 of 3")
&& warnings[2].message.contains("131,072"),
"{}",
warnings[2].message
);
let Some(FocrError::Partial(partial)) = partial_output(&cut, &warnings, &model) else {
panic!("a length stop is a partial output");
};
assert_eq!(
(
partial.finish_reason,
partial.pages_out,
partial.warnings.len()
),
("length", 2, 3)
);
assert_eq!(
(partial.model_id, partial.source_sha256.as_deref()),
("unlimited-ocr-2", None)
);
assert_eq!(cut.run_finish_reason(), FinishReason::Length);
let late = report(FinishReason::Budget, 0, false);
let warnings = decode_warnings(&late);
assert_eq!(warnings[1].kind, "v2_budget");
assert!(
warnings[1]
.message
.starts_with("stopped before page 1 of 3"),
"{}",
warnings[1].message
);
assert_eq!(late.run_finish_reason(), FinishReason::Budget);
let Some(error) = partial_output(&late, &warnings, &model) else {
panic!("a budget stop is a partial output");
};
assert_eq!(error.exit_code(), crate::error::EXIT_TIMEOUT);
let FocrError::Partial(partial) = &error else {
panic!("{error:?}");
};
assert_eq!((partial.finish_reason, partial.pages_out), ("budget", 0));
let v1 = multi_page_report(
FinishReason::Length,
Some(LengthCap::Positions),
3,
"<PAGE>\na\n<PAGE>\nb",
);
let warnings = decode_warnings(&v1);
assert_eq!(warnings[0].kind, "v1_truncated");
let v1_model = ModelIdentity {
model_id: model_arch::UNLIMITED_OCR_V1_ID,
source_sha256: Some("ab".repeat(32)),
};
assert_eq!(
v1_model.version_tag(),
format!("unlimited-ocr@{}", "ab".repeat(32))
);
assert!(
partial_output(&v1, &warnings, &v1_model).is_none(),
"v1 stays exit 0"
);
}
#[test]
fn a_v1_runaway_is_a_partial_output_with_its_text_written() {
let v1_model = ModelIdentity {
model_id: model_arch::UNLIMITED_OCR_V1_ID,
source_sha256: None,
};
let single = DecodeReport {
pages: None,
..multi_page_report(FinishReason::Runaway, None, 1, "")
};
assert_eq!(single.run_finish_reason(), FinishReason::Runaway);
let warnings = decode_warnings(&single);
assert!(warnings.is_empty());
let Some(error) = partial_output(&single, &warnings, &v1_model) else {
panic!("a runaway stop is a partial output");
};
assert_eq!(error.exit_code(), crate::error::EXIT_TIMEOUT);
assert!(error.remediation().contains("FOCR_RUNAWAY_GUARD=0"));
let FocrError::Partial(partial) = &error else {
panic!("{error:?}");
};
assert_eq!((partial.finish_reason, partial.pages_out), ("runaway", 1));
assert_eq!(
partial.message,
"the runaway guard stopped the decode because its output kept repeating; the text \
before that point was written"
);
let pdf = DecodeReport {
decodes: 4,
..single.clone()
};
let Some(FocrError::Partial(partial)) = partial_output(&pdf, &[], &v1_model) else {
panic!("one runaway page of four is a partial output");
};
assert_eq!(partial.pages_out, 4);
assert!(
partial
.message
.starts_with("the runaway guard stopped 1 of 4 decodes"),
"{}",
partial.message
);
let mixed = DecodeReport {
decodes: 2,
length_stops: 2,
..single.clone()
};
assert_eq!(mixed.run_finish_reason(), FinishReason::Runaway);
let capped = DecodeReport {
finish_reason: FinishReason::Length,
runaway_stops: 0,
..mixed
};
assert_eq!(capped.run_finish_reason(), FinishReason::Length);
assert!(partial_output(&capped, &[], &v1_model).is_none());
let multi = multi_page_report(FinishReason::Runaway, None, 3, "<PAGE>\na\n<PAGE>\nb");
assert!(decode_warnings(&multi).is_empty());
let Some(FocrError::Partial(partial)) = partial_output(&multi, &[], &v1_model) else {
panic!("a multi-page runaway is a partial output");
};
assert_eq!((partial.finish_reason, partial.pages_out), ("runaway", 2));
}
#[test]
fn a_v1_decode_report_names_eos_runaway_and_limit_alike_for_both_drivers() {
let params = sampler::DecodeParams::single_image();
let eos = params.eos_token_id;
let v1_model = ModelIdentity {
model_id: model_arch::UNLIMITED_OCR_V1_ID,
source_sha256: None,
};
for ran_away in [false, true] {
let stop = DecodeReport::v1_decode(vec![5, 6, eos], ¶ms, ran_away);
assert_eq!(stop.finish_reason, FinishReason::Stop);
assert_eq!(stop.cap, None);
assert_eq!(
(stop.decodes, stop.length_stops, stop.runaway_stops),
(1, 0, 0)
);
assert!(partial_output(&stop, &[], &v1_model).is_none());
}
let limit = DecodeReport::v1_decode(vec![5, 6, 7], ¶ms, false);
assert_eq!(limit.finish_reason, FinishReason::Length);
assert_eq!(limit.cap, Some(LengthCap::MaxLength));
assert_eq!((limit.length_stops, limit.runaway_stops), (1, 0));
assert_eq!(limit.max_length, params.max_length);
assert!(partial_output(&limit, &[], &v1_model).is_none());
let runaway = DecodeReport::v1_decode(vec![5, 6, 7], ¶ms, true);
assert_eq!(runaway.ids, vec![5, 6, 7]);
assert_eq!(runaway.finish_reason, FinishReason::Runaway);
assert_eq!(runaway.cap, None);
assert_eq!((runaway.length_stops, runaway.runaway_stops), (1, 1));
assert_eq!(runaway.run_finish_reason(), FinishReason::Runaway);
let Some(FocrError::Partial(partial)) = partial_output(&runaway, &[], &v1_model) else {
panic!("a runaway page fails after its text is written");
};
assert_eq!((partial.finish_reason, partial.pages_out), ("runaway", 1));
}
#[test]
fn page_reread_warning_lists_replaced_and_kept_pages() {
assert!(page_reread_warning(&[], &[]).is_none());
let one = page_reread_warning(&[3], &[]).expect("a replaced page warns");
let w = one.to_json();
assert_eq!(w["kind"], "v2_page_reread");
assert_eq!(w["pages"], serde_json::json!([3]));
assert_eq!(w["kept"], serde_json::json!([]));
assert_eq!(w["recovery"], warning_recovery(WarningKind::V2PageReread));
assert_eq!(
one.message,
"page 3 was read again alone, and the re-read replaced the capped text"
);
let mixed = page_reread_warning(
&[2, 5],
&[(7, "its re-read reached the cap again".to_owned())],
)
.expect("re-read pages warn");
let w = mixed.to_json();
assert_eq!(w["pages"], serde_json::json!([2, 5]));
assert_eq!(w["kept"], serde_json::json!([7]));
assert_eq!(
mixed.message,
"pages 2, 5 were read again alone, and the re-read replaced the capped text; \
page 7 kept its capped text: its re-read reached the cap again"
);
let kept = page_reread_warning(&[], &[(4, "its re-read ended `budget`".to_owned())])
.expect("a kept page warns");
assert_eq!(kept.to_json()["pages"], serde_json::json!([]));
assert_eq!(kept.to_json()["kept"], serde_json::json!([4]));
assert!(RUN_WARNING_KINDS.contains(&"v2_page_reread"));
}
#[test]
fn only_the_capped_page_is_read_again_and_spliced_in() {
use PageFinish::{Eos, Marker, PageCap};
let page = |k: usize, body: &str, finish| PageResult {
finish: Some(finish),
..PageResult::read(
k,
body,
(100, 100),
PageOrigin {
source_page: k,
half: None,
},
model_arch::UNLIMITED_OCR_V2_ID,
)
};
let capped = || {
DocumentPages::joined(vec![
page(1, "First page.", Marker),
page(2, "814 815 816 817 818", PageCap),
page(3, "Third page.", Marker),
])
};
let mut calls = Vec::new();
let (document, warning) = reread_capped_pages(capped(), |p| {
calls.push(p.run_index);
Ok(page(p.run_index, "A table, read alone.", Eos))
});
assert_eq!(calls, [2], "one re-read, of page 2");
let before = capped();
assert_eq!(document.pages[0], before.pages[0]);
assert_eq!(document.pages[1], page(2, "A table, read alone.", Eos));
assert_eq!(document.pages[2], before.pages[2]);
let joined = DocumentPages::joined(document.pages.clone());
assert_eq!(document.markdown, joined.markdown);
assert_eq!(document.raw, joined.raw);
assert!(!document.markdown.contains("814") && !document.raw.contains("814"));
let w = warning.expect("a replaced page warns").to_json();
assert_eq!(
(&w["pages"], &w["kept"]),
(&serde_json::json!([2]), &serde_json::json!([]))
);
let (kept, warning) = reread_capped_pages(capped(), |_| {
Err("its re-read reached the cap again".to_owned())
});
assert_eq!(kept.pages, before.pages);
assert_eq!((kept.markdown, kept.raw), (before.markdown, before.raw));
let w = warning.expect("a kept page warns").to_json();
assert_eq!(
(&w["pages"], &w["kept"]),
(&serde_json::json!([]), &serde_json::json!([2]))
);
let plain =
|| DocumentPages::joined(vec![page(1, "First page.", Marker), page(2, "Last.", Eos)]);
let (same, warning) = reread_capped_pages(plain(), |_| panic!("no capped page to read"));
assert_eq!(
(same.pages, same.markdown),
(plain().pages, plain().markdown)
);
assert!(warning.is_none());
let ran_away = DocumentPages::joined(vec![
page(1, "First page.", Marker),
page(2, "814 815 816 817 818", PageFinish::Runaway),
]);
let mut calls = Vec::new();
let (document, warning) = reread_capped_pages(ran_away, |p| {
calls.push(p.run_index);
Ok(page(p.run_index, "A table, read alone.", Eos))
});
assert_eq!(calls, [2]);
assert_eq!(document.pages[1], page(2, "A table, read alone.", Eos));
assert_eq!(
warning.expect("a replaced page warns").to_json()["pages"],
serde_json::json!([2])
);
}
#[test]
fn page_capped_pages_warn_once_and_the_run_stays_complete() {
use PageFinish::{Eos, Marker, PageCap};
let report = |page_finish: Vec<PageFinish>| DecodeReport {
ids: Vec::new(),
finish_reason: FinishReason::Stop,
cap: None,
pages: Some(PageBodies {
expected: 5,
bodies: 5,
}),
max_length: 32_768,
max_positions: sampler::UOCR2_MAX_POSITIONS,
decodes: 1,
length_stops: 0,
runaway_stops: 0,
document: Some(DocumentStats {
page_finish,
..DocumentStats::default()
}),
admission_warnings: Vec::new(),
};
let capped = report(vec![Marker, Marker, PageCap, Marker, Eos]);
let warnings = decode_warnings(&capped);
assert_eq!(warnings.len(), 1);
let w = warnings[0].to_json();
assert_eq!(w["kind"], "v2_page_cap");
assert_eq!(w["pages"], serde_json::json!([3]));
assert_eq!(w["recovery"], warning_recovery(WarningKind::V2PageCap));
assert!(
warnings[0].message.starts_with("page 3 of 5 reached"),
"{}",
warnings[0].message
);
assert!(RUN_WARNING_KINDS.contains(&"v2_page_cap"));
let model = ModelIdentity {
model_id: model_arch::UNLIMITED_OCR_V2_ID,
source_sha256: None,
};
assert!(partial_output(&capped, &warnings, &model).is_none());
let two = decode_warnings(&report(vec![PageCap, Marker, Marker, PageCap, Eos]));
assert_eq!(two.len(), 1);
assert_eq!(two[0].to_json()["pages"], serde_json::json!([1, 4]));
assert!(
two[0].message.starts_with("pages 1, 4 of 5 reached"),
"{}",
two[0].message
);
assert!(two[0].to_json()["runaway"].is_null());
let mixed = decode_warnings(&report(vec![
PageFinish::Runaway,
PageCap,
Marker,
Marker,
Eos,
]));
assert_eq!(mixed.len(), 1);
let w = mixed[0].to_json();
assert_eq!(
(&w["pages"], &w["runaway"]),
(&serde_json::json!([1, 2]), &serde_json::json!([1]))
);
assert_eq!(
mixed[0].message,
"page 2 of 5 reached the per-page token cap and was closed; page 1 of 5 was closed \
by the runaway guard: the model probably repeated itself, so that text is unreliable"
);
assert!(decode_warnings(&report(vec![Marker, Marker, Marker, Marker, Eos])).is_empty());
}
#[test]
fn recorded_multi_page_outputs_are_page_merges() {
for (name, expected, got) in [("p20", 20, 7), ("p10", 10, 9), ("p9_p14", 2, 1)] {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join(format!("tests/fixtures/multi_page/{name}_raw.md"));
let raw = std::fs::read_to_string(&path).expect("recorded oracle output");
let report = multi_page_report(FinishReason::Stop, None, expected, &raw);
let warnings = decode_warnings(&report);
assert_eq!(warnings.len(), 1, "{name}");
let w = warnings[0].to_json();
assert_eq!(w["kind"], "v1_page_merge", "{name}");
assert_eq!(
(w["expected"].as_u64(), w["got"].as_u64()),
(Some(expected as u64), Some(got)),
"{name}"
);
assert_eq!(
w["message"],
format!(
"v1 produced {got} page bodies for {expected} pages; page boundaries in this \
output are unreliable"
)
);
assert_eq!(w["recovery"], warning_recovery(WarningKind::V1PageMerge));
let origins = list_origins(expected);
let frame = PageFrame {
sizes: vec![(999, 999); expected],
origins: &origins,
model_id: model_arch::UNLIMITED_OCR_V1_ID,
};
assert!(
frame.text_pages(&postprocess::strip_eos(&raw)).is_empty(),
"{name}"
);
}
}
#[test]
fn extra_page_bodies_warn_and_an_exact_count_does_not() {
let decoded = "<PAGE>a<PAGE>b<PAGE>c";
for (finish, cap) in [
(FinishReason::Stop, None),
(FinishReason::Length, None),
(FinishReason::Length, Some(LengthCap::MaxLength)),
] {
let warnings = decode_warnings(&multi_page_report(finish, cap, 2, decoded));
assert_eq!(warnings.len(), 1, "{finish:?} {cap:?}");
let w = warnings[0].to_json();
assert_eq!(w["kind"], "v1_extra_pages");
assert_eq!(
(w["expected"].as_u64(), w["got"].as_u64()),
(Some(2), Some(3))
);
assert_eq!(w["recovery"], warning_recovery(WarningKind::V1ExtraPages));
assert!(RUN_WARNING_KINDS.contains(&"v1_extra_pages"));
}
let exact = multi_page_report(FinishReason::Stop, None, 3, decoded);
assert!(decode_warnings(&exact).is_empty());
}
#[test]
fn v1_text_has_pages_only_with_one_body_per_page() {
let origins = list_origins(3);
let frame = PageFrame {
sizes: vec![(999, 999); 3],
origins: &origins,
model_id: model_arch::UNLIMITED_OCR_V1_ID,
};
for text in ["<PAGE>a<PAGE>b<PAGE>c", "a<PAGE>b<PAGE>c"] {
let pages = frame.text_pages(text);
let summary: Vec<_> = pages
.iter()
.map(|p| {
(
p.run_index,
p.source_page,
p.text.as_str(),
p.tokens,
p.finish,
)
})
.collect();
assert_eq!(
summary,
[
(1, 1, "a", None, None),
(2, 2, "b", None, None),
(3, 3, "c", None, None),
],
"{text:?}"
);
assert_eq!(
postprocess::join_page_texts(pages.iter().map(|p| &p.text)),
postprocess::finalize_multi(text, 3).unwrap(),
"{text:?}"
);
}
for merged in ["<PAGE>a<PAGE>b", "<PAGE>a<PAGE>b<PAGE>c<PAGE>d", ""] {
assert!(frame.text_pages(merged).is_empty(), "{merged:?}");
}
}
#[test]
fn document_pages_map_to_their_source_pages() {
use crate::tokenizer::special::PAGE;
use crate::tokenizer::tests::{byte_id, byte_tokenizer};
let t = byte_tokenizer();
let text = |s: &str| s.bytes().map(byte_id).collect::<Vec<u32>>();
let figure = "<BLOCK>image[0, 0, 999, 499]<FORK>";
let ids = [
text("hi "),
vec![PAGE],
text("one"),
vec![PAGE],
text(figure),
vec![PAGE],
text("three"),
vec![PAGE],
]
.concat();
let decoded = DecodedDocument {
split: postprocess::split_pages_by_token(&t, &ids, PAGE, 3).unwrap(),
finish: vec![PageFinish::Marker, PageFinish::PageCap, PageFinish::Marker],
elapsed: [5, 6, 7].map(std::time::Duration::from_millis).to_vec(),
};
let origins = [
PageOrigin {
source_page: 1,
half: None,
},
PageOrigin {
source_page: 3,
half: Some(Half::Left),
},
PageOrigin {
source_page: 3,
half: Some(Half::Right),
},
];
let frame = PageFrame {
sizes: vec![(999, 999), (1998, 2997), (999, 999)],
origins: &origins,
model_id: model_arch::UNLIMITED_OCR_V2_ID,
};
let pages = frame.token_pages(&decoded);
let summary: Vec<_> = pages
.iter()
.map(|p| {
(
(p.run_index, p.source_page, p.half),
p.text.as_str(),
(p.tokens, p.finish, p.elapsed_ms),
)
})
.collect();
assert_eq!(
summary,
[
(
(1, 1, None),
"hi one",
(Some(6), Some(PageFinish::Marker), Some(5))
),
(
(2, 3, Some(Half::Left)),
"\n",
(Some(figure.len()), Some(PageFinish::PageCap), Some(6))
),
(
(3, 3, Some(Half::Right)),
"three",
(Some(5), Some(PageFinish::Marker), Some(7))
),
]
);
assert!(
pages
.iter()
.all(|p| p.window.is_none() && p.model_id == model_arch::UNLIMITED_OCR_V2_ID)
);
assert_eq!(pages[1].layout[0].boxes, [[0, 0, 1998, 1497]]);
assert_eq!(pages[1].figures[0].boxes, [[0, 0, 1998, 1497]]);
assert_eq!(pages[1].figures[0].markdown_ref, "");
assert!(pages[0].layout.is_empty() && pages[2].figures.is_empty());
let mut stream = postprocess::TokenPageStream::new(&t, PAGE, 3);
let mut streamed = Vec::new();
let mut close = |index: usize, body: &str, tokens: usize| {
streamed.push(frame.closed_page(&ClosedPage {
index,
body,
tokens,
finish: decoded.finish[index - 1],
elapsed: decoded.elapsed[index - 1],
}));
};
for &id in &ids {
stream.push(id, &mut close);
}
stream.finish(&mut close);
assert_eq!(streamed, pages, "streamed pages are the final pages");
}
#[test]
fn healthy_multi_page_outputs_do_not_warn() {
for decoded in ["<PAGE>a<PAGE>b<PAGE>c", "lead text<PAGE>b<PAGE>c"] {
let report = multi_page_report(FinishReason::Stop, None, 3, decoded);
assert_eq!(report.pages.map(|p| p.bodies), Some(3), "{decoded}");
assert!(decode_warnings(&report).is_empty(), "{decoded}");
}
let mut single = multi_page_report(FinishReason::Length, Some(LengthCap::MaxLength), 1, "");
single.pages = None;
assert!(decode_warnings(&single).is_empty());
assert_eq!(single.run_finish_reason(), FinishReason::Length);
}
#[test]
fn truncations_name_their_cap() {
let positions = decode_warnings(&multi_page_report(
FinishReason::Length,
Some(LengthCap::Positions),
3,
"<PAGE>one<PAGE>tw",
));
let w = positions[0].to_json();
assert_eq!(
(w["kind"].as_str(), w["cause"].as_str()),
(Some("v1_truncated"), Some("positions"))
);
assert_eq!(
(w["expected"].as_u64(), w["got"].as_u64()),
(Some(3), Some(2))
);
assert_eq!(
w["message"],
"stopped after 2 of 3 pages: v1 shares 32,768 positions between prompt and output \
(≈29 dense pages)"
);
assert_eq!(
w["recovery"],
"split the document with `--pages`, parse pages independently (omit `--multi-page`), \
or read it with Unlimited-OCR-2, which reads a document of any length in windows: \
`focr pull unlimited-ocr-2`, then `--model unlimited-ocr-2`"
);
let mut capped = multi_page_report(
FinishReason::Length,
Some(LengthCap::MaxLength),
3,
"<PAGE>one",
);
capped.max_length = 1200;
let w = decode_warnings(&capped)[0].to_json();
assert_eq!(
(w["kind"].as_str(), w["cause"].as_str()),
(Some("v1_truncated"), Some("user_cap"))
);
assert_eq!(
w["message"],
"stopped after 1 of 3 pages: the --max-length cap of 1,200 generated tokens was reached"
);
assert_eq!(w["recovery"], "raise `--max-length`");
}
#[test]
fn test_position_override_has_the_shipping_default() {
if std::env::var_os(TEST_MAX_POSITIONS_ENV).is_none() {
assert!(validate_test_max_positions().is_ok());
assert_eq!(
decode_params_from_env(DecodeOverrides::default()).max_positions,
MAX_POSITION_EMBEDDINGS
);
}
assert_eq!(thousands(32_768), "32,768");
assert_eq!(thousands(999), "999");
assert_eq!(thousands(1_000_000), "1,000,000");
}
fn synthetic_int8_embed_artifact(rows: usize, cols: usize) -> Weights {
let arch = model_arch::arch_by_id("got-ocr2").expect("test architecture is registered");
let payload: Vec<u8> = (0..rows * cols)
.map(|i| (((i * 37 + 11) % 255) as i64 - 127) as i8 as u8)
.collect();
let scales: Vec<u8> = (0..rows)
.flat_map(|r| (((r % 13) as f32) * 0.0007 + 0.0031).to_le_bytes())
.collect();
let mut builder = crate::quant::focrq::FocrqBuilder::new()
.with_model_id(arch.id())
.with_license_notice(arch.license_notice());
builder
.add_quantized(
EMBED_TOKENS,
crate::quant::focrq::WriteDType::QInt8PerChan,
vec![rows, cols],
payload,
scales,
0,
0,
)
.expect("valid synthetic QInt8 embedding record");
Weights::from_bytes(builder.build()).expect("synthetic artifact loads")
}
#[test]
fn int8_embed_row_lookup_matches_full_dequant() {
let (rows, cols) = (67usize, 16usize);
let weights = synthetic_int8_embed_artifact(rows, cols);
let full = weights.mat(EMBED_TOKENS).expect("whole-table widen");
assert_eq!(full.shape(), (rows, cols));
let table = embed_table_from(&weights).expect("int8 embed table resolves");
assert!(
matches!(table, EmbedTable::QInt8 { .. }),
"a QInt8PerChan table must take the zero-copy int8 arm"
);
assert_eq!((table.rows(), table.cols()), (rows, cols));
for r in 0..rows {
assert_eq!(
table.row_f32(r),
full.data[r * cols..(r + 1) * cols].to_vec(),
"per-row dequant of row {r} must be bit-identical to the full dequant"
);
}
let ids: Vec<u32> = vec![0, 5, 66, 5, 42];
let gathered = table.embed_ids(&ids).expect("gather over valid ids");
assert_eq!(gathered.shape(), (ids.len(), cols));
for (i, &id) in ids.iter().enumerate() {
let want = &full.data[id as usize * cols..(id as usize + 1) * cols];
assert_eq!(&gathered.data[i * cols..(i + 1) * cols], want, "id {id}");
}
assert!(
table.embed_ids(&[rows as u32]).is_err(),
"an out-of-vocab id must still error"
);
}
#[test]
fn forward_admission_serializes_concurrent_callers() {
use std::sync::Barrier;
use std::sync::atomic::{AtomicUsize, Ordering};
let start = Barrier::new(5);
let live = AtomicUsize::new(0);
let max_live = AtomicUsize::new(0);
std::thread::scope(|scope| {
let mut workers = Vec::new();
for _ in 0..4 {
workers.push(scope.spawn(|| {
start.wait();
let _permit = forward_admission().acquire();
let now = live.fetch_add(1, Ordering::SeqCst) + 1;
max_live.fetch_max(now, Ordering::SeqCst);
std::thread::sleep(std::time::Duration::from_millis(5));
live.fetch_sub(1, Ordering::SeqCst);
}));
}
start.wait();
for worker in workers {
worker.join().expect("gate contender did not panic");
}
});
assert_eq!(max_live.load(Ordering::SeqCst), 1);
assert_eq!(live.load(Ordering::SeqCst), 0);
}
#[test]
fn fallible_once_initializes_once_and_retries_after_error() {
use std::sync::Barrier;
use std::sync::atomic::{AtomicUsize, Ordering};
let cell = FallibleOnce::<usize>::new();
let start = Barrier::new(5);
let initializers = AtomicUsize::new(0);
std::thread::scope(|scope| {
let mut workers = Vec::new();
for _ in 0..4 {
workers.push(scope.spawn(|| {
start.wait();
let value = cell
.get_or_try_init(|| {
initializers.fetch_add(1, Ordering::SeqCst);
std::thread::sleep(std::time::Duration::from_millis(5));
Ok::<usize, ()>(42)
})
.expect("initializer succeeds");
assert_eq!(*value, 42);
}));
}
start.wait();
for worker in workers {
worker.join().expect("initializer contender did not panic");
}
});
assert_eq!(initializers.load(Ordering::SeqCst), 1);
let retry = FallibleOnce::<usize>::new();
assert_eq!(
retry
.get_or_try_init(|| Err::<usize, _>("first attempt"))
.expect_err("first initialization must fail"),
"first attempt"
);
assert_eq!(
*retry
.get_or_try_init(|| Ok::<usize, &str>(7))
.expect("failed initialization remains retryable"),
7
);
}
#[test]
fn resolve_model_rejects_missing_path() {
let missing = Path::new("/definitely/not/a/real/model/path.focrq");
let r = OcrModel::resolve_model(missing);
assert!(matches!(r, Err(FocrError::ModelNotFound(_))));
}
#[test]
fn load_missing_path_is_model_not_found() {
let missing = Path::new("/definitely/not/a/real/model/path.focrq");
let r = OcrModel::load(missing);
assert!(matches!(r, Err(FocrError::ModelNotFound(_))));
}
#[test]
fn load_existing_non_model_path_is_format_mismatch_not_panic() -> FocrResult<()> {
let mut tmp = std::env::temp_dir();
tmp.push(format!(
"franken_ocr_load_test_{}.focrq",
std::process::id()
));
std::fs::write(&tmp, b"not a real model blob").expect("write temp file");
let r = OcrModel::load(&tmp);
let _ = std::fs::remove_file(&tmp); let err = match r {
Ok(_) => {
return Err(FocrError::Other(anyhow::anyhow!(
"junk artifact must error, not load"
)));
}
Err(e) => e,
};
assert!(
matches!(err, FocrError::FormatMismatch(_)),
"expected FormatMismatch (exit 7) on a junk artifact, got {err:?}"
);
assert_eq!(err.exit_code(), 7, "FormatMismatch maps to exit 7");
Ok(())
}
fn temp_model_dir(label: &str) -> PathBuf {
let mut dir = std::env::temp_dir();
dir.push(format!("franken_ocr_{label}_{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create temp model dir");
dir
}
fn v1_raw_shard_name() -> &'static str {
unlimited_ocr_census::source_shards(model_arch::TensorCensus::UnlimitedOcrV1)[0].0
}
#[test]
fn resolve_model_searches_short_name_focrq_candidate() {
let dir = temp_model_dir("resolve_short_focrq");
let model = dir.join("unlimited-ocr.focrq");
std::fs::write(&model, b"only resolver existence is tested").expect("write model");
let resolved = resolve_model_from_search_dirs(Path::new("unlimited-ocr"), &[dir])
.expect("resolve short-name focrq candidate");
assert_eq!(resolved, model);
}
#[test]
fn resolve_model_searches_short_name_safetensors_directory() {
let root = temp_model_dir("resolve_short_safetensors_root");
let package = root.join("unlimited-ocr");
std::fs::create_dir_all(&package).expect("create safetensors package");
let shard = package.join(v1_raw_shard_name());
std::fs::write(&shard, b"only resolver existence is tested").expect("write shard");
let resolved = resolve_model_from_search_dirs(Path::new("unlimited-ocr"), &[root])
.expect("resolve short-name safetensors directory");
assert_eq!(resolved, shard);
}
#[test]
fn resolve_model_quant_preference_picks_matching_focrq_candidate() {
let dir = temp_model_dir("resolve_quant_preference");
let generic = dir.join("unlimited-ocr.focrq");
let int4 = dir.join("unlimited-ocr.int4.focrq");
std::fs::write(&generic, weights::FOCRQ_MAGIC).expect("write generic model");
std::fs::write(&int4, weights::FOCRQ_MAGIC).expect("write int4 model");
let resolved = resolve_model_from_search_dirs_with_quant(
Path::new("unlimited-ocr"),
&[dir],
Some(ModelQuantPreference::Int4),
)
.expect("resolve quant-specific focrq candidate");
assert_eq!(resolved, int4);
}
#[test]
fn resolve_model_accepts_model_dir_direct_focrq_artifact() {
let dir = temp_model_dir("resolve_model_dir_direct_focrq");
let model = dir.join("custom.focrq");
std::fs::write(&model, weights::FOCRQ_MAGIC).expect("write model");
let resolved =
resolve_model_from_search_dirs(Path::new("anything"), std::slice::from_ref(&model))
.expect("resolve direct artifact from model dir entry");
assert_eq!(resolved, model);
}
#[test]
fn resolve_model_searches_relative_default_basename_in_model_dir() {
let dir = temp_model_dir("resolve_default_basename");
let model = dir.join("unlimited-ocr.focrq");
std::fs::write(&model, weights::FOCRQ_MAGIC).expect("write model");
let resolved =
resolve_model_from_search_dirs(Path::new("models/unlimited-ocr.focrq"), &[dir])
.expect("resolve default basename in model dir");
assert_eq!(resolved, model);
}
#[test]
fn resolve_model_default_spec_finds_pulled_int8_artifact() {
let dir = temp_model_dir("resolve_default_int8");
let int8 = dir.join("unlimited-ocr.int8.focrq");
std::fs::write(&int8, weights::FOCRQ_MAGIC).expect("write int8 model");
let resolved =
resolve_model_from_search_dirs(Path::new("models/unlimited-ocr.focrq"), &[dir])
.expect("default spec resolves the pulled int8 artifact");
assert_eq!(resolved, int8);
}
#[test]
fn resolve_model_prefers_manifest_versioned_pull_over_legacy_quant_name() {
let dir = temp_model_dir("resolve_manifest_versioned_int8");
let current = dir.join(format!(
"unlimited-ocr.v{}.int8.focrq",
crate::UNLIMITED_OCR_ARTIFACT_VERSION
));
let legacy = dir.join("unlimited-ocr.int8.focrq");
std::fs::write(¤t, weights::FOCRQ_MAGIC).expect("write current model");
std::fs::write(&legacy, weights::FOCRQ_MAGIC).expect("write legacy model");
let resolved =
resolve_model_from_search_dirs(Path::new("models/unlimited-ocr.focrq"), &[dir])
.expect("default spec resolves manifest-versioned pull");
assert_eq!(resolved, current);
}
#[test]
fn resolve_model_names_each_models_artifacts_by_its_own_version() {
let v2 = model_arch::arch_by_id(model_arch::UNLIMITED_OCR_V2_ID).expect("v2 registered");
assert_ne!(v2.artifact_version(), crate::UNLIMITED_OCR_ARTIFACT_VERSION);
let dir = temp_model_dir("resolve_per_model_version");
let v1_current = dir.join(format!(
"unlimited-ocr.v{}.int8.focrq",
crate::UNLIMITED_OCR_ARTIFACT_VERSION
));
let v2_current = dir.join(format!(
"unlimited-ocr-2.v{}.int8.focrq",
v2.artifact_version()
));
for path in [&v1_current, &v2_current] {
std::fs::write(path, weights::FOCRQ_MAGIC).expect("write model");
}
for (spec, expected) in [
("unlimited-ocr", &v1_current),
("models/unlimited-ocr.focrq", &v1_current),
("unlimited-ocr-2", &v2_current),
("models/unlimited-ocr-2.focrq", &v2_current),
] {
let resolved =
resolve_model_from_search_dirs(Path::new(spec), std::slice::from_ref(&dir))
.expect("spec resolves");
assert_eq!(&resolved, expected, "{spec}");
}
let misversioned = temp_model_dir("resolve_per_model_misversioned");
std::fs::write(
misversioned.join(format!(
"unlimited-ocr-2.v{}.int8.focrq",
crate::UNLIMITED_OCR_ARTIFACT_VERSION
)),
weights::FOCRQ_MAGIC,
)
.expect("write model");
let error = resolve_model_from_search_dirs(Path::new("unlimited-ocr-2"), &[misversioned])
.expect_err("a misversioned name is not a v2 release");
assert!(matches!(error, FocrError::ModelNotFound(_)), "{error}");
let subdir = dir.join("unlimited-ocr-2");
std::fs::create_dir_all(&subdir).expect("model subdir");
std::fs::write(subdir.join(shards::SHARD_INDEX_NAME), b"{}").expect("write index");
let resolved = resolve_model_from_search_dirs(Path::new("unlimited-ocr-2"), &[dir])
.expect("the subdirectory resolves");
assert_eq!(resolved, subdir.join(shards::SHARD_INDEX_NAME));
}
#[test]
fn the_model_cache_search_skips_pull_state() {
let models = temp_model_dir("cache_search_pull_state");
for name in [
"unlimited-ocr-2",
"got-ocr2",
".focr-stage-0123456789abcdef0123456789abcdef-1-0.pkg",
".focr-prev-0123456789abcdef0123456789abcdef.pkg",
] {
std::fs::create_dir_all(models.join(name)).expect("create cache entry");
}
std::fs::write(models.join("unlimited-ocr.int8.focrq"), b"by hand").expect("root file");
assert_eq!(
cache_search_dirs(models.clone()),
[
models.join("got-ocr2"),
models.join("unlimited-ocr-2"),
models.clone(),
]
);
std::fs::remove_dir_all(models).expect("remove test cache");
}
#[cfg(unix)]
#[test]
fn a_registry_id_resolves_only_to_its_own_model() {
let v1_dir = temp_model_dir("registry_id_v1_raw");
let v1_index = write_sparse_two_shard_stand_in(
&v1_dir,
&unlimited_ocr_census::v1_source_tensors(),
&[],
);
let root = temp_model_dir("registry_id_root");
let v2_dir = root.join(model_arch::UNLIMITED_OCR_V2_ID);
std::fs::create_dir_all(&v2_dir).expect("v2 subdirectory");
let v2_index = write_sparse_v2_stand_in(&v2_dir);
let resolve =
|spec: &str, dirs: &[PathBuf]| resolve_model_from_search_dirs(Path::new(spec), dirs);
let v1_only = std::slice::from_ref(&v1_dir);
assert_eq!(
resolve("unlimited-ocr", v1_only).expect("v1's id"),
v1_index
);
assert_eq!(
resolve("anything", v1_only).expect("any other spec"),
v1_index
);
let error = resolve("unlimited-ocr-2", v1_only).expect_err("v1 never answers v2's id");
assert!(matches!(error, FocrError::ModelNotFound(_)), "{error}");
let both = [v1_dir.clone(), root.clone()];
assert_eq!(
resolve("unlimited-ocr-2", &both).expect("a later entry holds v2"),
v2_index
);
assert_eq!(
resolve("unlimited-ocr", &both).expect("the first entry holds v1"),
v1_index
);
let misnamed_root = temp_model_dir("registry_id_misnamed");
let misnamed = misnamed_root.join(model_arch::UNLIMITED_OCR_V2_ID);
std::fs::create_dir_all(&misnamed).expect("misnamed directory");
write_sparse_two_shard_stand_in(&misnamed, &unlimited_ocr_census::v1_source_tensors(), &[]);
let error = resolve("unlimited-ocr-2", &[misnamed_root]).expect_err("v1 under v2's name");
let text = error.to_string();
assert!(matches!(error, FocrError::ModelNotFound(_)), "{error}");
assert!(
text.contains("\"unlimited-ocr\"") && text.contains("\"unlimited-ocr-2\""),
"{text}"
);
for (spec, named) in [
("unlimited-ocr-2", true),
("unlimited-ocr", true),
("./unlimited-ocr-2", false),
("models/unlimited-ocr-2", false),
("unlimited-ocr-2.focrq", false),
("unlimited-ocr-3", false),
] {
assert_eq!(registry_spec(Path::new(spec)).is_some(), named, "{spec}");
}
}
#[test]
fn resolve_model_prefers_exact_focrq_over_quant_variant() {
let dir = temp_model_dir("resolve_exact_over_int8");
let generic = dir.join("unlimited-ocr.focrq");
let int8 = dir.join("unlimited-ocr.int8.focrq");
std::fs::write(&generic, weights::FOCRQ_MAGIC).expect("write generic model");
std::fs::write(&int8, weights::FOCRQ_MAGIC).expect("write int8 model");
let resolved =
resolve_model_from_search_dirs(Path::new("models/unlimited-ocr.focrq"), &[dir])
.expect("resolve exact generic focrq");
assert_eq!(resolved, generic);
}
#[test]
fn resolve_model_missing_short_name_lists_search_dirs() {
let dirs = [
PathBuf::from("/tmp/franken_ocr_missing_a"),
PathBuf::from("/tmp/franken_ocr_missing_b"),
];
let err = resolve_model_from_search_dirs(Path::new("missing-model"), &dirs)
.expect_err("missing short name should fail");
let text = err.to_string();
assert!(matches!(err, FocrError::ModelNotFound(_)));
assert!(text.contains("missing-model"));
assert!(text.contains("/tmp/franken_ocr_missing_a"));
assert!(text.contains("/tmp/franken_ocr_missing_b"));
assert!(text.contains(MODEL_DIR_ENV));
}
fn minimal_focrq_blob(version: u32, header_json: &str, payload: &[u8]) -> Vec<u8> {
let mut blob = Vec::new();
blob.extend_from_slice(weights::FOCRQ_MAGIC);
blob.extend_from_slice(&version.to_le_bytes());
blob.push(0);
blob.extend_from_slice(&[0u8; 32]);
blob.extend_from_slice(&(header_json.len() as u64).to_le_bytes());
blob.extend_from_slice(header_json.as_bytes());
blob.extend_from_slice(payload);
blob
}
#[test]
fn load_future_focrq_version_preserves_format_mismatch() {
let payload = [0u8, 0u8];
let header = "{\"t\":{\"dtype\":\"BF16\",\"shape\":[1],\"byte_offset\":0,\"byte_len\":2}}";
let path = temp_model_dir("future_focrq_version").join("future-version.focrq");
std::fs::write(
&path,
minimal_focrq_blob(weights::FOCRQ_FORMAT_VERSION + 1, header, &payload),
)
.expect("write future-version artifact");
let err = load_weights_from_resolved_model(&path).unwrap_err();
assert!(matches!(err, FocrError::FormatMismatch(_)));
assert_eq!(err.exit_code(), crate::error::EXIT_FORMAT_MISMATCH);
assert!(format!("{err}").contains("requires exactly"));
}
#[test]
fn load_malformed_safetensors_preserves_format_mismatch() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&(1u64).to_le_bytes());
bytes.extend_from_slice(b"{");
let path = temp_model_dir("malformed_safetensors").join("bad.safetensors");
std::fs::write(&path, bytes).expect("write malformed safetensors");
let err = load_weights_from_resolved_model(&path).unwrap_err();
assert!(matches!(err, FocrError::FormatMismatch(_)));
assert_eq!(err.exit_code(), crate::error::EXIT_FORMAT_MISMATCH);
}
fn compatible_test_model_builder() -> crate::quant::focrq::FocrqBuilder {
let arch = model_arch::arch_by_id("got-ocr2").expect("test arch is registered");
let mut builder = crate::quant::focrq::FocrqBuilder::new()
.with_model_id(arch.id())
.with_license_notice(arch.license_notice());
builder
.add_tensor(
"test.base.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add minimal compatible test-model tensor");
builder
}
fn incomplete_unlimited_builder() -> crate::quant::focrq::FocrqBuilder {
let mut builder =
crate::quant::focrq::FocrqBuilder::new().with_packing_manifest_json(format!(
r#"{{"quant_recipe":"{}"}}"#,
crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID,
));
builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add minimal compatible high-precision tensor");
builder
}
#[test]
fn ocr_model_load_owns_weight_bytes_by_default() -> FocrResult<()> {
let path = temp_model_dir("ocr_model_mmap").join("minimal.focrq");
let blob = compatible_test_model_builder().build();
std::fs::write(&path, blob).expect("write valid minimal focrq");
let model = OcrModel::load(&path)?;
assert!(
!model.weights.is_mapped() || weights::mmap_requested(),
"production load may map only after explicit opt-in"
);
Ok(())
}
#[cfg(unix)]
fn loader_swap_fixture(label: &str, marker: &str) -> (PathBuf, Vec<u8>) {
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let nonce = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut dir = std::env::temp_dir();
dir.push(format!(
"franken_ocr_loader_swap_{label}_{}_{nonce}",
std::process::id()
));
std::fs::create_dir_all(&dir).expect("create loader-swap fixture dir");
let mut builder = compatible_test_model_builder();
builder
.add_tensor(
marker,
crate::quant::focrq::WriteDType::Bf16,
vec![1],
vec![0; 2],
)
.expect("add loader-swap marker tensor");
(dir, builder.build())
}
#[cfg(unix)]
#[test]
fn production_loader_pins_validated_file_across_path_swap() {
let (dir, original_blob) = loader_swap_fixture("path", "test.original");
let model_path = dir.join("active.focrq");
let replacement_path = dir.join("replacement.focrq");
let displaced_path = dir.join("validated-original.focrq");
std::fs::write(&model_path, original_blob).expect("write original model");
let mut replacement = compatible_test_model_builder();
replacement
.add_tensor(
"test.replacement",
crate::quant::focrq::WriteDType::Bf16,
vec![1],
vec![0; 2],
)
.expect("add replacement marker tensor");
std::fs::write(&replacement_path, replacement.build()).expect("write replacement model");
let loaded = load_weights_from_resolved_model_after_validation(
&model_path,
|_| Ok(()),
|| {
std::fs::rename(&model_path, &displaced_path)
.expect("preserve validated path entry");
std::fs::rename(&replacement_path, &model_path).expect("swap active path entry");
},
)
.expect("load the descriptor validated before the path swap");
assert!(loaded.weights.record("test.original").is_some());
assert!(loaded.weights.record("test.replacement").is_none());
let path_now = Weights::load(&model_path).expect("load replacement now at active path");
assert!(path_now.record("test.original").is_none());
assert!(path_now.record("test.replacement").is_some());
}
#[cfg(unix)]
#[test]
fn production_loader_pins_validated_file_across_symlink_swap() {
use std::os::unix::fs::symlink;
let (dir, original_blob) = loader_swap_fixture("symlink", "test.original_link");
let original_path = dir.join("original.focrq");
let replacement_path = dir.join("replacement.focrq");
let active_link = dir.join("active.focrq");
let next_link = dir.join("next.focrq");
let retired_link = dir.join("validated-link.focrq");
std::fs::write(&original_path, original_blob).expect("write original symlink target");
let mut replacement = compatible_test_model_builder();
replacement
.add_tensor(
"test.replacement_link",
crate::quant::focrq::WriteDType::Bf16,
vec![1],
vec![0; 2],
)
.expect("add replacement symlink marker tensor");
std::fs::write(&replacement_path, replacement.build())
.expect("write replacement symlink target");
symlink(&original_path, &active_link).expect("link active path to original");
symlink(&replacement_path, &next_link).expect("prepare replacement symlink");
let loaded = load_weights_from_resolved_model_after_validation(
&active_link,
|_| Ok(()),
|| {
std::fs::rename(&active_link, &retired_link).expect("preserve validated symlink");
std::fs::rename(&next_link, &active_link).expect("swap active symlink");
},
)
.expect("load the descriptor validated before the symlink swap");
assert!(loaded.weights.record("test.original_link").is_some());
assert!(loaded.weights.record("test.replacement_link").is_none());
let link_now = Weights::load(&active_link).expect("load replacement symlink target");
assert!(link_now.record("test.original_link").is_none());
assert!(link_now.record("test.replacement_link").is_some());
}
fn add_unit_qint8(builder: &mut crate::quant::focrq::FocrqBuilder, name: &str) {
builder
.add_quantized(
name,
crate::quant::focrq::WriteDType::QInt8PerChan,
vec![1, 1],
vec![0],
1.0f32.to_le_bytes().to_vec(),
0,
0,
)
.expect("add synthetic qint8 tensor");
}
#[test]
fn unlimited_focrq_rejects_int8_attention_and_lm_head() {
for name in ["model.layers.0.self_attn.q_proj.weight", "lm_head.weight"] {
let mut builder = crate::quant::focrq::FocrqBuilder::new();
add_unit_qint8(&mut builder, name);
let weights = Weights::from_bytes(builder.build()).expect("parse synthetic artifact");
let err = validate_unlimited_ocr_quant_recipe(&weights)
.expect_err("gated tensor must stay high precision in the default recipe");
assert!(matches!(err, FocrError::FormatMismatch(_)));
assert!(err.to_string().contains(name));
assert!(
err.to_string()
.contains(crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID)
);
}
}
#[test]
fn unlimited_focrq_requires_int8_for_present_ffn_weights() {
let mut builder = crate::quant::focrq::FocrqBuilder::new();
builder
.add_tensor(
"model.layers.0.mlp.down_proj.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add synthetic bf16 FFN");
let weights = Weights::from_bytes(builder.build()).expect("parse synthetic artifact");
let err = validate_unlimited_ocr_quant_recipe(&weights)
.expect_err("validated FFN tensor must use int8 storage");
assert!(err.to_string().contains("expected QInt8PerChan"));
}
fn add_unit_qint4(builder: &mut crate::quant::focrq::FocrqBuilder, name: &str) {
builder
.add_quantized(
name,
crate::quant::focrq::WriteDType::QInt4PerGroup,
vec![1, 16],
vec![0x21; 8],
1.0f32.to_le_bytes().to_vec(),
16,
0,
)
.expect("add synthetic qint4 tensor");
}
fn wasm_recipe_manifest_json() -> String {
format!(
r#"{{"quant_recipe":"{}"}}"#,
crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID,
)
}
#[test]
fn wasm_recipe_records_accept_int4_experts_and_int8_gated_sets() {
let wasm = Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID);
validate_unlimited_ocr_quant_records(
true,
model_arch::default_arch().id(),
wasm,
[
(
"model.layers.3.mlp.experts.7.up_proj.weight",
DType::QInt4PerGroup,
),
("model.layers.0.mlp.down_proj.weight", DType::QInt4PerGroup),
(
"model.layers.11.mlp.shared_experts.gate_proj.weight",
DType::QInt4PerGroup,
),
(
"model.layers.0.self_attn.q_proj.weight",
DType::QInt8PerChan,
),
("lm_head.weight", DType::QInt8PerChan),
("model.embed_tokens.weight", DType::QInt8PerChan),
("model.norm.weight", DType::BF16),
("model.layers.5.mlp.gate.weight", DType::BF16), ],
)
.expect("the declared wasm recipe must accept its own storage dtypes");
}
#[test]
fn wasm_recipe_records_reject_int4_attention_and_wrong_expert_storage() {
let wasm = Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID);
let err = validate_unlimited_ocr_quant_records(
true,
model_arch::default_arch().id(),
wasm,
[(
"model.layers.0.self_attn.q_proj.weight",
DType::QInt4PerGroup,
)],
)
.expect_err("int4 attention must be rejected under the wasm recipe");
assert!(matches!(err, FocrError::FormatMismatch(_)));
assert!(err.to_string().contains("expected QInt8PerChan"));
assert!(
err.to_string()
.contains(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID)
);
for dtype in [DType::QInt8PerChan, DType::BF16] {
let err = validate_unlimited_ocr_quant_records(
true,
model_arch::default_arch().id(),
wasm,
[("model.layers.3.mlp.experts.7.up_proj.weight", dtype)],
)
.expect_err("non-int4 expert storage must be rejected under the wasm recipe");
assert!(err.to_string().contains("expected QInt4PerGroup"));
}
let err = validate_unlimited_ocr_quant_records(
true,
model_arch::default_arch().id(),
wasm,
[("model.layers.5.mlp.gate.weight", DType::QInt8PerChan)],
)
.expect_err("quantized router gate must be rejected");
assert!(err.to_string().contains("expected BF16 or F32"));
}
#[test]
fn v2_records_hold_only_the_v2_conservative_recipe() {
let v2_id = model_arch::UNLIMITED_OCR_V2_ID;
let v2_recipe = crate::quant::convert::UNLIMITED_OCR_2_INT8_RECIPE_ID;
let good = [
(
"model.layers.3.mlp.experts.7.up_proj.weight",
DType::QInt8PerChan,
),
("model.layers.0.mlp.down_proj.weight", DType::QInt8PerChan),
("model.layers.0.self_attn.q_proj.weight", DType::BF16),
(
"model.layers.0.self_attn.dash_router.q_proj.weight",
DType::BF16,
),
("model.layers.0.self_attn.dash_router.head_cls", DType::BF16),
(
"model.layers.11.self_attn.indexer.k_norm.weight",
DType::BF16,
),
("model.layers.5.mlp.gate.weight", DType::BF16),
("lm_head.weight", DType::BF16),
];
validate_unlimited_ocr_quant_records(true, v2_id, Some(v2_recipe), good)
.expect("the v2 recipe accepts its own storage dtypes");
validate_unlimited_ocr_quant_records(false, v2_id, None, good)
.expect("raw checkpoints carry no recipe");
for declared in [
None,
Some(crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID),
Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID),
] {
let err = validate_unlimited_ocr_quant_records(true, v2_id, declared, good)
.expect_err("only the v2 recipe id is accepted");
assert_eq!(err.exit_code(), 7, "{err}");
assert!(err.to_string().contains("must be exactly"), "{err}");
assert!(err.to_string().contains(v2_recipe), "{err}");
}
for name in [
"model.layers.4.self_attn.dash_router.q_proj.weight",
"model.layers.4.self_attn.indexer.k_proj.weight",
"model.layers.4.self_attn.o_proj.weight",
] {
let err = validate_unlimited_ocr_quant_records(
true,
v2_id,
Some(v2_recipe),
[(name, DType::QInt8PerChan)],
)
.expect_err("a quantized high-precision tensor is refused");
assert_eq!(err.exit_code(), 7, "{err}");
let message = err.to_string();
assert!(message.contains(name), "{message}");
assert!(
message.contains("Unlimited-OCR-2 .focrq violates quant recipe"),
"{message}"
);
assert!(message.contains(v2_recipe), "{message}");
}
}
#[test]
fn conservative_recipe_still_rejects_int4_expert_storage() {
for declared in [
None,
Some(crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID),
] {
let err = validate_unlimited_ocr_quant_records(
true,
model_arch::default_arch().id(),
declared,
[(
"model.layers.3.mlp.experts.7.up_proj.weight",
DType::QInt4PerGroup,
)],
)
.expect_err("int4 experts must stay rejected outside the wasm recipe");
assert!(err.to_string().contains("expected QInt8PerChan"));
assert!(
err.to_string()
.contains(crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID)
);
}
}
#[test]
fn wasm_recipe_artifact_passes_dtype_validation_and_fails_only_the_census() {
let mut builder = crate::quant::focrq::FocrqBuilder::new()
.with_packing_manifest_json(wasm_recipe_manifest_json())
.with_source_sha256([
0x2b, 0xc4, 0x8a, 0x7a, 0x11, 0x00, 0x61, 0xea, 0x58, 0xff, 0xf6, 0x5d, 0x31, 0x69,
0x36, 0x7e, 0xeb, 0xe3, 0xae, 0xe3, 0x71, 0xca, 0x69, 0x68, 0xdc, 0x22, 0x19, 0xc1,
0xb2, 0x85, 0x5f, 0xc6,
]);
add_unit_qint4(&mut builder, "model.layers.3.mlp.experts.7.up_proj.weight");
add_unit_qint8(&mut builder, "model.layers.0.self_attn.q_proj.weight");
add_unit_qint8(&mut builder, "lm_head.weight");
let weights = Weights::from_bytes(builder.build()).expect("synthetic wasm artifact parses");
assert_eq!(
weights.quant_recipe(),
Some(crate::quant::convert::UNLIMITED_OCR_WASM_INT4_RECIPE_ID),
"the loader must retain the declared recipe id"
);
let error = validate_unlimited_ocr_quant_recipe(&weights)
.expect_err("a 3-tensor subset must still fail the completeness census");
let text = error.to_string();
assert!(
text.contains("expected 2710 tensors, found 3"),
"failure must be the census, not a dtype-recipe violation: {text}"
);
assert!(
!text.contains("violates quant recipe"),
"wasm dtypes must not be flagged as recipe violations: {text}"
);
}
#[test]
fn weights_without_manifest_report_no_quant_recipe() {
let mut builder = crate::quant::focrq::FocrqBuilder::new();
add_unit_qint8(&mut builder, "model.layers.0.mlp.down_proj.weight");
let weights = Weights::from_bytes(builder.build()).expect("synthetic artifact parses");
assert_eq!(weights.quant_recipe(), None);
}
#[test]
fn focrq_int4_invalid_group_size_is_rejected_with_format_mismatch() {
let mut builder = crate::quant::focrq::FocrqBuilder::new()
.with_packing_manifest_json(wasm_recipe_manifest_json());
add_unit_qint4(&mut builder, "model.layers.3.mlp.experts.7.up_proj.weight");
let blob = builder.build();
let needle = b"\"group_size\":16";
let position = blob
.windows(needle.len())
.position(|window| window == needle)
.expect("header must contain the int4 group_size field");
let mut corrupt = blob.clone();
corrupt[position + needle.len() - 2] = b'9';
let err = validate_model_header_from_reader(corrupt.as_slice(), corrupt.len() as u64)
.expect_err("group_size 96 must be rejected");
assert!(matches!(err, FocrError::FormatMismatch(_)));
assert!(
err.to_string().contains("group_size"),
"error must name the corrupt field: {err}"
);
let weights = Weights::from_bytes(corrupt).expect("directory framing is still intact");
let err = weights
.qint4("model.layers.3.mlp.experts.7.up_proj.weight")
.expect_err("qint4 accessor must reject group_size 96");
assert!(matches!(err, FocrError::FormatMismatch(_)));
}
#[test]
fn resolver_skips_wasm_recipe_artifacts_but_explicit_path_loads_them() {
let dir = temp_model_dir("resolver_skips_wasm_recipe");
let wasm_path = dir.join("unlimited-ocr.int8.focrq");
let mut wasm_builder = crate::quant::focrq::FocrqBuilder::new()
.with_packing_manifest_json(wasm_recipe_manifest_json());
add_unit_qint4(
&mut wasm_builder,
"model.layers.3.mlp.experts.7.up_proj.weight",
);
std::fs::write(&wasm_path, wasm_builder.build()).expect("write wasm-recipe artifact");
assert!(focrq_declares_wasm_recipe(&wasm_path));
let v2_path = dir.join("v2-declaring-wasm.focrq");
let v2_builder = crate::quant::focrq::FocrqBuilder::new()
.with_model_id(model_arch::UNLIMITED_OCR_V2_ID)
.with_packing_manifest_json(wasm_recipe_manifest_json());
std::fs::write(&v2_path, v2_builder.build()).expect("write v2 artifact");
assert!(!focrq_declares_wasm_recipe(&v2_path));
let err =
resolve_model_from_search_dirs(Path::new("unlimited-ocr"), std::slice::from_ref(&dir))
.expect_err("the resolver must skip the wasm-recipe artifact");
assert!(matches!(err, FocrError::ModelNotFound(_)));
let resolved =
OcrModel::resolve_model(&wasm_path).expect("explicit path must resolve verbatim");
assert_eq!(resolved, wasm_path);
let conservative_path = dir.join("unlimited-ocr.int4.focrq");
let mut conservative_builder = crate::quant::focrq::FocrqBuilder::new()
.with_packing_manifest_json(format!(
r#"{{"quant_recipe":"{}"}}"#,
crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID,
));
add_unit_qint8(
&mut conservative_builder,
"model.layers.0.mlp.down_proj.weight",
);
std::fs::write(&conservative_path, conservative_builder.build())
.expect("write conservative artifact");
assert!(!focrq_declares_wasm_recipe(&conservative_path));
let resolved = resolve_model_from_search_dirs(Path::new("unlimited-ocr"), &[dir])
.expect("a non-wasm artifact must still resolve");
assert_eq!(resolved, conservative_path);
}
#[test]
fn unlimited_focrq_rejects_incomplete_conservative_subset() -> FocrResult<()> {
let mut builder = crate::quant::focrq::FocrqBuilder::new().with_source_sha256([
0x2b, 0xc4, 0x8a, 0x7a, 0x11, 0x00, 0x61, 0xea, 0x58, 0xff, 0xf6, 0x5d, 0x31, 0x69,
0x36, 0x7e, 0xeb, 0xe3, 0xae, 0xe3, 0x71, 0xca, 0x69, 0x68, 0xdc, 0x22, 0x19, 0xc1,
0xb2, 0x85, 0x5f, 0xc6,
]);
add_unit_qint8(&mut builder, "model.layers.0.mlp.down_proj.weight");
builder.add_tensor(
"model.layers.0.self_attn.q_proj.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)?;
builder.add_tensor(
"lm_head.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)?;
let weights = Weights::from_bytes(builder.build())?;
let error = validate_unlimited_ocr_quant_recipe(&weights)
.expect_err("a present-only subset must not pass the production census");
assert!(error.to_string().contains("expected 2710 tensors, found 3"));
assert!(error.to_string().contains("missing"));
Ok(())
}
#[test]
fn resolve_model_accepts_safetensors_directory_shard() {
let dir = temp_model_dir("resolve_safetensors_dir");
let shard = dir.join(v1_raw_shard_name());
std::fs::write(&shard, b"not loaded by resolver").expect("write shard");
let resolved = OcrModel::resolve_model(&dir).expect("resolve safetensors directory");
assert_eq!(resolved, shard);
}
#[test]
fn load_safetensors_directory_preserves_format_mismatch() {
let dir = temp_model_dir("load_safetensors_dir");
let shard = dir.join(v1_raw_shard_name());
let mut bytes = Vec::new();
bytes.extend_from_slice(&(1u64).to_le_bytes());
bytes.extend_from_slice(b"{");
std::fs::write(&shard, bytes).expect("write malformed shard");
let r = OcrModel::load(&dir);
assert!(matches!(&r, Err(FocrError::FormatMismatch(_))));
if let Err(err) = r {
assert_eq!(err.exit_code(), crate::error::EXIT_FORMAT_MISMATCH);
}
}
#[test]
fn native_model_available_requires_a_compatible_focrq_header() {
let mut tmp = std::env::temp_dir();
tmp.push(format!(
"franken_ocr_available_focrq_{}.focrq",
std::process::id()
));
std::fs::write(&tmp, compatible_test_model_builder().build())
.expect("write compatible focrq");
assert!(native_model_available(&tmp));
assert_eq!(model_id_at(&tmp), Some("got-ocr2"), "the builder's arch");
assert!(load_weights_from_resolved_model(&tmp).is_ok());
let mut magic_only = tmp.clone();
magic_only.set_file_name(format!(
"franken_ocr_available_magic_only_{}.focrq",
std::process::id()
));
std::fs::write(&magic_only, weights::FOCRQ_MAGIC).expect("write magic-only focrq");
assert!(!native_model_available(&magic_only));
assert_eq!(model_id_at(&magic_only), None);
assert!(load_weights_from_resolved_model(&magic_only).is_err());
}
#[test]
fn native_model_available_rejects_unsupported_versions_and_wrong_dtype() {
let mut future = compatible_test_model_builder().build();
future[6..10].copy_from_slice(&(weights::FOCRQ_FORMAT_VERSION + 1).to_le_bytes());
let future_path = temp_model_dir("available_future").join("future.focrq");
std::fs::write(&future_path, future).expect("write future focrq");
assert!(!native_model_available(&future_path));
assert!(load_weights_from_resolved_model(&future_path).is_err());
let mut builder = incomplete_unlimited_builder();
add_unit_qint8(&mut builder, "model.layers.0.self_attn.q_proj.weight");
let wrong_recipe_path = temp_model_dir("available_wrong_recipe").join("wrong-recipe.focrq");
std::fs::write(&wrong_recipe_path, builder.build()).expect("write wrong recipe focrq");
assert!(!native_model_available(&wrong_recipe_path));
assert!(load_weights_from_resolved_model(&wrong_recipe_path).is_err());
}
#[test]
fn native_model_available_rejects_version_zero_and_header_disagreement() {
let mut version_zero = compatible_test_model_builder().build();
version_zero[6..10].copy_from_slice(&0u32.to_le_bytes());
let version_zero_path = temp_model_dir("available_version_zero").join("zero.focrq");
std::fs::write(&version_zero_path, version_zero).expect("write v0 focrq");
assert!(!native_model_available(&version_zero_path));
assert!(load_weights_from_resolved_model(&version_zero_path).is_err());
let mut mismatch = compatible_test_model_builder().build();
let marker = b"\"format_version\":1";
let marker_start = mismatch
.windows(marker.len())
.position(|window| window == marker)
.expect("builder emits header format_version");
mismatch[marker_start + marker.len() - 1] = b'0';
let mismatch_path = temp_model_dir("available_version_mismatch").join("mismatch.focrq");
std::fs::write(&mismatch_path, mismatch).expect("write mismatched version focrq");
assert!(!native_model_available(&mismatch_path));
assert!(load_weights_from_resolved_model(&mismatch_path).is_err());
let missing_header_version = serde_json::json!({
"arch_target": 0,
"license_notice": crate::FOCR_MODEL_LICENSE_NOTICE,
"packing_manifest": {
"quant_recipe": crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID,
},
"source_sha256": "",
"tensors": {
"model.embed_tokens.weight": {
"dtype": "BF16",
"shape": [1, 1],
"byte_offset": 0,
"byte_len": 2,
},
},
})
.to_string();
let missing_header_version_path =
temp_model_dir("available_missing_header_version").join("missing-version.focrq");
std::fs::write(
&missing_header_version_path,
minimal_focrq_blob(
weights::FOCRQ_FORMAT_VERSION,
&missing_header_version,
&[0u8; 2],
),
)
.expect("write focrq without header format_version");
assert!(!native_model_available(&missing_header_version_path));
assert!(load_weights_from_resolved_model(&missing_header_version_path).is_err());
}
#[test]
fn native_model_available_rejects_preamble_header_provenance_disagreement() {
let mut arch_mismatch = compatible_test_model_builder().with_arch_target(2).build();
arch_mismatch[10] = 1;
let arch_path = temp_model_dir("available_arch_mismatch").join("arch-mismatch.focrq");
std::fs::write(&arch_path, arch_mismatch).expect("write arch-mismatched focrq");
let arch_error = validate_model_header_file(&arch_path)
.expect_err("header and preamble arch_target must agree");
assert!(arch_error.to_string().contains("header arch_target"));
assert!(!native_model_available(&arch_path));
let mut source_mismatch = compatible_test_model_builder()
.with_source_sha256([7u8; 32])
.build();
source_mismatch[11] = 6;
let source_path = temp_model_dir("available_source_mismatch").join("source-mismatch.focrq");
std::fs::write(&source_path, source_mismatch).expect("write source-mismatched focrq");
let source_error = validate_model_header_file(&source_path)
.expect_err("header and preamble source_sha256 must agree");
assert!(source_error.to_string().contains("header source_sha256"));
assert!(!native_model_available(&source_path));
let mut uppercase_source = compatible_test_model_builder()
.with_source_sha256([0xabu8; 32])
.build();
let marker = b"\"source_sha256\":\"ab";
let marker_start = uppercase_source
.windows(marker.len())
.position(|window| window == marker)
.expect("builder emits lowercase source sha256");
uppercase_source[marker_start + marker.len() - 2] = b'A';
let uppercase_path =
temp_model_dir("available_uppercase_source").join("uppercase-source.focrq");
std::fs::write(&uppercase_path, uppercase_source).expect("write uppercase-hash focrq");
let uppercase_error = validate_model_header_file(&uppercase_path)
.expect_err("header source_sha256 must be lowercase");
assert!(uppercase_error.to_string().contains("64 lowercase hex"));
assert!(!native_model_available(&uppercase_path));
}
#[test]
fn native_model_available_rejects_unknown_arch_target() {
let path = temp_model_dir("available_unknown_arch").join("unknown-arch.focrq");
let blob = compatible_test_model_builder().with_arch_target(4).build();
std::fs::write(&path, blob).expect("write unknown-arch focrq");
let error = validate_model_header_file(&path)
.expect_err("bounded production header validation must reject unknown arch targets");
assert!(error.to_string().contains("arch_target 4 is unsupported"));
assert!(!native_model_available(&path));
assert!(load_weights_from_resolved_model(&path).is_err());
}
#[test]
fn native_model_available_requires_exact_unlimited_quant_recipe_metadata() {
let empty_path = temp_model_dir("available_empty_focrq").join("empty.focrq");
std::fs::write(
&empty_path,
crate::quant::focrq::FocrqBuilder::new()
.with_packing_manifest_json(format!(
r#"{{"quant_recipe":"{}"}}"#,
crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID,
))
.build(),
)
.expect("write empty focrq");
assert!(!native_model_available(&empty_path));
assert!(load_weights_from_resolved_model(&empty_path).is_err());
let missing_path = temp_model_dir("available_missing_recipe").join("missing.focrq");
let mut missing_builder = crate::quant::focrq::FocrqBuilder::new();
missing_builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add recipe-less tensor");
std::fs::write(&missing_path, missing_builder.build()).expect("write recipe-less focrq");
assert!(!native_model_available(&missing_path));
assert!(load_weights_from_resolved_model(&missing_path).is_err());
let wrong_path = temp_model_dir("available_bad_recipe").join("wrong.focrq");
let mut wrong_builder = crate::quant::focrq::FocrqBuilder::new()
.with_packing_manifest_json(r#"{"quant_recipe":"legacy-full-int8"}"#);
wrong_builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add wrong-recipe tensor");
std::fs::write(&wrong_path, wrong_builder.build()).expect("write wrong-recipe focrq");
assert!(!native_model_available(&wrong_path));
assert!(load_weights_from_resolved_model(&wrong_path).is_err());
}
#[test]
fn native_model_available_rejects_malformed_quant_scale_metadata() {
let header = serde_json::json!({
"arch_target": 0,
"format_version": weights::FOCRQ_FORMAT_VERSION,
"license_notice": crate::FOCR_MODEL_LICENSE_NOTICE,
"packing_manifest": {
"quant_recipe": crate::quant::convert::UNLIMITED_OCR_INT8_RECIPE_ID,
},
"source_sha256": "00".repeat(32),
"tensors": {
"model.layers.0.mlp.down_proj.weight": {
"dtype": "QInt8PerChan",
"shape": [1, 1],
"byte_offset": 0,
"byte_len": 1,
"scales_offset": 1,
"scales_len": 0,
"group_size": 0,
"tier": 0,
},
},
})
.to_string();
let path = temp_model_dir("available_bad_quant_scales").join("bad-scales.focrq");
std::fs::write(
&path,
minimal_focrq_blob(weights::FOCRQ_FORMAT_VERSION, &header, &[0u8; 5]),
)
.expect("write malformed quant metadata focrq");
assert!(!native_model_available(&path));
assert!(load_weights_from_resolved_model(&path).is_err());
}
#[test]
fn native_model_available_rejects_overlapping_payload_ranges() {
let got = model_arch::arch_by_id("got-ocr2").expect("GOT test arch is registered");
let header = serde_json::json!({
"arch_target": 0,
"format_version": weights::FOCRQ_FORMAT_VERSION,
"license_notice": got.license_notice(),
"model_id": got.id(),
"source_sha256": "00".repeat(32),
"tensors": {
"test.a": {
"dtype": "BF16",
"shape": [2],
"byte_offset": 0,
"byte_len": 4,
},
"test.b": {
"dtype": "BF16",
"shape": [2],
"byte_offset": 0,
"byte_len": 4,
},
},
})
.to_string();
let path = temp_model_dir("available_overlapping_ranges").join("overlap.focrq");
std::fs::write(
&path,
minimal_focrq_blob(weights::FOCRQ_FORMAT_VERSION, &header, &[0u8; 4]),
)
.expect("write overlapping-range focrq");
let error = validate_model_header_file(&path)
.expect_err("bounded production header validation must reject aliases");
assert!(error.to_string().contains("payload ranges overlap"));
assert!(!native_model_available(&path));
assert!(load_weights_from_resolved_model(&path).is_err());
}
#[test]
fn native_model_available_rejects_incomplete_safetensors_directory_header() {
let dir = temp_model_dir("available_safetensors_dir");
let shard = dir.join(v1_raw_shard_name());
let header = br#"{"x":{"dtype":"BF16","shape":[1],"data_offsets":[0,2]}}"#;
let mut bytes = Vec::new();
bytes.extend_from_slice(&(header.len() as u64).to_le_bytes());
bytes.extend_from_slice(header);
bytes.extend_from_slice(&[0u8; 2]);
std::fs::write(&shard, bytes).expect("write safetensors header");
assert!(!native_model_available(&dir));
assert!(load_weights_from_resolved_model(&shard).is_err());
}
#[test]
fn native_model_available_rejects_empty_safetensors_directory() {
let dir = temp_model_dir("available_empty_safetensors");
let shard = dir.join(v1_raw_shard_name());
let mut bytes = Vec::new();
bytes.extend_from_slice(&(2u64).to_le_bytes());
bytes.extend_from_slice(b"{}");
std::fs::write(&shard, bytes).expect("write empty safetensors header");
assert!(!native_model_available(&dir));
assert!(load_weights_from_resolved_model(&shard).is_err());
}
#[test]
fn a_sharded_directory_resolves_to_its_index_and_opens_through_the_probe() {
use shards::tests::{POSITION_IDS_RULE, arange_bytes, index_json, safetensors_file};
let dir = temp_model_dir("sharded_dir");
let index = dir.join(shards::SHARD_INDEX_NAME);
std::fs::write(
dir.join("model-00001-of-00002.safetensors"),
safetensors_file(&[("enc.w", "BF16", vec![2], vec![1, 0, 2, 0])]),
)
.expect("write shard 1");
std::fs::write(
dir.join("model-00002-of-00002.safetensors"),
safetensors_file(&[
("dec.w", "BF16", vec![1], vec![3, 0]),
("vision.position_ids", "I64", vec![1, 4], arange_bytes(4)),
]),
)
.expect("write shard 2");
std::fs::write(
&index,
index_json(
&[
("enc.w", "model-00001-of-00002.safetensors"),
("dec.w", "model-00002-of-00002.safetensors"),
("vision.position_ids", "model-00002-of-00002.safetensors"),
],
4 + 2 + 32,
),
)
.expect("write index");
std::fs::write(dir.join(v1_raw_shard_name()), b"stray").expect("write stray shard");
assert_eq!(OcrModel::resolve_model(&dir).expect("resolves"), index);
let opened = shards::open_shards(&index).expect("opens every shard");
let merged = read_opened_shard_headers(&opened).expect("headers merge");
assert_eq!((merged.records.len(), merged.int64.len()), (2, 1));
let error = validate_model_header_file(&index).expect_err("no census matches");
assert!(
error.to_string().contains("matches no registered model"),
"{error}"
);
assert!(load_weights_from_resolved_model(&index).is_err());
let _rules = shards::test_rules::supply(&POSITION_IDS_RULE);
let weights = Weights::load_opened_shards(opened).expect("sharded load");
assert_eq!(weights.names().collect::<Vec<_>>(), ["dec.w", "enc.w"]);
let broken = temp_model_dir("sharded_dir_missing_shard");
let broken_index = broken.join(shards::SHARD_INDEX_NAME);
std::fs::write(&broken_index, index_json(&[("a", "gone.safetensors")], 2))
.expect("write index");
let error = validate_model_header_file(&broken_index).expect_err("missing shard");
assert!(matches!(error, FocrError::ModelNotFound(_)), "{error}");
assert!(
error.to_string().contains("lists shard gone.safetensors"),
"{error}"
);
}
#[cfg(unix)]
pub(super) fn write_sparse_two_shard_stand_in(
dir: &Path,
tensors: &[(String, DType, Vec<usize>)],
int64: &[(&str, &[usize])],
) -> PathBuf {
use std::io::{Seek as _, SeekFrom, Write as _};
let half = tensors.len() / 2;
let mut map: Vec<(&str, String)> = Vec::new();
let mut total = 0usize;
for (position, chunk) in [&tensors[..half], &tensors[half..]].into_iter().enumerate() {
let shard = format!("model-{:05}-of-00002.safetensors", position + 1);
let mut entries = Vec::with_capacity(chunk.len());
let mut offset = 0usize;
for (name, dtype, shape) in chunk {
let elem = match dtype {
DType::F32 => 4,
DType::F16 | DType::BF16 => 2,
other => panic!("a source census holds no {other:?} tensor"),
};
let len = shape.iter().product::<usize>() * elem;
entries.push(format!(
r#""{name}":{{"dtype":"{dtype:?}","shape":{shape:?},"data_offsets":[{offset},{}]}}"#,
offset + len
));
offset += len;
map.push((name.as_str(), shard.clone()));
}
let mut aranges = Vec::new();
if position == 1 {
for &(name, shape) in int64 {
let count = shape.iter().product::<usize>();
entries.push(format!(
r#""{name}":{{"dtype":"I64","shape":{shape:?},"data_offsets":[{offset},{}]}}"#,
offset + count * 8
));
aranges.push((offset, count));
offset += count * 8;
map.push((name, shard.clone()));
}
}
let header = format!("{{{}}}", entries.join(","));
let mut file = std::fs::File::create(dir.join(&shard)).expect("create shard");
file.write_all(&(header.len() as u64).to_le_bytes())
.expect("write prefix");
file.write_all(header.as_bytes()).expect("write header");
file.set_len((8 + header.len() + offset) as u64)
.expect("sparse payload");
for (start, count) in aranges {
let bytes: Vec<u8> = (0..count as i64).flat_map(i64::to_le_bytes).collect();
file.seek(SeekFrom::Start((8 + header.len() + start) as u64))
.expect("seek to the I64 tensor");
file.write_all(&bytes).expect("write arange");
}
total += offset;
}
let pairs: Vec<(&str, &str)> = map
.iter()
.map(|(name, shard)| (*name, shard.as_str()))
.collect();
let index = dir.join(shards::SHARD_INDEX_NAME);
std::fs::write(&index, shards::tests::index_json(&pairs, total as u64))
.expect("write index");
index
}
#[cfg(unix)]
#[test]
fn a_sharded_checkpoint_judges_the_sidecar_inside_its_directory() {
let parent = temp_model_dir("sharded_sidecar_parent");
let dir = parent.join("checkpoint");
std::fs::create_dir_all(&dir).expect("checkpoint dir");
let index =
write_sparse_two_shard_stand_in(&dir, &unlimited_ocr_census::v1_source_tensors(), &[]);
assert_eq!(OcrModel::resolve_model(&dir).expect("resolves"), index);
let arch = validate_model_header_file(&index).expect("the v1 census matches across shards");
assert_eq!(arch.id(), model_arch::UNLIMITED_OCR_V1_ID);
assert!(native_model_available(&dir));
std::fs::write(parent.join("tokenizer.json"), SIDECAR_JSON).expect("parent sidecar");
assert!(
eager_sidecar_tokenizer(arch, &index)
.expect("an absent sidecar is not an open error")
.is_none()
);
let inside = dir.join("tokenizer.json");
std::fs::write(&inside, SIDECAR_JSON).expect("inner sidecar");
let error = load_weights_from_resolved_model_after_validation(
&index,
|_| Ok(()),
|| panic!("weights must not be read past a refused sidecar"),
)
.expect_err("a foreign sidecar is refused");
let message = error.to_string();
assert!(message.contains(&inside.display().to_string()), "{message}");
assert!(
message.contains(arch.tokenizer_sha256().expect("v1 pins its tokenizer")),
"{message}"
);
if let Some(model) = std::env::var_os("FOCR_MODEL_PATH").map(PathBuf::from) {
let real = model.with_file_name("tokenizer.json");
let armed = parent.join("armed");
std::fs::create_dir_all(&armed).expect("armed dir");
std::os::unix::fs::symlink(&real, armed.join("tokenizer.json")).expect("link sidecar");
let tokenizer = eager_sidecar_tokenizer(arch, &armed.join(shards::SHARD_INDEX_NAME))
.expect("the real sidecar passes");
assert!(tokenizer.is_some());
}
}
#[cfg(unix)]
#[test]
fn v2_admission_and_sidecar_come_before_its_weights() {
let test = "v2_admission_and_sidecar_come_before_its_weights";
let started = std::time::Instant::now();
let dir = temp_model_dir("v2_admission").join("checkpoint");
std::fs::create_dir_all(&dir).expect("checkpoint dir");
let index = write_sparse_v2_stand_in(&dir);
let arch = validate_model_header_file(&index).expect("the v2 census matches");
assert_eq!(arch.id(), model_arch::UNLIMITED_OCR_V2_ID);
assert_eq!(model_id_at(&dir), Some(model_arch::UNLIMITED_OCR_V2_ID));
std::fs::write(dir.join("tokenizer.json"), SIDECAR_JSON).expect("foreign sidecar");
let armed = |arch: &'static dyn model_arch::ModelArch| {
lever_policy::admit(
arch,
&lever_policy::LeverInputs {
var: &|name| (name == "FOCR_PREFILL_CHUNK").then(|| "64".to_owned()),
full_int8_forced: false,
},
)
.verdict
};
let error = load_weights_from_resolved_model_after_validation(&index, armed, || {
panic!("v2 weights must not be read past the admission")
})
.expect_err("v2 is refused");
let message = error.to_string();
assert!(matches!(error, FocrError::Usage(_)), "{error:?}");
assert!(
message.contains("FOCR_PREFILL_CHUNK is not supported for unlimited-ocr-2"),
"{message}"
);
assert!(
!message.contains("tokenizer"),
"the sidecar was not judged: {message}"
);
let sidecar = load_weights_from_resolved_model_after_validation(
&index,
|_| Ok(()),
|| panic!("weights must not be read past a refused sidecar"),
)
.expect_err("the foreign sidecar is refused")
.to_string();
assert!(sidecar.contains("tokenizer.json"), "{sidecar}");
let Err(public) = OcrModel::load(&dir) else {
panic!("OcrModel::load refuses the foreign sidecar");
};
let admitted = lever_policy::admit_from_process(arch).verdict.is_ok();
if admitted {
assert_eq!(public.exit_code(), 7, "{public}");
assert!(public.to_string().contains("tokenizer.json"), "{public}");
} else {
assert_eq!(public.exit_code(), 2, "{public}");
}
let served = resident_admission(&index).is_some();
assert_eq!(served, admitted);
v2_gated_line(
test,
started,
"sparse_v2_stand_in",
"assert",
"pass",
serde_json::json!({
"assertion": "identified as v2; admission, then sidecar, before weights",
"pass": true, "exit_code": public.exit_code(), "message": message,
"sidecar_message": sidecar, "weights_touched": false,
"resident_admitted": served,
}),
);
if let Some(v1) = std::env::var_os("FOCR_MODEL_PATH").map(PathBuf::from) {
assert_eq!(model_id_at(&v1), Some(model_arch::UNLIMITED_OCR_V1_ID));
assert!(resident_admission(&v1).is_some());
}
if let Some(v2) = std::env::var_os("FOCR_UOCR2_SOURCE_DIR").map(PathBuf::from) {
assert_eq!(model_id_at(&v2), Some(model_arch::UNLIMITED_OCR_V2_ID));
let index = v2.join(shards::SHARD_INDEX_NAME);
assert_eq!(resident_admission(&index).is_some(), admitted);
}
}
struct RouteArch {
decoder: model_arch::Decoder,
variant: Option<model_arch::UnlimitedVariant>,
implemented: bool,
}
impl model_arch::ModelArch for RouteArch {
fn id(&self) -> &'static str {
"route-test"
}
fn display_name(&self) -> &'static str {
"route test"
}
fn license_notice(&self) -> &'static str {
"test"
}
fn default_artifact_basename(&self) -> &'static str {
"route-test.focrq"
}
fn vision_encoder(&self) -> model_arch::VisionEncoder {
model_arch::VisionEncoder::SamClip
}
fn decoder(&self) -> model_arch::Decoder {
self.decoder
}
fn tokenizer(&self) -> model_arch::TokenizerKind {
model_arch::TokenizerKind::DeepSeekBpe
}
fn decode_contract(&self) -> model_arch::DecodeContract {
model_arch::default_arch().decode_contract()
}
fn tasks(&self) -> &'static [model_arch::Task] {
&[model_arch::Task::Ocr]
}
fn maturity(&self) -> model_arch::Maturity {
if self.implemented {
model_arch::Maturity::Ready
} else {
model_arch::Maturity::Planned
}
}
fn required_quant_recipe(&self, _quant: &str) -> Option<&'static str> {
None
}
fn unlimited_variant(&self) -> Option<model_arch::UnlimitedVariant> {
self.variant
}
}
#[test]
fn every_arch_routes_explicitly_and_only_v1_reaches_v1() {
use model_arch::{Decoder, UnlimitedVariant};
for arch in model_arch::registry() {
let single = forward_route(*arch);
let multi = multi_page_route(*arch);
match arch.id() {
model_arch::UNLIMITED_OCR_V1_ID => {
assert_eq!(single.expect("v1 routes"), ForwardRoute::UnlimitedV1);
multi.expect("v1 multi-page routes");
}
model_arch::UNLIMITED_OCR_V2_ID => {
assert_eq!(single.expect("v2 routes"), ForwardRoute::UnlimitedV2);
multi.expect("v2 runs its own document loop");
}
id => {
let expected = match id {
"got-ocr2" => Some(ForwardRoute::GotOcr2),
"smolvlm2" => Some(ForwardRoute::SmolVlm2),
"onechart" => Some(ForwardRoute::OneChart),
"tromr" => Some(ForwardRoute::Tromr),
_ => None,
};
match expected {
Some(route) => assert_eq!(single.expect("zoo routes"), route, "{id}"),
None => {
let error = single.expect_err("planned");
assert_eq!(error.exit_code(), 1, "{id}");
assert!(error.to_string().contains("epic bd-3jo6"), "{error}");
}
}
let multi = multi.expect_err("the zoo parses pages independently");
assert!(multi.to_string().contains("parses pages independently"));
}
}
}
let pairs = [
(Decoder::DeepSeekV2MoeRswa, Some(UnlimitedVariant::V2SrSwa)),
(Decoder::DeepSeekV2MoeSrSwa, Some(UnlimitedVariant::V1Rswa)),
(Decoder::DeepSeekV2MoeRswa, None),
(Decoder::Qwen2Dense, None),
];
for (decoder, variant) in pairs {
for implemented in [false, true] {
let arch = RouteArch {
decoder,
variant,
implemented,
};
let route = forward_route(&arch);
assert!(
!matches!(route, Ok(ForwardRoute::UnlimitedV1)),
"{decoder:?}/{variant:?} reached v1's forward"
);
let error = route.expect_err("no route").to_string();
let expected = if implemented {
"no forward wired"
} else {
"not yet implemented"
};
assert!(error.contains(expected), "{error}");
}
}
for implemented in [false, true] {
let arch = RouteArch {
decoder: Decoder::DeepSeekV2MoeSrSwa,
variant: Some(UnlimitedVariant::V2SrSwa),
implemented,
};
match (forward_route(&arch), multi_page_route(&arch)) {
(Ok(single), Ok(())) if implemented => {
assert_eq!(single, ForwardRoute::UnlimitedV2);
}
(Err(single), Err(multi)) if !implemented => {
assert!(
single.to_string().contains("not yet implemented"),
"{single}"
);
assert!(multi.to_string().contains("not yet implemented"), "{multi}");
}
other => panic!("implemented={implemented}: {other:?}"),
}
}
}
#[test]
fn a_v2_model_routes_every_entry_point_and_windows_long_documents() {
let v2 = model_arch::arch_by_id(model_arch::UNLIMITED_OCR_V2_ID).expect("v2 registered");
let model = OcrModel::assemble(
PathBuf::from("<route-test>"),
SidecarBundle::default(),
v2,
Weights::default(),
None,
);
let missing = Path::new("/nonexistent/franken_ocr_route_test_page.png");
let image = || image::DynamicImage::new_rgb8(4, 4);
let reads_input = |error: FocrError| {
assert!(matches!(error, FocrError::InputDecode(_)), "{error:?}");
};
reads_input(model.forward(missing).expect_err("forward"));
reads_input(model.recognize(missing).expect_err("recognize"));
for result in model.recognize_batch(&[missing, missing]) {
reads_input(result.expect_err("recognize_batch"));
}
let options = DocumentOptions::default();
let document = |source: &mut crate::page_source::PageSource, options: &DocumentOptions| {
model.recognize_document(source, options, &mut |_| {}, None)
};
let files = |count: usize| {
crate::page_source::PageSource::images(vec![missing.to_path_buf(); count])
.expect("a source")
};
let rasters =
|count: usize| crate::page_source::PageSource::rasters(vec![image(); count]).unwrap();
reads_input(document(&mut files(1), &options).expect_err("document"));
let over = UOCR2_UNWINDOWED_PAGE_LIMIT + 1;
for window in [
None,
Some(PageWindow::Off),
Some(PageWindow::Pages(3)),
Some(PageWindow::Pages(UOCR2_UNWINDOWED_PAGE_LIMIT)),
] {
let options = DocumentOptions {
page_window: window,
..options.clone()
};
reads_input(document(&mut files(over), &options).expect_err("unread pages"));
}
let mut source = rasters(over);
let error = model
.recognize_document(
&mut source,
&options,
&mut |_| {},
Some(&mut |_: &DocumentEvent<'_>| panic!("no page decodes")),
)
.expect_err("no weights");
assert!(!matches!(error, FocrError::Usage(_)), "{error:?}");
let own = window::own_pages_per_window(
window::DEFAULT_PAGE_WINDOW,
window::PER_PAGE_EST_BASE,
UOCR2_UNWINDOWED_PAGE_LIMIT,
);
assert_eq!(
source.page_map().len(),
own + 1,
"pages read as they stream"
);
let v1 = OcrModel::assemble(
PathBuf::from("<route-test>"),
SidecarBundle::default(),
model_arch::default_arch(),
Weights::default(),
None,
);
assert!(matches!(
v1.forward(missing),
Err(FocrError::InputDecode(_))
));
let mut source = files(1);
assert!(matches!(
v1.recognize_document(&mut source, &options, &mut |_| {}, None),
Err(FocrError::InputDecode(_))
));
}
#[test]
fn a_batch_keeps_one_end_per_image_and_nothing_from_before_it() {
let v1 = model_arch::arch_by_id(model_arch::UNLIMITED_OCR_V1_ID).expect("v1 registered");
let model = OcrModel::assemble(
PathBuf::from("<batch-ends-test>"),
SidecarBundle::default(),
v1,
Weights::default(),
None,
);
let leave_a_page_behind = || {
*model.last_decode.lock().expect("last_decode") = Some(DecodeReport::v1_decode(
vec![5, 6],
&model.decode_params,
true,
));
*model.chart_meta.lock().expect("chart_meta") = Some(ChartPageMeta {
pred_locs: Vec::new(),
reliable_distance: None,
reliable: Some(false),
});
};
let missing = Path::new("/nonexistent/franken_ocr_batch_ends_test_page.png");
leave_a_page_behind();
let results = model.recognize_batch(&[missing, missing, missing]);
assert_eq!(results.len(), 3);
for result in results {
assert!(
matches!(result, Err(FocrError::InputDecode(_))),
"{result:?}"
);
}
assert_eq!(model.take_last_decode(), None, "the earlier page's report");
assert_eq!(model.take_chart_meta(), None, "the earlier page's verdict");
assert_eq!(model.take_batch_decodes(), vec![None, None, None]);
assert_eq!(model.take_batch_charts(), vec![None, None, None]);
assert!(
model.take_batch_decodes().is_empty(),
"a take consumes them"
);
assert!(model.take_batch_charts().is_empty(), "a take consumes them");
leave_a_page_behind();
assert!(model.recognize_batch(&[]).is_empty());
assert_eq!(model.take_last_decode(), None);
assert_eq!(model.take_chart_meta(), None);
assert!(model.take_batch_decodes().is_empty());
assert!(model.take_batch_charts().is_empty());
}
#[test]
fn decoder_variables_pick_one_decoder() {
use DocumentPrecision::{CachedF32, Mixed, Stateless};
for (stateless, f32, full_int8, want) in [
(false, false, false, Some(Mixed)),
(false, false, true, Some(Mixed)),
(true, false, false, Some(Stateless)),
(true, false, true, Some(Stateless)),
(false, true, false, Some(CachedF32)),
(false, true, true, None),
(true, true, false, None),
(true, true, true, None),
] {
let got = DocumentPrecision::from_flags(stateless, f32, full_int8);
match want {
Some(want) => assert_eq!(
got.ok(),
Some(want),
"stateless {stateless}, f32 {f32}, full int8 {full_int8}"
),
None => assert!(
matches!(&got, Err(FocrError::Usage(m)) if m.contains(DECODE_F32_ENV)),
"stateless {stateless}, f32 {f32}, full int8 {full_int8}: {got:?}"
),
}
}
}
#[test]
fn document_options_plan_what_the_model_decodes_and_refuse_the_rest() {
let v1 = model_arch::UNLIMITED_OCR_V1_ID;
let refused = |options: DocumentOptions, windowed: bool, pages: usize, says: &str| {
let error = options.plan(v1, windowed, pages, || Ok(7)).expect_err(says);
assert!(matches!(error, FocrError::Usage(_)), "{error:?}");
assert!(error.to_string().contains(says), "{error}");
};
let window = |page_window| DocumentOptions::default().page_window(page_window);
let plan = |options: DocumentOptions, windowed: bool, pages: usize| {
options
.plan(v1, windowed, pages, || Ok(7))
.expect("planned")
};
let default = DocumentOptions::default;
assert_eq!(
plan(default(), true, 500),
DocumentPlan::Windows(window::DEFAULT_PAGE_WINDOW)
);
assert_eq!(plan(default(), false, 500), DocumentPlan::OnePass);
for windowed in [true, false] {
assert_eq!(
plan(window(PageWindow::Off), windowed, 500),
DocumentPlan::OnePass
);
refused(
window(PageWindow::Pages(0)),
windowed,
3,
"at least one page",
);
assert_eq!(
plan(window(PageWindow::Auto), windowed, 500),
if windowed {
DocumentPlan::Windows(7)
} else {
DocumentPlan::OnePass
}
);
let no_room = window(PageWindow::Auto).plan(v1, windowed, 500, || {
Err(FocrError::Usage("no room".into()))
});
assert_eq!(no_room.is_err(), windowed, "only a window can be refused");
for resume in [ResumePolicy::IfMatching, ResumePolicy::Recompute] {
refused(default().resume(resume), windowed, 1, "resume");
let files = checkpoint::RunFiles::beside(
std::path::Path::new("doc.md"),
checkpoint::ResumeKey::new(),
);
let with_files = default().resume(resume).run(files);
if windowed {
assert!(matches!(
plan(with_files.clone(), true, 500),
DocumentPlan::Windows(_)
));
refused(
with_files.page_window(PageWindow::Off),
true,
500,
"no window to resume",
);
} else {
refused(with_files, false, 500, "no window to resume");
}
}
}
assert_eq!(
plan(window(PageWindow::Pages(2)), true, 3),
DocumentPlan::Windows(2)
);
assert_eq!(
plan(window(PageWindow::Pages(3)), false, 3),
DocumentPlan::OnePass
);
refused(
window(PageWindow::Pages(2)),
false,
3,
"has no windowed decoding",
);
assert_eq!(
DocumentOptions::default().resume(ResumePolicy::Never),
DocumentOptions::default()
);
}
fn v2_gated_line(
test: &str,
started: std::time::Instant,
case: &str,
event: &str,
result: &str,
extra: serde_json::Value,
) {
let mut object = serde_json::json!({
"schema_version": 1, "ts": started.elapsed().as_secs_f64(), "test": test,
"case": case, "run_seq": 0, "event": event, "result": result,
});
if let (Some(object), Some(extra)) = (object.as_object_mut(), extra.as_object()) {
object.extend(extra.clone());
}
println!("{object}");
}
#[test]
fn real_v2_index_and_shard_headers_parse_through_the_resolver() {
let test = "real_v2_index_and_shard_headers_parse_through_the_resolver";
let started = std::time::Instant::now();
let line = |case: &str, event: &str, result: &str, extra: serde_json::Value| {
v2_gated_line(test, started, case, event, result, extra);
};
let Some(dir) = std::env::var_os("FOCR_UOCR2_SOURCE_DIR").map(PathBuf::from) else {
line(
"v2",
"skip",
"skip_no_model",
serde_json::json!({
"reason": "FOCR_UOCR2_SOURCE_DIR unset",
}),
);
return;
};
let index = OcrModel::resolve_model(&dir).expect("the directory resolves");
assert_eq!(index, dir.join(shards::SHARD_INDEX_NAME));
let opened = shards::open_shards(&index).expect("every shard opens");
for shard in &opened.shards {
let file = std::fs::File::open(&shard.path).expect("open shard");
let (header, _, _) =
read_validated_safetensors_header(&file, shard.len).expect("shard header");
line(
&shard.name,
"assert",
"pass",
serde_json::json!({
"assertion": "shard header parses and validates",
"pass": true,
"bytes": shard.len, "header_ok": true, "tensors": header.len(),
"hashed": false,
}),
);
}
let merged = read_opened_shard_headers(&opened).expect("headers merge against the index");
assert_eq!(opened.index.shards().len(), 2);
assert_eq!(opened.index.total_size(), 6_687_958_536);
assert_eq!(merged.len(), 2_819, "the v2 tensor count");
assert_eq!(
merged.int64.keys().collect::<Vec<_>>(),
["model.vision_model.embeddings.position_ids"],
"exactly one I64 tensor"
);
let arch = identify_checkpoint(&merged).expect("the headers pass the v2 census");
assert_eq!(arch.id(), model_arch::UNLIMITED_OCR_V2_ID);
let rules = shards::int64_rules(Some(arch));
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].name, "model.vision_model.embeddings.position_ids");
line(
"v2",
"result",
"pass",
serde_json::json!({
"elapsed_us": started.elapsed().as_micros() as u64,
"tensors": merged.len(), "int64": merged.int64.len(),
"source_sha256": serde_json::Value::Null,
}),
);
}
#[test]
fn real_v2_checkpoint_loads_raw_and_matches_its_pins() {
let test = "real_v2_checkpoint_loads_raw_and_matches_its_pins";
let started = std::time::Instant::now();
let line = |case: &str, event: &str, result: &str, extra: serde_json::Value| {
v2_gated_line(test, started, case, event, result, extra);
};
let Some(dir) = std::env::var_os("FOCR_UOCR2_SOURCE_DIR").map(PathBuf::from) else {
line(
"v2",
"skip",
"skip_no_model",
serde_json::json!({"reason": "FOCR_UOCR2_SOURCE_DIR unset"}),
);
return;
};
let index = OcrModel::resolve_model(&dir).expect("the directory resolves");
let opened = load_weights_from_resolved_model(&index).expect("the raw v2 load succeeds");
assert_eq!(opened.arch.id(), model_arch::UNLIMITED_OCR_V2_ID);
assert_eq!(opened.weights.model_id(), model_arch::UNLIMITED_OCR_V2_ID);
assert!(!opened.weights.is_focrq());
assert_eq!(
opened.weights.len(),
2_818,
"2,819 less the verified I64 tensor"
);
assert!(
!opened
.weights
.contains("model.vision_model.embeddings.position_ids")
);
let tokenizer = opened.tokenizer.as_ref().expect("the pinned sidecar loads");
assert_eq!(
tokenizer.token_to_id("<PAGE>"),
Some(crate::tokenizer::special::PAGE)
);
line(
"raw_load",
"assert",
"pass",
serde_json::json!({
"assertion": "raw v2 load identifies unlimited-ocr-2 and passes its census",
"pass": true, "tensors": opened.weights.len(), "hashed": false,
}),
);
let digests = shards::hash_shards(&index).expect("hash both shards");
let pinned = unlimited_ocr_census::source_shards(model_arch::TensorCensus::UnlimitedOcrV2);
assert_eq!(digests.len(), pinned.len());
for (digest, (name, sha256)) in digests.iter().zip(pinned) {
let hex: String = digest.sha256.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!((digest.name.as_str(), hex.as_str()), (*name, *sha256));
line(
name,
"assert",
"pass",
serde_json::json!({
"assertion": "shard sha256 equals its pin",
"pass": true, "bytes": digest.size, "hashed": true,
}),
);
}
let source: String = shards::source_sha256(&digests)
.iter()
.map(|b| format!("{b:02x}"))
.collect();
assert_eq!(source, unlimited_ocr_census::V2_SOURCE_SHA256);
let v1 = std::env::var_os("FOCR_MODEL_PATH")
.map(PathBuf::from)
.map(|path| load_weights_from_resolved_model(&path).expect("the v1 artifact loads"));
if let Some(v1) = &v1 {
assert_eq!(v1.weights.model_id(), model_arch::UNLIMITED_OCR_V1_ID);
}
let identity = unlimited_ocr_census::v2_vision_identity_sha256();
assert_eq!(identity.len(), 476);
for (name, expected) in identity {
use sha2::Digest as _;
let view = opened.weights.tensor(name).expect("vision tensor present");
let actual = format!("{:x}", sha2::Sha256::digest(view.data));
assert_eq!(&actual, expected, "{name}");
if let Some(v1) = &v1 {
let v1_view = v1.weights.tensor(name).expect("v1 has the vision tensor");
assert_eq!(v1_view.dtype, DType::BF16, "{name}");
assert!(v1_view.data == view.data, "{name} differs from v1");
}
}
line(
"vision_identity",
"assert",
"pass",
serde_json::json!({
"assertion": "476 vision tensors match the census hashes",
"pass": true, "compared_to_v1": v1.is_some(),
}),
);
line(
"v2",
"result",
"pass",
serde_json::json!({
"elapsed_us": started.elapsed().as_micros() as u64,
"source_sha256": source,
}),
);
}
fn v2_gated_model(test: &str, started: std::time::Instant) -> Option<OcrModel> {
let Some(dir) = std::env::var_os("FOCR_UOCR2_SOURCE_DIR").map(PathBuf::from) else {
v2_gated_line(
test,
started,
"v2",
"skip",
"skip_no_model",
serde_json::json!({"reason": "FOCR_UOCR2_SOURCE_DIR unset"}),
);
return None;
};
let index = OcrModel::resolve_model(&dir).expect("the directory resolves");
let opened = load_weights_from_resolved_model(&index).expect("the raw v2 load succeeds");
assert_eq!(opened.arch.id(), model_arch::UNLIMITED_OCR_V2_ID);
Some(OcrModel::assemble(
index,
SidecarBundle::default(),
opened.arch,
opened.weights,
opened.tokenizer,
))
}
#[test]
fn retire_fallback_reruns_the_document_unretired_once() {
use crate::tokenizer::special::PAGE;
let fired = || {
FocrError::Other(
anyhow::Error::new(srswa::RetireFallback {
q_pos: 700,
query_page: 1,
open: srswa::OpenRow::NoRemoteAllowed,
dead_blocks: 4,
})
.context("layer 3"),
)
};
assert!(
srswa::retire_fallback(&fired()).is_some(),
"through context"
);
let ids = [PAGE, 10, 11, PAGE, 12];
let run = |retire: bool, fire_at: Option<usize>, rerun: &[u32], cancel: bool| {
let mut calls = Vec::new();
let mut seen = Vec::new();
let mut observer = |id: u32| seen.push(id);
let result = decode_with_retire_fallback(
retire,
Some(&mut observer),
|retire, floor, out, mut obs| {
calls.push((retire, floor));
out.clear();
let emit = if calls.len() == 1 { &ids[..] } else { rerun };
for (k, &id) in emit.iter().enumerate() {
out.push(id);
if let Some(f) = obs.as_deref_mut() {
f(id);
}
if retire && fire_at == Some(k + 1) {
return Err(fired());
}
if cancel && k == 1 {
return Err(FocrError::Cancelled);
}
}
Ok(DocumentDecode {
ids: out.clone(),
finish_reason: FinishReason::Stop,
stats: DocumentStats::default(),
})
},
);
(result, calls, seen)
};
let (result, calls, seen) = run(true, Some(3), &ids, false);
let decode = result.expect("the rerun completes");
assert_eq!(decode.ids, ids);
assert_eq!(calls, [(true, 0), (false, 3)]);
assert_eq!(seen, ids, "each id observed once, in order");
assert_eq!(decode.stats.retire_fallbacks, 1);
let (result, calls, seen) = run(false, Some(3), &ids, false);
assert_eq!(result.expect("never fires").stats.retire_fallbacks, 0);
assert_eq!((calls, seen), (vec![(false, 0)], ids.to_vec()));
let (result, calls, _) = run(true, None, &ids, true);
assert!(matches!(result, Err(FocrError::Cancelled)));
assert_eq!(calls, [(true, 0)], "no rerun on another error");
let (result, calls, seen) = run(true, Some(3), &[PAGE, 10, 99, PAGE, 12], false);
let message = result.expect_err("a departing rerun").to_string();
assert!(
message.contains("departs") && message.contains("step 3"),
"{message}"
);
assert_eq!(calls, [(true, 0), (false, 3)]);
assert_eq!(
seen,
[PAGE, 10, 11, PAGE, 12],
"the departure is reported, not streamed"
);
}
#[test]
fn paged_prompt_reproduces_the_v2_oracle_prompts() {
use crate::tokenizer::special;
use sha2::Digest as _;
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let read = |rel: &str| -> serde_json::Value {
serde_json::from_slice(&std::fs::read(root.join(rel)).unwrap()).unwrap()
};
let golden = read("tokenizer_baidu/expected_uocr2.json");
let one_page = golden["fixtures"]
.as_array()
.unwrap()
.iter()
.find(|r| r["text"] == "<PAGE><image>Multi page parsing.")
.expect("the one-page prompt is in the golden");
let ids: Vec<u32> = one_page["ids"]
.as_array()
.unwrap()
.iter()
.map(|id| u32::try_from(id.as_u64().unwrap()).unwrap())
.collect();
assert_eq!(ids[..2], [special::PAGE, special::IMAGE]);
let text = &ids[2..];
let oracle = read("uocr2_oracle/ORACLE.json");
let block = |mode: preprocess::PreprocessMode, (w, h): (u32, u32)| {
let page = image::DynamicImage::new_rgb8(w, h);
preprocess::preprocess_dynamic(page, mode, preprocess::PreprocessProfile::V2)
.unwrap()
.placeholder_token_count()
};
let mut blocks = std::collections::BTreeMap::new();
let fixtures = oracle["fixtures"].as_object().unwrap();
assert_eq!(fixtures.len(), 10, "S1–S4 and the 40-page S5 (U2-A6b)");
for (name, f) in fixtures {
let mode = match f["mode"].as_str().unwrap() {
"base" => preprocess::PreprocessMode::Base { base_size: 1024 },
"gundam" => preprocess::PreprocessMode::Gundam {
base_size: 1024,
tile_size: 640,
},
other => panic!("{name}: mode {other}"),
};
let counts: Vec<usize> = f["pages"]
.as_array()
.unwrap()
.iter()
.map(|p| {
let size = |k: &str| u32::try_from(p[k].as_u64().unwrap()).unwrap();
let key = (f["mode"].to_string(), size("width"), size("height"));
*blocks
.entry(key)
.or_insert_with(|| block(mode, (size("width"), size("height"))))
})
.collect();
let (prompt, mask) = paged_prompt(0, special::PAGE, special::IMAGE, &counts, text);
assert_eq!(
prompt.len() as u64,
f["prompt_token_count"].as_u64().unwrap(),
"{name}"
);
let json = serde_json::to_string(&prompt).unwrap();
let sha: String = sha2::Sha256::digest(json.as_bytes())
.iter()
.map(|b| format!("{b:02x}"))
.collect();
assert_eq!(sha, f["prompt_token_ids_sha256"], "{name}");
assert_eq!(
mask.iter().filter(|&&m| m).count(),
counts.iter().sum::<usize>(),
"{name}"
);
assert!(
mask.iter()
.zip(&prompt)
.all(|(&m, &id)| m == (id == special::IMAGE))
);
}
assert_eq!(
blocks.values().copied().collect::<Vec<_>>(),
[273, 273, 273 + 630],
"base 1182×1837, base 1191×1810, then gundam"
);
}
#[test]
fn real_v2_document_loop_decodes_oracle_fixtures() {
use crate::tokenizer::special;
let test = "real_v2_document_loop_decodes_oracle_fixtures";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
for (fixture, pages, stream) in [
("S1-page_0009", &["page_0009.png"][..], false),
("S3-p9_p14", &["page_0009.png", "page_0014.png"][..], true),
] {
let Some(paths) = v2_corpus_pages(test, started, fixture, pages) else {
continue;
};
let pres: Vec<Preprocessed> = paths
.iter()
.map(|p| {
let img = image::open(p).expect("page decodes");
model.document_page(img).expect("preprocess")
})
.collect();
let t = std::time::Instant::now();
let mut events: Vec<(usize, String, usize, PageFinish)> = Vec::new();
let mut sink = |p: &ClosedPage<'_>| {
events.push((p.index, p.body.to_string(), p.tokens, p.finish));
};
let decoded = model
.decode_document(
&pres,
stream.then_some(&mut sink as &mut dyn FnMut(&ClosedPage<'_>)),
)
.expect("the document decodes");
let split = &decoded.split;
let report = model.take_last_decode().expect("a decode report");
let stats = report.document.clone().expect("document stats");
let n = pages.len();
let oracle = v2_oracle_ids(fixture);
let agree = report
.ids
.iter()
.zip(&oracle)
.take_while(|(a, b)| a == b)
.count();
let near = |ids: &[u32]| ids[agree.min(ids.len())..(agree + 8).min(ids.len())].to_vec();
v2_gated_line(
test,
started,
fixture,
"decode",
"info",
serde_json::json!({
"pages_expected": n, "pages_produced": split.bodies.len(),
"finish_reason": report.finish_reason.as_str(),
"tokens": report.ids.len(), "oracle_tokens": oracle.len(),
"info_agreeing_prefix": agree,
"info_exact": report.ids == oracle,
"info_ours_at_divergence": near(&report.ids),
"info_oracle_at_divergence": near(&oracle),
"page_tokens": stats.page_tokens,
"page_finish": stats.page_finish.iter().map(|f| f.as_str()).collect::<Vec<_>>(),
"extra_markers": stats.extra_markers,
"leading_text": stats.leading_text,
"forced_steps": stats.forced_steps,
"disc_u2_2_steps": stats.hf_all_neg_inf_steps,
"retired_blocks": stats.retired_blocks,
"peak_kv_bytes": stats.peak_kv_bytes,
"retire_fallbacks": stats.retire_fallbacks,
"wall_s": t.elapsed().as_secs_f64(),
}),
);
assert_eq!(report.finish_reason, FinishReason::Stop, "{fixture}");
assert_eq!(split.bodies.len(), n, "{fixture}");
assert_eq!(
report.ids.iter().filter(|&&id| id == special::PAGE).count(),
n,
"{fixture}: one marker per page"
);
let mut want = vec![PageFinish::Marker; n - 1];
want.push(PageFinish::Eos);
assert_eq!(stats.page_finish, want, "{fixture}");
assert_eq!(stats.hf_all_neg_inf_steps, 0, "{fixture}: DISC-U2-2");
assert_eq!(stats.retire_fallbacks, 0, "{fixture}");
assert_eq!(
stats.page_tokens.iter().sum::<usize>() + n,
report.ids.len()
);
assert_eq!(decoded.finish, stats.page_finish, "{fixture}");
assert_eq!(decoded.elapsed.len(), n, "{fixture}: a clock per page");
if stream {
let split_pages: Vec<(usize, String, usize, PageFinish)> = split
.bodies
.iter()
.zip(&split.tokens)
.zip(&stats.page_finish)
.enumerate()
.map(|(i, ((b, &t), &f))| (i + 1, b.trim().to_string(), t, f))
.collect();
assert_eq!(events, split_pages, "{fixture}: streamed pages");
}
v2_gated_line(
test,
started,
fixture,
"assert",
"pass",
serde_json::json!({}),
);
}
}
#[test]
fn real_v2_teacher_forced_f32_follows_the_oracle_across_pages() {
use crate::tokenizer::special;
let test = "real_v2_teacher_forced_f32_follows_the_oracle_across_pages";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let fixture = "S3-p9_p14";
let Some(paths) =
v2_corpus_pages(test, started, fixture, &["page_0009.png", "page_0014.png"])
else {
return;
};
let (embeds, prompt) = v2_decoder_prompt(&model, &paths);
let oracle = v2_oracle_ids(fixture);
let params = model.document_params(2);
let table = model.embed_table().expect("embed table");
let guard_after = |t: usize| {
let mut guard = sampler::PageGuard::new(
2,
special::PAGE,
params.eos_token_id,
params.max_page_tokens.min(params.max_length),
);
for &id in &oracle[..t] {
guard.observe(id);
}
guard
};
let pick = |logits: &Mat, t: usize| {
let step = sampler::decode_step_guarded(logits, &oracle[..t], &guard_after(t), ¶ms)
.expect("a guarded step");
(!step.is_eos).then_some(step.token_id)
};
let gap = |logits: &Mat, want: Option<u32>| {
let id = want.unwrap_or(params.eos_token_id) as usize;
let best = logits
.data
.iter()
.copied()
.fold(f32::NEG_INFINITY, f32::max);
best - logits.data[id]
};
let markers: Vec<usize> = oracle
.iter()
.enumerate()
.filter_map(|(i, &id)| (id == special::PAGE).then_some(i))
.collect();
assert_eq!(markers.len(), 2, "the oracle marks both pages");
let mut probes = vec![
0,
markers[1],
markers[1] + 1,
73,
74,
(markers[1] + oracle.len()) / 2,
oracle.len() - 1,
oracle.len(),
];
probes.sort_unstable();
probes.dedup();
let mut f32_misses = Vec::new();
for t in probes {
let tp = std::time::Instant::now();
let mut data = embeds.data.clone();
for &id in &oracle[..t] {
data.extend(table.row_f32(id as usize));
}
let mut ids = prompt.clone();
ids.extend_from_slice(&oracle[..t]);
let grown = Mat::from_vec(embeds.rows + t, embeds.cols, data);
let logits = model
.document_logits(&DocumentDecoder::Stateless {
embeds: grown,
ids,
domain: srswa::EntmaxDomain::TopM,
})
.expect("f32 logits");
let want = oracle.get(t).copied();
let got = pick(&logits, t);
v2_gated_line(
test,
started,
fixture,
"f32_probe",
if got == want { "pass" } else { "fail" },
serde_json::json!({
"step": t, "want": want, "got": got,
"oracle_gap": gap(&logits, want), "wall_s": tp.elapsed().as_secs_f64(),
}),
);
if got != want {
f32_misses.push((t, want, got));
}
}
let wc = model.decoder_cache().expect("decoder cache");
let coords = srswa::PageCoords::build(&prompt).expect("page coords");
let (hidden, state) =
decoder::prefill_with_cache_srswa(wc, &embeds, &coords, srswa::EntmaxDomain::TopM)
.expect("prefill");
let mut cached = DocumentDecoder::Cached {
wc,
state,
hidden: OcrModel::last_hidden_row(&hidden).expect("last row"),
};
let mut cached_misses = Vec::new();
for t in 0..=oracle.len() {
let logits = model.document_logits(&cached).expect("cached logits");
let want = oracle.get(t).copied();
let got = pick(&logits, t);
if got != want {
cached_misses.push(serde_json::json!({
"step": t, "want": want, "got": got, "oracle_gap": gap(&logits, want),
}));
}
let Some(id) = want else { break };
let DocumentDecoder::Cached { wc, state, hidden } = &mut cached else {
unreachable!("built cached")
};
let forced = matches!(
guard_after(t + 1).verdict(),
sampler::GuardVerdict::Force(_)
);
let row = Mat::from_vec(1, embeds.cols, table.row_f32(id as usize));
let h = decoder::decode_step_with_cache_srswa(wc, state, id, &row, forced)
.expect("cached step");
*hidden = OcrModel::last_hidden_row(&h).expect("last row");
}
v2_gated_line(
test,
started,
fixture,
"cached_teacher_forced",
"info",
serde_json::json!({
"steps": oracle.len() + 1, "disagreements": cached_misses.len(),
"first": cached_misses.iter().take(16).collect::<Vec<_>>(),
}),
);
assert!(
f32_misses.is_empty(),
"the f32 path departs from the oracle (step, want, got): {f32_misses:?}"
);
}
#[test]
fn real_v2_document_limits_stop_where_the_contract_says() {
use crate::tokenizer::special::PAGE;
let test = "real_v2_document_limits_stop_where_the_contract_says";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let fixture = "S2-p9_p14_p19";
let pages = ["page_0009.png", "page_0014.png", "page_0019.png"];
let Some(paths) = v2_corpus_pages(test, started, fixture, &pages) else {
return;
};
let (embeds, prompt) = v2_decoder_prompt(&model, &paths);
let tok = model.tokenizer().expect("tokenizer");
let base = model.document_params(pages.len());
let run = |params: &DecodeParams, retire: bool, deadline: Option<Instant>| {
let pass = DocumentPass {
precision: DocumentPrecision::Mixed,
retire,
deadline,
budget_floor: 0,
};
let mut ids = Vec::new();
let decode = model
.generate_document_once(&embeds, &prompt, params, pass, &mut ids, None)
.expect("the pass decodes");
let split = postprocess::split_pages_by_token(tok, &decode.ids, PAGE, pages.len())
.expect("the split");
(decode, split)
};
let finishes = |decode: &DocumentDecode| -> Vec<&'static str> {
decode
.stats
.page_finish
.iter()
.map(|f| f.as_str())
.collect()
};
let capped = DecodeParams {
max_length: 50,
..base.clone()
};
let t = Instant::now();
let (retired, split) = run(&capped, true, None);
let (plain, _) = run(&capped, false, None);
v2_gated_line(
test,
started,
"page_cap_50",
"decode",
"info",
serde_json::json!({
"pages_expected": pages.len(), "pages_produced": split.bodies.len(),
"finish_reason": retired.finish_reason.as_str(), "page_tokens": split.tokens,
"page_finish": finishes(&retired), "forced_steps": retired.stats.forced_steps,
"disc_u2_2_steps": retired.stats.hf_all_neg_inf_steps,
"retired_blocks": retired.stats.retired_blocks,
"peak_kv_bytes": retired.stats.peak_kv_bytes,
"unretired_peak_kv_bytes": plain.stats.peak_kv_bytes,
"wall_s": t.elapsed().as_secs_f64(),
}),
);
assert_eq!(retired.finish_reason, FinishReason::Stop);
assert_eq!(split.bodies.len(), pages.len());
assert_eq!(retired.stats.page_finish.len(), pages.len());
for (k, (&tokens, finish)) in split
.tokens
.iter()
.zip(&retired.stats.page_finish)
.enumerate()
{
assert!(tokens <= 50, "page {}: {tokens} ids", k + 1);
assert_eq!(
*finish == PageFinish::PageCap,
tokens == 50,
"page {}: {finish:?} after {tokens} ids",
k + 1
);
}
assert!(
retired.stats.page_finish.contains(&PageFinish::PageCap),
"the cap bound at least one page"
);
assert_eq!(retired.stats.hf_all_neg_inf_steps, 0, "DISC-U2-2");
assert_eq!(plain.ids, retired.ids, "retired ≡ unretired");
assert_eq!(plain.stats.retired_blocks, 0);
let positions = DecodeParams {
max_positions: prompt.len() + 60,
..base.clone()
};
let (stopped, split) = run(&positions, true, None);
v2_gated_line(
test,
started,
"position_limit",
"decode",
"info",
serde_json::json!({
"max_positions": positions.max_positions, "tokens": stopped.ids.len(),
"pages_produced": split.bodies.len(), "page_tokens": split.tokens,
"finish_reason": stopped.finish_reason.as_str(),
"page_finish": finishes(&stopped),
}),
);
assert_eq!(stopped.finish_reason, FinishReason::Length);
assert_eq!(stopped.ids.len(), 60, "pos + 1 reached the limit");
assert_eq!(stopped.stats.page_finish.last(), Some(&PageFinish::Length));
assert_eq!(stopped.stats.page_finish.len(), split.bodies.len());
assert!(split.bodies.len() < pages.len());
let (spent, split) = run(&base, true, Some(Instant::now()));
v2_gated_line(
test,
started,
"budget_spent",
"decode",
"info",
serde_json::json!({
"tokens": spent.ids.len(), "pages_produced": split.bodies.len(),
"finish_reason": spent.finish_reason.as_str(), "page_finish": finishes(&spent),
}),
);
assert_eq!(spent.finish_reason, FinishReason::Budget);
assert!(spent.ids.is_empty());
assert_eq!(spent.stats.page_finish, [PageFinish::Budget]);
assert!(split.bodies.is_empty());
v2_gated_line(
test,
started,
fixture,
"assert",
"pass",
serde_json::json!({}),
);
}
#[test]
fn real_v2_entmax_domains_decode_s1_alike() {
let test = "real_v2_entmax_domains_decode_s1_alike";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let fixture = "S1-page_0009";
let Some(paths) = v2_corpus_pages(test, started, fixture, &["page_0009.png"]) else {
return;
};
let oracle = v2_oracle_ids(fixture);
let (embeds, prompt) = v2_decoder_prompt(&model, &paths);
let decode = |domain: srswa::EntmaxDomain| {
let params = DecodeParams {
entmax_domain: domain,
..model.document_params(1)
};
let pass = DocumentPass {
precision: DocumentPrecision::Mixed,
retire: true,
deadline: None,
budget_floor: 0,
};
let t = Instant::now();
let mut ids = Vec::new();
let decode = model
.generate_document_once(&embeds, &prompt, ¶ms, pass, &mut ids, None)
.expect("the pass decodes");
(decode, t.elapsed().as_secs_f64())
};
let (topm, topm_s) = decode(srswa::EntmaxDomain::TopM);
let (all, all_s) = decode(srswa::EntmaxDomain::AllCandidates);
let first_diff = topm.ids.iter().zip(&all.ids).position(|(a, b)| a != b);
v2_gated_line(
test,
started,
fixture,
"decode",
"info",
serde_json::json!({
"topm_tokens": topm.ids.len(), "all_tokens": all.ids.len(),
"oracle_tokens": oracle.len(), "first_diff": first_diff,
"topm_finish": topm.finish_reason.as_str(),
"all_finish": all.finish_reason.as_str(),
"topm_wall_s": topm_s, "all_wall_s": all_s,
}),
);
assert_eq!(all.ids, topm.ids, "D21: the domains agree on S1");
assert_eq!(topm.ids, oracle, "the top-M decode is the oracle's");
v2_gated_line(
test,
started,
fixture,
"assert",
"pass",
serde_json::json!({}),
);
}
#[test]
fn real_v2_l4_s1_pages_decode_the_oracle_ids() {
v2_l4(
"real_v2_l4_s1_pages_decode_the_oracle_ids",
&["S1-page_0009", "S1-page_0014", "S1-page_0590"],
);
}
#[test]
fn real_v2_l4_documents_decode_the_oracle_ids() {
v2_l4(
"real_v2_l4_documents_decode_the_oracle_ids",
&["S2-p9_p14_p19", "S3-p9_p14"],
);
}
#[test]
fn real_v2_l4_long_documents_decode_the_oracle_ids() {
let test = "real_v2_l4_long_documents_decode_the_oracle_ids";
let long = matches!(
std::env::var("FOCR_E2E_LONG").ok().as_deref(),
Some(v) if !v.is_empty() && v != "0"
);
if !long {
v2_gated_line(
test,
std::time::Instant::now(),
"S3-p10,S3-p20",
"skip",
"skip",
serde_json::json!({"reason": "FOCR_E2E_LONG unset or 0"}),
);
return;
}
v2_l4(test, &["S3-p10", "S3-p20"]);
}
fn v2_windowed(
model: &OcrModel,
paths: &[PathBuf],
opts: &window::WindowedOptions,
bodies: &mut Vec<Vec<String>>,
) -> window::WindowedDocument {
let mut pages = paths.iter().map(|path| {
let img = image::open(path)
.map_err(|e| FocrError::Other(anyhow::anyhow!("{}: {e}", path.display())))?;
model.document_page(img)
});
let mut sink = |record: &window::WindowRecord, _: Option<&window::WindowBoundary<'_>>| {
bodies.push(record.bodies.clone())
};
model
.decode_document_windowed(&mut pages, opts, Some(&mut sink))
.expect("the windowed decode runs")
.into_windowed()
.expect("the document is windowed")
}
fn v2_windowed_line(doc: &window::WindowedDocument) -> serde_json::Value {
let windows: Vec<serde_json::Value> = doc
.windows
.iter()
.map(|w| {
serde_json::json!({
"index": w.index, "pages": [w.pages.start, w.pages.end],
"lookahead": w.lookahead, "reused": w.reused,
"fallback_reason": w.fallback_reason.map(window::FallbackReason::as_str),
"straddle": w.straddle, "prefill_tokens": w.prefill_tokens,
"copied_rows": w.copied_rows, "forced_tokens": w.forced_tokens,
"generated_tokens": w.generated_tokens, "finish": w.finish.as_str(),
"kv_bytes_peak": w.kv_bytes_peak, "retire_fallbacks": w.retire_fallbacks,
"elapsed_ms": w.elapsed_ms,
})
})
.collect();
serde_json::json!({
"finish_reason": doc.finish_reason.as_str(), "bodies": doc.pages.bodies.len(),
"extra_markers": doc.pages.extra_markers, "leading_text": doc.pages.leading_text,
"retired_blocks": doc.stats.retired_blocks, "peak_kv_bytes": doc.stats.peak_kv_bytes,
"windows": windows,
})
}
#[test]
fn real_v2_windowed_budget_keeps_the_finished_windows() {
let test = "real_v2_windowed_budget_keeps_the_finished_windows";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let names = ["page_0009.png", "page_0014.png", "page_0019.png"];
let Some(paths) = v2_corpus_pages(test, started, "S3-p3", &names) else {
return;
};
let opts = window::WindowedOptions {
window: 1,
budget_spent_at: Some(1),
precision: Some(DocumentPrecision::Mixed),
..window::WindowedOptions::default()
};
let mut bodies = Vec::new();
let doc = v2_windowed(&model, &paths, &opts, &mut bodies);
assert_eq!(doc.finish_reason, FinishReason::Budget);
assert_eq!(
doc.windows.len(),
2,
"the document stops at the spent window"
);
assert_eq!(doc.windows[0].finish, FinishReason::Stop);
let spent = &doc.windows[1];
assert_eq!(spent.finish, FinishReason::Budget);
assert_eq!(
(spent.prefill_tokens, spent.copied_rows),
(0, 0),
"no work claimed"
);
assert_eq!((spent.forced_tokens, spent.generated_tokens), (0, 0));
assert_eq!(bodies.len(), 2);
assert_eq!(bodies[0].len(), 1, "window 0 owns one page");
assert!(bodies[1].is_empty(), "no body for a page never reached");
assert_eq!(doc.pages.bodies, bodies[0]);
assert!(!doc.pages.bodies[0].trim().is_empty());
assert!(matches!(
doc.stats.page_finish.as_slice(),
[PageFinish::Marker | PageFinish::Eos, PageFinish::Budget]
));
v2_gated_line(
test,
started,
"S3-p3",
"windowed",
"pass",
v2_windowed_line(&doc),
);
}
#[test]
fn real_v2_windowed_25_pages_own_every_page_once() {
use crate::tokenizer::special::PAGE;
let test = "real_v2_windowed_25_pages_own_every_page_once";
let started = std::time::Instant::now();
let long = matches!(
std::env::var("FOCR_E2E_LONG").ok().as_deref(),
Some(v) if !v.is_empty() && v != "0"
);
if !long {
let why = serde_json::json!({"reason": "FOCR_E2E_LONG unset or 0"});
v2_gated_line(test, started, "corpus-25", "skip", "skip", why);
return;
}
let Some(model) = v2_gated_model(test, started) else {
return;
};
let corpus = [
"page_0009",
"page_0014",
"page_0019",
"page_0023",
"page_0086",
"page_0091",
"page_0105",
"page_0107",
"page_0108",
"page_0126",
"page_0136",
"page_0179",
"page_0250",
"page_0358",
"page_0400",
"page_0437",
"page_0547",
"page_0557",
"page_0590",
"page_0645",
];
let names: Vec<String> = corpus
.iter()
.chain(&corpus[..5])
.map(|p| format!("{p}.png"))
.collect();
let names: Vec<&str> = names.iter().map(String::as_str).collect();
let Some(paths) = v2_corpus_pages(test, started, "corpus-25", &names) else {
return;
};
let opts = window::WindowedOptions {
window: window::DEFAULT_PAGE_WINDOW,
..window::WindowedOptions::default()
};
let mut bodies = Vec::new();
let doc = v2_windowed(&model, &paths, &opts, &mut bodies);
let spans: Vec<(usize, usize, bool)> = doc
.windows
.iter()
.map(|w| (w.pages.start, w.pages.end, w.lookahead))
.collect();
assert_eq!(spans, [(0, 10, true), (10, 20, true), (20, 25, false)]);
assert_eq!(doc.finish_reason, FinishReason::Stop);
for (w, ids) in doc.windows.iter().zip(&doc.window_ids) {
assert_eq!(w.finish, FinishReason::Stop, "window {}", w.index);
assert_eq!(w.reused, w.index > 0, "window {}", w.index);
assert_eq!(bodies[w.index].len(), w.pages.len(), "window {}", w.index);
if w.lookahead {
let opening = w.pages.len() + 1;
let markers: Vec<usize> = (0..ids.len()).filter(|&i| ids[i] == PAGE).collect();
assert!(markers.len() <= opening, "window {}", w.index);
if markers.len() == opening {
assert_eq!(markers[opening - 1], ids.len() - 1, "window {}", w.index);
}
}
}
assert_eq!(doc.pages.bodies.len(), 25);
assert_eq!(doc.pages.bodies, bodies.concat());
v2_gated_line(
test,
started,
"corpus-25",
"windowed",
"pass",
v2_windowed_line(&doc),
);
}
#[test]
fn real_v2_windowed_decode_matches_the_windowed_oracle() {
let test = "real_v2_windowed_decode_matches_the_windowed_oracle";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let ids = |v: &serde_json::Value| -> Vec<u32> {
v.as_array().map_or_else(Vec::new, |a| {
a.iter()
.map(|id| u32::try_from(id.as_u64().expect("an id")).expect("a u32 id"))
.collect()
})
};
let mut failures = Vec::new();
for run in window::oracle_fixtures() {
let tag = run["tag"].as_str().expect("a tag").to_string();
let width = usize::try_from(run["window"].as_u64().expect("window")).expect("W");
let windows = run["windows"].as_array().expect("windows");
if run["plan"].is_null() {
let [only] = windows.as_slice() else {
panic!("{tag}: an unsplit run is one window");
};
let names: Vec<&str> = only["pages"]
.as_array()
.expect("pages")
.iter()
.map(|p| p.as_str().expect("a page"))
.collect();
let Some(paths) = v2_corpus_pages(test, started, &tag, &names) else {
continue;
};
let opts = window::WindowedOptions {
window: width,
precision: Some(DocumentPrecision::CachedF32),
..window::WindowedOptions::default()
};
let mut pages = paths.iter().map(|path| {
let img = image::open(path).map_err(|e| {
FocrError::Other(anyhow::anyhow!("{}: {e}", path.display()))
})?;
model.document_page(img)
});
match model
.decode_document_windowed(&mut pages, &opts, None)
.expect("planning runs")
{
window::Windowing::Fits(pres) => {
assert_eq!(pres.len(), names.len(), "{tag}: every page comes back");
}
window::Windowing::Windowed(_) => {
panic!("{tag}: the oracle split no windows")
}
}
continue;
}
let mut names: Vec<String> = Vec::new();
for w in windows {
let lo_hi = w["lo_hi_emit"].as_array().expect("lo_hi_emit");
let own = lo_hi[1].as_u64().expect("hi") - lo_hi[0].as_u64().expect("lo");
let pages = w["pages"].as_array().expect("pages");
names.extend(
pages[..usize::try_from(own).expect("own")]
.iter()
.map(|p| p.as_str().expect("a page").to_string()),
);
}
let names: Vec<&str> = names.iter().map(String::as_str).collect();
let Some(paths) = v2_corpus_pages(test, started, &tag, &names) else {
continue;
};
let mut opts = window::WindowedOptions {
window: width,
precision: Some(DocumentPrecision::CachedF32),
..window::WindowedOptions::default()
};
let forced = run["forced_fallbacks"]
.as_object()
.cloned()
.unwrap_or_default();
for (k, how) in &forced {
let k: usize = k.parse().expect("a window index");
match how.as_str().expect("a reason") {
"pool_too_small" => {
opts.force_fallback.push((k, window::FallbackReason::Alloc));
}
"no_text" => opts
.force_fallback
.push((k, window::FallbackReason::NoOutput)),
"no_entry" => {
opts.force_fallback
.push((k, window::FallbackReason::NoRetained));
}
"splice_alloc_failed" => opts.marker_only_splice.push(k),
other => panic!("{tag}: no hook for the oracle's {other}"),
}
}
let mut bodies = Vec::new();
let doc = v2_windowed(&model, &paths, &opts, &mut bodies);
assert_eq!(doc.windows.len(), windows.len(), "{tag}: windows");
let mut first_mismatch = Vec::new();
for ((w, ours), oracle) in doc.windows.iter().zip(&doc.window_ids).zip(windows) {
let want_forced = ids(&oracle["forced_ids"]);
let want = ids(&oracle["generated_ids"]);
assert_eq!(
w.reused,
oracle["reused"] == true,
"{tag} w{}: reused",
w.index
);
let (got_forced, got) = ours.split_at(w.forced_tokens);
let at = got
.iter()
.zip(&want)
.position(|(a, b)| a != b)
.or_else(|| (got.len() != want.len()).then_some(got.len().min(want.len())));
let forced_equal = got_forced == want_forced.as_slice();
if at.is_some() || !forced_equal {
failures.push(format!(
"{tag} w{}: forced equal {forced_equal}, first generated mismatch {at:?} \
of {} (oracle {})",
w.index,
got.len(),
want.len()
));
}
first_mismatch.push(serde_json::json!({
"window": w.index, "forced_equal": forced_equal, "first_mismatch": at,
"tokens": got.len(), "oracle_tokens": want.len(),
}));
}
let want_bodies: Vec<&str> = run["document"]["bodies"]
.as_array()
.expect("bodies")
.iter()
.map(|b| b.as_str().expect("a body"))
.collect();
let bodies_equal =
doc.pages.bodies == want_bodies && bodies.concat() == doc.pages.bodies;
if !bodies_equal {
failures.push(format!(
"{tag}: the stitched bodies differ from the oracle's"
));
}
let mut line = v2_windowed_line(&doc);
line["compare"] = first_mismatch.into();
line["bodies_equal"] = bodies_equal.into();
line["path"] = "f32-cached".into();
let exact = bodies_equal
&& line["compare"].as_array().is_some_and(|c| {
c.iter()
.all(|w| w["forced_equal"] == true && w["first_mismatch"].is_null())
});
let result = if exact { "pass" } else { "fail" };
v2_gated_line(test, started, &tag, "windowed_parity", result, line);
}
assert!(failures.is_empty(), "windowed parity: {failures:#?}");
}
const V2_L4_STATELESS_MAX_IDS: usize = 256;
fn v2_armed_levers() -> serde_json::Value {
[
"FOCR_FUSE_SWIGLU",
"FOCR_INT8_AUTOVEC",
"FOCR_LMHEAD_SHARD",
"FOCR_LMHEAD_SHARD_TILES",
"FOCR_MOE_SCORE_ORDER",
"FOCR_SOFTMAX_COPY",
]
.into_iter()
.filter_map(|name| {
std::env::var(name)
.ok()
.map(|value| (name.to_string(), serde_json::Value::String(value)))
})
.collect::<serde_json::Map<_, _>>()
.into()
}
fn v2_ids_sha256(ids: &[u32]) -> String {
use sha2::Digest as _;
let json = serde_json::to_string(ids).expect("ids serialize");
sha2::Sha256::digest(json.as_bytes())
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
fn v2_replay(
model: &OcrModel,
wc: &decoder::DecoderWeightCache,
(embeds, prompt): (&Mat, &[u32]),
ids: &[u32],
params: &DecodeParams,
) -> (Mat, Vec<Vec<u32>>) {
use crate::tokenizer::special::PAGE;
let pages = prompt.iter().filter(|&&id| id == PAGE).count();
let mut guard = document_guard(pages, PAGE, params);
let table = model.embed_table().expect("embed table");
let coords = srswa::PageCoords::build(prompt).expect("page coords");
let (hidden, mut state) =
decoder::prefill_with_cache_srswa_traced(wc, embeds, &coords, params.entmax_domain)
.expect("traced prefill");
let mut hidden = OcrModel::last_hidden_row(&hidden).expect("last row");
let mut routes: Vec<Vec<u32>> = state
.trace
.as_ref()
.expect("traced")
.prefill_last
.iter()
.map(|r| r.as_ref().map_or_else(Vec::new, |r| r.blocks.clone()))
.collect();
for &id in ids {
guard.observe(id);
let forced = matches!(guard.verdict(), sampler::GuardVerdict::Force(_));
let row = Mat::from_vec(1, embeds.cols, table.row_f32(id as usize));
let h = decoder::decode_step_with_cache_srswa(wc, &mut state, id, &row, forced)
.expect("replayed step");
hidden = OcrModel::last_hidden_row(&h).expect("last row");
routes = state
.trace
.as_ref()
.expect("traced")
.decode
.iter()
.map(|r| r.as_ref().map_or_else(Vec::new, |r| r.route.blocks.clone()))
.collect();
}
let logits = decoder::lm_head_cached(wc, &hidden).expect("lm_head");
(logits, routes)
}
fn v2_l4(test: &str, fixtures: &[&str]) {
use crate::tokenizer::special::PAGE;
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let meta: serde_json::Value = serde_json::from_slice(
&std::fs::read(
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/uocr2_oracle/ORACLE.json"),
)
.expect("ORACLE.json"),
)
.expect("ORACLE.json parses");
let tok = model.tokenizer().expect("tokenizer");
let levers = v2_armed_levers();
for &fixture in fixtures {
let entry = &meta["fixtures"][fixture];
assert_eq!(entry["mode"], "base", "{fixture}: the L4 lanes are base");
let pages: Vec<&str> = entry["pages"]
.as_array()
.expect("pages")
.iter()
.map(|p| p["name"].as_str().expect("page name"))
.collect();
let Some(paths) = v2_corpus_pages(test, started, fixture, &pages) else {
continue;
};
let (embeds, prompt) = v2_decoder_prompt(&model, &paths);
assert_eq!(
Some(prompt.len() as u64),
entry["prompt_token_count"].as_u64(),
"{fixture}: prompt rows"
);
let n = pages.len();
let params = model.document_params(n);
let oracle = v2_oracle_ids(fixture);
assert_eq!(
v2_ids_sha256(&oracle),
entry["token_ids_sha256"]
.as_str()
.expect("token_ids_sha256"),
"{fixture}: the committed ids are the oracle's"
);
let oracle_split =
postprocess::split_pages_by_token(tok, &oracle, PAGE, n).expect("oracle split");
let mut decoded: Vec<(DocumentPrecision, Vec<u32>)> = Vec::new();
for precision in [
DocumentPrecision::Stateless,
DocumentPrecision::CachedF32,
DocumentPrecision::Mixed,
] {
let retires: &[bool] = match precision {
DocumentPrecision::Stateless if oracle.len() > V2_L4_STATELESS_MAX_IDS => {
continue;
}
DocumentPrecision::Stateless => &[false],
_ => &[true, false],
};
let mut by_retire = Vec::new();
for &retire in retires {
let pass = DocumentPass {
precision,
retire,
deadline: None,
budget_floor: 0,
};
let t = Instant::now();
let mut ids = Vec::new();
let decode = model
.generate_document_once(&embeds, &prompt, ¶ms, pass, &mut ids, None)
.expect("the pass decodes");
let wall = t.elapsed().as_secs_f64();
let split = postprocess::split_pages_by_token(tok, &decode.ids, PAGE, n)
.expect("the split");
let first_mismatch = decode
.ids
.iter()
.zip(&oracle)
.position(|(a, b)| a != b)
.or_else(|| {
(decode.ids.len() != oracle.len())
.then_some(oracle.len().min(decode.ids.len()))
});
let fed = &decode.ids[..decode.ids.len().saturating_sub(1)];
let disc_u2_7 = fed
.iter()
.filter(|&&id| id == PAGE)
.count()
.saturating_sub(n);
let exact = first_mismatch.is_none();
let result = match (exact, precision) {
(true, _) => "pass",
(false, DocumentPrecision::Mixed) => "xfail",
(false, _) => "fail",
};
v2_gated_line(
test,
started,
fixture,
"parity",
result,
serde_json::json!({
"rung": "L4", "path": precision.name(), "retire": retire,
"levers": levers, "tokens": decode.ids.len(),
"oracle_tokens": oracle.len(),
"first_mismatch_step": first_mismatch,
"ids_sha256": v2_ids_sha256(&decode.ids),
"oracle_sha256": entry["token_ids_sha256"],
"finish_reason": decode.finish_reason.as_str(),
"page_tokens": split.tokens,
"page_finish": decode.stats.page_finish.iter().map(|f| f.as_str()).collect::<Vec<_>>(),
"extra_markers": split.extra_markers,
"disc_u2_7_steps": disc_u2_7,
"disc_u2_2_steps": decode.stats.hf_all_neg_inf_steps,
"leading_text": split.leading_text,
"forced_steps": decode.stats.forced_steps,
"retired_blocks": decode.stats.retired_blocks,
"peak_kv_bytes": decode.stats.peak_kv_bytes,
"retire_fallbacks": decode.stats.retire_fallbacks,
"wall_s": wall,
"ms_per_token": 1e3 * wall / decode.ids.len().max(1) as f64,
}),
);
let path = precision.name();
assert_eq!(decode.finish_reason, FinishReason::Stop, "{fixture} {path}");
assert_eq!(split.bodies.len(), n, "{fixture} {path}: a body per page");
let mut want = vec![PageFinish::Marker; n - 1];
want.push(PageFinish::Eos);
assert_eq!(
decode.stats.page_finish, want,
"{fixture} {path}: EOS only on the last page"
);
assert_eq!(split.extra_markers, 0, "{fixture} {path}: DISC-U2-3");
assert_eq!(disc_u2_7, 0, "{fixture} {path}: DISC-U2-7");
assert_eq!(
decode.stats.hf_all_neg_inf_steps, 0,
"{fixture} {path}: DISC-U2-2"
);
assert_eq!(decode.stats.retire_fallbacks, 0, "{fixture} {path}");
assert!(!split.leading_text, "{fixture} {path}: DISC-U2-9");
for (k, (ours, theirs)) in
split.bodies.iter().zip(&oracle_split.bodies).enumerate()
{
assert_eq!(
ours.trim().is_empty(),
theirs.trim().is_empty(),
"{fixture} {path}: page {} body emptiness",
k + 1
);
}
by_retire.push((decode.ids, first_mismatch));
}
if let [(retired, _), (unretired, _)] = by_retire.as_slice() {
assert_eq!(
retired,
unretired,
"{fixture} {}: retired ≡ unretired",
precision.name()
);
}
let (ids, first_mismatch) = by_retire.swap_remove(0);
if let Some(step) = first_mismatch {
let prefix = &oracle[..step];
let (f32_logits, f32_routes) = v2_replay(
&model,
model.decoder_cache_f32().expect("f32 cache"),
(&embeds, &prompt),
prefix,
¶ms,
);
let (mixed_logits, mixed_routes) = v2_replay(
&model,
model.decoder_cache().expect("mixed cache"),
(&embeds, &prompt),
prefix,
¶ms,
);
let page = prefix.iter().filter(|&&id| id == PAGE).count();
let page_len = prefix.iter().rev().take_while(|&&id| id != PAGE).count();
let diff = decoder::decode_trace_diff(
step,
page,
page_len,
decoder::StepSide {
label: "f32",
logits: &f32_logits.data,
routes: &f32_routes,
},
decoder::StepSide {
label: "int8-ffn",
logits: &mixed_logits.data,
routes: &mixed_routes,
},
);
let want = oracle.get(step).copied();
let mut guard = document_guard(n, PAGE, ¶ms);
for &id in prefix {
guard.observe(id);
}
let pick = |logits: &Mat| {
let s = sampler::decode_step_guarded(logits, prefix, &guard, ¶ms)
.expect("a guarded step");
(!s.is_eos).then_some(s.token_id)
};
let gap = |logits: &Mat| {
let best = logits
.data
.iter()
.copied()
.fold(f32::NEG_INFINITY, f32::max);
best - logits.data[want.unwrap_or(params.eos_token_id) as usize]
};
let routes_differ: Vec<usize> = (0..f32_routes.len())
.filter(|&l| {
f32_routes[l] != mixed_routes.get(l).cloned().unwrap_or_default()
})
.collect();
v2_gated_line(
test,
started,
fixture,
"trace_diff",
"info",
serde_json::json!({
"path": precision.name(), "step": step, "page": page,
"page_len": page_len, "oracle_id": want,
"f32_pick": pick(&f32_logits), "int8_ffn_pick": pick(&mixed_logits),
"f32_oracle_gap": gap(&f32_logits),
"int8_ffn_oracle_gap": gap(&mixed_logits),
"layers_routing_differently": routes_differ,
"diff": diff,
}),
);
assert_eq!(
precision,
DocumentPrecision::Mixed,
"{fixture}: the f32 path departs from the oracle\n{diff}"
);
assert_eq!(
pick(&f32_logits),
want,
"{fixture}: at the int8-FFN departure the f32 path must pick the \
oracle's token\n{diff}"
);
}
decoded.push((precision, ids));
}
v2_gated_line(
test,
started,
fixture,
"assert",
"pass",
serde_json::json!({
"levers": levers,
"paths_exact": decoded.iter().filter(|(_, ids)| *ids == oracle).map(|(p, _)| p.name()).collect::<Vec<_>>(),
}),
);
}
}
fn v2_oracle_ids(fixture: &str) -> Vec<u32> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/uocr2_oracle")
.join(format!("{fixture}.ids.txt"));
std::fs::read_to_string(path)
.expect("oracle ids")
.lines()
.filter(|l| !l.starts_with('#'))
.flat_map(str::split_whitespace)
.map(|t| t.parse().expect("an id"))
.collect()
}
fn v2_corpus_pages(
test: &str,
started: std::time::Instant,
fixture: &str,
pages: &[&str],
) -> Option<Vec<PathBuf>> {
let corpus = std::env::var_os("FOCR_CORPUS_DIR").map_or_else(
|| {
PathBuf::from(std::env::var_os("HOME").expect("HOME"))
.join(".cache/franken_ocr/corpus/pages")
},
PathBuf::from,
);
let paths: Vec<PathBuf> = pages.iter().map(|p| corpus.join(p)).collect();
if let Some(missing) = paths.iter().find(|p| !p.is_file()) {
v2_gated_line(
test,
started,
fixture,
"skip",
"skip_no_model",
serde_json::json!({"reason": format!("{} absent", missing.display())}),
);
return None;
}
Some(paths)
}
fn v2_decoder_prompt(model: &OcrModel, pages: &[PathBuf]) -> (Mat, Vec<u32>) {
let pres: Vec<Preprocessed> = pages
.iter()
.map(|page| {
let img = image::open(page).expect("page decodes");
model.document_page(img).expect("preprocess")
})
.collect();
let (embeds, ids) = model.document_inputs(&pres).expect("document inputs");
let pages_in_prompt = ids
.iter()
.filter(|&&id| id == crate::tokenizer::special::PAGE)
.count();
assert_eq!(pages_in_prompt, pages.len());
(embeds, ids)
}
fn argmax_id(logits: &[f32]) -> u32 {
let mut best = 0;
for (i, &x) in logits.iter().enumerate() {
if x > logits[best] {
best = i;
}
}
best as u32
}
fn fixture_page(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/got")
.join(name)
}
fn push_embed(embeds: Mat, table: &EmbedTable<'_>, id: u32) -> Mat {
let (rows, cols) = (embeds.rows + 1, embeds.cols);
let mut data = embeds.data;
data.extend(table.row_f32(id as usize));
Mat::from_vec(rows, cols, data)
}
#[test]
fn real_v2_decoder_runs_cached_and_stateless_with_finite_logits() {
const STEPS: usize = 32;
let test = "real_v2_decoder_runs_cached_and_stateless_with_finite_logits";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let line = |case: &str, event: &str, result: &str, extra: serde_json::Value| {
v2_gated_line(test, started, case, event, result, extra);
};
let (embeds, ids) = v2_decoder_prompt(&model, &[fixture_page("sample_text.png")]);
let table = model.embed_table().expect("embed table");
let wc = model.decoder_cache().expect("the v2 weight cache builds");
assert_eq!(wc.router().expect("v2 caches its router").len(), 12);
let finite = |logits: &Mat| logits.data.iter().all(|x| x.is_finite());
let coords = srswa::PageCoords::build(&ids).expect("v2 page coordinates");
let (hidden, mut state) =
decoder::prefill_with_cache_srswa(wc, &embeds, &coords, srswa::EntmaxDomain::TopM)
.expect("cached prefill");
let mut logits =
decoder::lm_head_cached(wc, &OcrModel::last_hidden_row(&hidden).expect("last row"))
.expect("lm_head");
let mut cached = Vec::new();
for step in 0..=STEPS {
assert!(finite(&logits), "cached logits at step {step}");
let id = argmax_id(&logits.data);
cached.push(id);
if step == STEPS {
break;
}
let row = Mat::from_vec(1, table.cols(), table.row_f32(id as usize));
let h = decoder::decode_step_with_cache_srswa(wc, &mut state, id, &row, false)
.expect("cached decode step");
logits = decoder::lm_head_cached(wc, &h).expect("lm_head");
}
line(
"cached",
"assert",
"pass",
serde_json::json!({
"assertion": "prefill + 32 decode steps give finite logits",
"pass": true, "prompt_rows": ids.len(), "tokens": cached,
"elapsed_s": started.elapsed().as_secs_f64(),
}),
);
let (mut grown, mut grown_ids) = (embeds, ids.clone());
let mut stateless = Vec::new();
for step in 0..=STEPS {
let coords = srswa::PageCoords::build(&grown_ids).expect("coordinates");
let h =
decoder::forward_srswa(&model.weights, &grown, &coords, srswa::EntmaxDomain::TopM)
.expect("stateless forward");
let logits = decoder::lm_head(
&model.weights,
&OcrModel::last_hidden_row(&h).expect("last row"),
)
.expect("lm_head");
assert!(finite(&logits), "stateless logits at step {step}");
let id = argmax_id(&logits.data);
stateless.push(id);
grown_ids.push(id);
grown = push_embed(grown, &table, id);
}
let agree = cached
.iter()
.zip(&stateless)
.take_while(|(a, b)| a == b)
.count();
line(
"stateless",
"assert",
"pass",
serde_json::json!({
"assertion": "32 stateless decode steps give finite logits",
"pass": true, "tokens": stateless,
"info_agreeing_prefix": agree, "info_of": STEPS + 1,
"elapsed_s": started.elapsed().as_secs_f64(),
}),
);
}
#[test]
fn real_v2_cached_decode_equals_a_reprefill() {
const STEPS: usize = 12;
let test = "real_v2_cached_decode_equals_a_reprefill";
let started = std::time::Instant::now();
let Some(model) = v2_gated_model(test, started) else {
return;
};
let pages = [
fixture_page("sample_text.png"),
fixture_page("format_corpus/table.png"),
];
let (embeds, ids) = v2_decoder_prompt(&model, &pages);
let table = model.embed_table().expect("embed table");
let wc = model.decoder_cache().expect("the v2 weight cache builds");
let last_logits = |embeds: &Mat, ids: &[u32]| {
let coords = srswa::PageCoords::build(ids).expect("coordinates");
let (h, state) =
decoder::prefill_with_cache_srswa(wc, embeds, &coords, srswa::EntmaxDomain::TopM)
.expect("cached prefill");
let logits =
decoder::lm_head_cached(wc, &OcrModel::last_hidden_row(&h).expect("last row"))
.expect("lm_head");
(logits, state)
};
let (mut logits, mut state) = last_logits(&embeds, &ids);
let (mut grown, mut grown_ids) = (embeds, ids.clone());
let mut gaps = Vec::new();
for step in 1..=STEPS {
let id = argmax_id(&logits.data);
assert_ne!(
id,
crate::tokenizer::special::IMAGE,
"a re-prefill would read it as an image"
);
let row = Mat::from_vec(1, table.cols(), table.row_f32(id as usize));
let h = decoder::decode_step_with_cache_srswa(wc, &mut state, id, &row, false)
.expect("cached decode step");
logits = decoder::lm_head_cached(wc, &h).expect("lm_head");
grown_ids.push(id);
grown = push_embed(grown, &table, id);
let (fresh, _) = last_logits(&grown, &grown_ids);
let gap = logits
.data
.iter()
.zip(&fresh.data)
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max);
let scale = fresh.data.iter().fold(0.0f32, |m, x| m.max(x.abs()));
assert_eq!(
argmax_id(&logits.data),
argmax_id(&fresh.data),
"step {step}: incremental and re-prefill pick different tokens"
);
assert!(
gap <= 0.10 * scale,
"step {step}: logit gap {gap} exceeds 10% of the largest logit {scale}"
);
gaps.push((gap, scale));
}
v2_gated_line(
test,
started,
"two_pages",
"assert",
"pass",
serde_json::json!({
"assertion": "incremental decode and a re-prefill pick the same tokens, logits within 10%",
"pass": true, "prompt_rows": ids.len(),
"max_abs_gap_per_step": gaps.iter().map(|g| g.0).collect::<Vec<_>>(),
"max_abs_logit_per_step": gaps.iter().map(|g| g.1).collect::<Vec<_>>(),
"tokens": &grown_ids[ids.len()..],
"elapsed_s": started.elapsed().as_secs_f64(),
}),
);
}
#[test]
fn native_model_available_rejects_missing_and_garbage() {
let missing = Path::new("/definitely/not/a/real/model/path.focrq");
assert!(!native_model_available(missing));
let mut tmp = std::env::temp_dir();
tmp.push(format!(
"franken_ocr_available_garbage_{}.bin",
std::process::id()
));
std::fs::write(&tmp, b"not a model").expect("write garbage");
assert!(!native_model_available(&tmp));
}
#[test]
fn default_decode_params_are_single_image_greedy() {
let p = DecodeParams::single_image();
assert!(p.is_greedy());
assert_eq!(p.eos_token_id, sampler::DEFAULT_EOS_TOKEN_ID);
assert_eq!(
p.no_repeat_ngram_size,
sampler::DEFAULT_NO_REPEAT_NGRAM_SIZE
);
assert_eq!(p.ngram_window, sampler::NGRAM_WINDOW_SINGLE);
assert!(p.max_length > 0, "max_length must bound the decode loop");
}
#[test]
fn unlimited_vision_cache_kill_switch_parses_for_both_vision_schedules() {
for value in ["0", "off", "FALSE", " No "] {
assert!(
!unlimited_vision_cache_enabled_for(Some(value)),
"{value:?} must disable retained statics in sequential and batched vision"
);
}
for value in [None, Some("1"), Some("true"), Some("yes")] {
assert!(unlimited_vision_cache_enabled_for(value));
}
}
#[test]
fn batched_vision_cache_off_keeps_statics_batch_local() {
let cache = FallibleOnce::<usize>::new();
let initializations = std::cell::Cell::new(0usize);
let initialize = || {
initializations.set(initializations.get() + 1);
Ok::<usize, ()>(17)
};
let first = retained_or_owned(false, &cache, initialize).expect("batch-local init");
assert!(matches!(first, RetainedOrOwned::Owned(17)));
let second = retained_or_owned(false, &cache, initialize).expect("next batch-local init");
assert!(matches!(second, RetainedOrOwned::Owned(17)));
assert_eq!(initializations.get(), 2, "cache-off rebuilds per batch");
assert!(cache.value.get().is_none(), "cache-off retains no statics");
let retained = retained_or_owned(true, &cache, initialize).expect("retained init");
assert_eq!(*retained.as_ref(), 17);
let reused = retained_or_owned(true, &cache, initialize).expect("retained reuse");
assert_eq!(*reused.as_ref(), 17);
assert_eq!(
initializations.get(),
3,
"cache-on initializes exactly once"
);
}
#[test]
fn batched_vision_matches_per_page_tower_on_real_model() {
let (Ok(model_path), Ok(images)) = (
std::env::var("FOCR_MODEL_PATH"),
std::env::var("FOCR_PARITY_IMAGES"),
) else {
eprintln!("skip-with-SUCCESS: FOCR_MODEL_PATH / FOCR_PARITY_IMAGES unset");
return;
};
let model = OcrModel::load(Path::new(&model_path)).expect("model loads");
let paths: Vec<&str> = images.split(',').filter(|s| !s.is_empty()).collect();
assert!(
!paths.is_empty(),
"FOCR_PARITY_IMAGES must name at least one page"
);
let pres: Vec<Preprocessed> = paths
.iter()
.map(|p| {
preprocess::preprocess_image(
Path::new(p),
preprocess::PreprocessMode::Base { base_size: 1024 },
preprocess::PreprocessProfile::V1,
)
.expect("page preprocesses")
})
.collect();
let prefs: Vec<&Preprocessed> = pres.iter().collect();
let batched = model
.vision_tower_batched_pages(&prefs)
.expect("batched tower runs");
for (k, pre) in pres.iter().enumerate() {
let per_page = model.vision_tower(pre).expect("per-page tower runs");
assert_eq!(batched[k].len(), per_page.len(), "page {k} view count");
for (v, (b, s)) in batched[k].iter().zip(&per_page).enumerate() {
assert_eq!(b.shape(), s.shape(), "page {k} view {v} shape");
let bits_equal = b
.data
.iter()
.zip(&s.data)
.all(|(x, y)| x.to_bits() == y.to_bits());
assert!(
bits_equal,
"page {k} view {v}: batched vision != per-page tower (bit drift)"
);
}
}
}
#[test]
fn preprocess_overrides_resolve_modes() {
use preprocess::PreprocessMode as M;
assert_eq!(
resolve_preprocess_mode(PreprocessOverrides::default()),
M::Base { base_size: 1024 }
);
assert_eq!(
resolve_preprocess_mode(PreprocessOverrides {
base_size: Some(512),
..Default::default()
}),
M::Base { base_size: 512 }
);
assert_eq!(
resolve_preprocess_mode(PreprocessOverrides {
gundam: Some(true),
..Default::default()
}),
M::Gundam {
base_size: 1024,
tile_size: 640
}
);
assert_eq!(
resolve_preprocess_mode(PreprocessOverrides {
gundam: Some(false),
base_size: Some(768),
image_size: Some(512),
}),
M::Base { base_size: 768 }
);
}
#[test]
fn only_unlimited_ocr_2_takes_the_v2_preprocess_profile() {
for &arch in model_arch::registry() {
let want = if arch.id() == model_arch::UNLIMITED_OCR_V2_ID {
preprocess::PreprocessProfile::V2
} else {
preprocess::PreprocessProfile::V1
};
assert_eq!(preprocess_profile(arch), want, "{}", arch.id());
}
}
#[test]
fn decode_overrides_apply_some_fields_and_keep_none() {
let base = DecodeParams::single_image();
let mut p = DecodeParams::single_image();
apply_decode_overrides(&mut p, DecodeOverrides::default());
assert_eq!(p.max_length, base.max_length);
assert_eq!(p.temperature.to_bits(), base.temperature.to_bits());
assert_eq!(p.no_repeat_ngram_size, base.no_repeat_ngram_size);
assert_eq!(p.ngram_window, base.ngram_window);
let mut p = DecodeParams::single_image();
apply_decode_overrides(
&mut p,
DecodeOverrides {
max_length: Some(700),
temperature: None,
no_repeat_ngram: Some(20),
ngram_window: None,
max_page_tokens: None,
},
);
assert_eq!(p.max_length, 700);
assert_eq!(p.temperature.to_bits(), base.temperature.to_bits());
assert_eq!(p.no_repeat_ngram_size, 20);
assert_eq!(p.ngram_window, base.ngram_window);
}
#[test]
fn explicit_overrides_reach_the_v2_decode_params() {
let v2 = model_arch::arch_by_id(model_arch::UNLIMITED_OCR_V2_ID).expect("v2 registered");
let defaults = decode_params_for(v2, DecodeOverrides::default());
assert_eq!(
defaults.max_page_tokens,
sampler::UOCR2_DEFAULT_MAX_PAGE_TOKENS
);
let p = decode_params_for(
v2,
DecodeOverrides {
no_repeat_ngram: Some(0),
max_page_tokens: Some(0),
..DecodeOverrides::default()
},
);
assert_eq!((p.no_repeat_ngram_size, p.max_page_tokens), (0, 0));
let p = decode_params_for(
v2,
DecodeOverrides {
max_page_tokens: Some(512),
..DecodeOverrides::default()
},
);
assert_eq!(p.max_page_tokens, 512);
assert_eq!(p.no_repeat_ngram_size, defaults.no_repeat_ngram_size);
let v1 = model_arch::arch_by_id(model_arch::UNLIMITED_OCR_V1_ID).expect("v1 registered");
let p = decode_params_for(
v1,
DecodeOverrides {
max_page_tokens: Some(512),
..DecodeOverrides::default()
},
);
assert_eq!(
p.max_page_tokens,
DecodeParams::single_image().max_page_tokens,
"a model without pages ignores the page cap"
);
}
#[test]
fn driver_uses_base_1024_global_grid() {
let pre = Preprocessed::default();
assert_eq!(OcrModel::global_grid_h(&pre), 16);
assert_eq!(OcrModel::global_grid_w(&pre), 16);
}
#[test]
fn structural_params_read_through_weights_accessor() {
let w = Weights::default();
assert!(matches!(
OcrModel::image_newline(&w),
Err(FocrError::FormatMismatch(_))
));
assert!(matches!(
OcrModel::view_seperator(&w),
Err(FocrError::FormatMismatch(_))
));
}
#[test]
fn image_dims_come_from_preprocessed_original_size() {
let pre = Preprocessed {
original_size: (123, 45),
..Preprocessed::default()
};
assert_eq!(OcrModel::image_dims(&pre), (123, 45));
}
#[test]
fn views_forward_local_tiles_then_global_thumbnail() {
let global = Mat::from_vec(3, 4, (0..12).map(|v| v as f32).collect());
let tile_a = Mat::from_vec(3, 1, vec![1.0, 2.0, 3.0]);
let tile_b = Mat::from_vec(3, 1, vec![4.0, 5.0, 6.0]);
let pre = Preprocessed {
mode: preprocess::PreprocessMode::Gundam {
base_size: 128,
tile_size: 64,
},
global: preprocess::ViewTensor {
pixels: global.clone(),
height: 2,
width: 2,
},
tiles: vec![
preprocess::ViewTensor {
pixels: tile_a.clone(),
height: 1,
width: 1,
},
preprocess::ViewTensor {
pixels: tile_b.clone(),
height: 1,
width: 1,
},
],
crop_grid: preprocess::CropGrid {
width_crop_num: 2,
height_crop_num: 1,
},
original_size: (640, 320),
};
assert_eq!(OcrModel::views(&pre), vec![tile_a, tile_b, global]);
assert_eq!(OcrModel::global_grid_h(&pre), 2);
assert_eq!(OcrModel::global_grid_w(&pre), 2);
}
#[test]
fn last_hidden_row_rejects_empty_decoder_output_without_panic() {
let hidden = Mat::zeros(0, 4);
let err = OcrModel::last_hidden_row(&hidden).expect_err("expected empty hidden error");
assert!(matches!(err, FocrError::Other(_)));
assert!(err.to_string().contains("zero hidden rows"));
}
#[test]
fn last_hidden_row_rejects_malformed_decoder_output_without_panic() {
let hidden = Mat {
rows: 2,
cols: 3,
data: vec![1.0, 2.0, 3.0, 4.0, 5.0],
};
let err = OcrModel::last_hidden_row(&hidden).expect_err("expected malformed hidden error");
assert!(matches!(err, FocrError::Other(_)));
assert!(err.to_string().contains("data len 5 != rows*cols 6"));
}
#[test]
fn last_hidden_row_extracts_final_row() {
let hidden = Mat::from_vec(3, 2, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let last = OcrModel::last_hidden_row(&hidden).expect("last row");
assert_eq!(last.shape(), (1, 2));
assert_eq!(last.row(0), &[5.0, 6.0]);
}
#[test]
fn crop_figures_crops_only_image_spans_from_source() {
let source = image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(
120,
80,
image::Rgb([7, 8, 9]),
));
let decoded = concat!(
"<|ref|>title<|/ref|><|det|>[[0,0,500,500]]<|/det|>",
"<|ref|>image<|/ref|><|det|>[[0,0,999,999]]<|/det|>",
);
let figs = OcrModel::crop_figures(decoded, &source, 120, 80, "");
assert_eq!(figs.len(), 1, "only the image span is cropped");
assert_eq!(figs[0].index, 0);
assert_eq!(figs[0].label, "image");
assert_eq!(figs[0].markdown_ref, "");
assert_eq!(figs[0].bbox, [0, 0, 120, 80]);
assert_eq!(figs[0].image.width(), 120);
assert_eq!(figs[0].image.height(), 80);
}
#[test]
fn crop_figures_crops_the_right_subregion() {
let mut buf = image::RgbImage::new(100, 40);
for y in 0..40 {
for x in 0..100 {
let c = if x < 50 {
image::Rgb([255, 0, 0])
} else {
image::Rgb([0, 0, 255])
};
buf.put_pixel(x, y, c);
}
}
let source = image::DynamicImage::ImageRgb8(buf);
let decoded = "<|ref|>image<|/ref|><|det|>[[500,0,999,999]]<|/det|>";
let figs = OcrModel::crop_figures(decoded, &source, 100, 40, "");
assert_eq!(figs.len(), 1);
assert_eq!(figs[0].bbox, [50, 0, 100, 40]);
let crop = figs[0].image.to_rgb8();
assert_eq!(crop.dimensions(), (50, 40));
assert!(
crop.pixels().all(|p| p.0 == [0, 0, 255]),
"the right-half crop must be all blue"
);
}
#[test]
fn crop_figures_skips_degenerate_box() {
let source = image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel(
50,
50,
image::Rgb([0, 0, 0]),
));
let decoded = "<|ref|>image<|/ref|><|det|>[[10,0,10,999]]<|/det|>";
assert!(OcrModel::crop_figures(decoded, &source, 50, 50, "").is_empty());
}
#[test]
fn forward_dispatch_guard_passes_unlimited_ocr_rejects_a_planned_arch() {
use crate::native_engine::model_arch::{
self, DecodeContract, Decoder, ModelArch, Task, TokenizerKind, VisionEncoder,
};
OcrModel::ensure_arch_implemented(model_arch::default_arch())
.expect("unlimited-ocr is implemented");
struct PlannedArch;
impl ModelArch for PlannedArch {
fn id(&self) -> &'static str {
"got-ocr2"
}
fn display_name(&self) -> &'static str {
"GOT-OCR2.0"
}
fn license_notice(&self) -> &'static str {
"Apache-2.0"
}
fn default_artifact_basename(&self) -> &'static str {
"got-ocr2.focrq"
}
fn vision_encoder(&self) -> VisionEncoder {
VisionEncoder::SamVit
}
fn decoder(&self) -> Decoder {
Decoder::Qwen2Dense
}
fn tokenizer(&self) -> TokenizerKind {
TokenizerKind::Qwen2Bpe
}
fn decode_contract(&self) -> DecodeContract {
DecodeContract {
temperature: 0.0,
eos_token_id: 0,
no_repeat_ngram_size: 0,
ngram_window: 0,
ngram_domain: model_arch::NgramDomain::PromptAndGenerated,
pages: None,
}
}
fn tasks(&self) -> &'static [Task] {
&[Task::Ocr]
}
fn maturity(&self) -> model_arch::Maturity {
model_arch::Maturity::Planned
}
fn required_quant_recipe(&self, _quant: &str) -> Option<&'static str> {
None
}
}
let err = OcrModel::ensure_arch_implemented(&PlannedArch)
.expect_err("a planned arch must be rejected");
assert!(matches!(err, FocrError::NotImplemented(_)), "got {err:?}");
assert_eq!(err.exit_code(), 1);
assert!(
err.to_string().contains("got-ocr2"),
"names the arch: {err}"
);
}
#[test]
fn arch_is_read_from_the_loaded_focrq_model_id_tag() {
use crate::native_engine::model_arch;
let got_notice = model_arch::arch_by_id("got-ocr2")
.expect("got-ocr2 registered")
.license_notice();
let mut builder = crate::quant::focrq::FocrqBuilder::new()
.with_model_id("got-ocr2")
.with_license_notice(got_notice);
builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add minimal tagged tensor");
let blob = builder.build();
let path = std::env::temp_dir().join(format!(
"focr_arch_tag_{}_{}.focrq",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::write(&path, &blob).unwrap();
let model = OcrModel::load(&path).expect("a tagged .focrq loads");
assert_eq!(
model.arch().id(),
"got-ocr2",
"arch() reads the model_id tag"
);
OcrModel::ensure_arch_implemented(model.arch())
.expect("got-ocr2 is implemented, so its forward is admitted");
let _ = std::fs::remove_file(&path);
}
fn unidentified_safetensors() -> Vec<u8> {
let header = br#"{"w":{"dtype":"BF16","shape":[1],"data_offsets":[0,2]}}"#;
let mut bytes = (header.len() as u64).to_le_bytes().to_vec();
bytes.extend_from_slice(header);
bytes.extend_from_slice(&[0, 0]);
bytes
}
const SIDECAR_JSON: &str = r#"{"added_tokens":[{"id":128815,"content":"<image>","special":true}],
"model":{"type":"BPE","vocab":{"a":0,"b":1},"merges":[]}}"#;
struct SidecarArch {
sha256: Option<&'static str>,
tokens: &'static [(u32, &'static str)],
source: Option<&'static str>,
}
impl model_arch::ModelArch for SidecarArch {
fn id(&self) -> &'static str {
"sidecar-test"
}
fn display_name(&self) -> &'static str {
"sidecar test"
}
fn license_notice(&self) -> &'static str {
"test"
}
fn default_artifact_basename(&self) -> &'static str {
"sidecar-test.focrq"
}
fn vision_encoder(&self) -> model_arch::VisionEncoder {
model_arch::VisionEncoder::SamClip
}
fn decoder(&self) -> model_arch::Decoder {
model_arch::Decoder::DeepSeekV2MoeRswa
}
fn tokenizer(&self) -> model_arch::TokenizerKind {
model_arch::TokenizerKind::DeepSeekBpe
}
fn decode_contract(&self) -> model_arch::DecodeContract {
model_arch::default_arch().decode_contract()
}
fn tasks(&self) -> &'static [model_arch::Task] {
&[model_arch::Task::Ocr]
}
fn maturity(&self) -> model_arch::Maturity {
model_arch::Maturity::Planned
}
fn required_quant_recipe(&self, _quant: &str) -> Option<&'static str> {
None
}
fn tokenizer_sha256(&self) -> Option<&'static str> {
self.sha256
}
fn required_token_ids(&self) -> &'static [(u32, &'static str)] {
self.tokens
}
fn tokenizer_source(&self) -> Option<&'static str> {
self.source
}
}
fn sha256_hex(bytes: &[u8]) -> &'static str {
use sha2::Digest as _;
Box::leak(format!("{:x}", sha2::Sha256::digest(bytes)).into_boxed_str())
}
#[test]
fn absent_model_id_opens_as_v1_under_another_default() {
let got = model_arch::arch_by_id("got-ocr2").expect("got-ocr2 registered");
let _flip = model_arch::test_default::flip(got);
assert_eq!(
compatible_arch("").expect("absent id resolves").id(),
model_arch::UNLIMITED_OCR_V1_ID
);
let mut builder = crate::quant::focrq::FocrqBuilder::new();
builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add tensor");
let weights = Weights::from_bytes(builder.build()).expect("v1 container parses");
assert_eq!(weights.model_id(), model_arch::UNLIMITED_OCR_V1_ID);
}
#[test]
fn an_unidentified_raw_shard_is_refused_on_both_open_paths() {
let weights = Weights::from_bytes(unidentified_safetensors()).expect("raw shard parses");
assert_eq!(weights.model_id(), "");
let message = OcrModel::from_weights(weights, SidecarBundle::default())
.err()
.expect("byte path refuses it")
.to_string();
assert!(
message.contains("matches no registered model's tensor census"),
"{message}"
);
let path = temp_model_dir("unidentified_raw").join("model.safetensors");
std::fs::write(&path, unidentified_safetensors()).expect("write raw shard");
let message = load_weights_from_resolved_model(&path)
.expect_err("file path refuses it")
.to_string();
assert!(
message.contains("matches no registered model's tensor census"),
"{message}"
);
}
#[test]
fn checked_tokenizer_refuses_a_foreign_sidecar_or_a_missing_token() {
let bytes = SIDECAR_JSON.as_bytes();
let pinned = SidecarArch {
sha256: Some(sha256_hex(bytes)),
tokens: &[(128815, "<image>")],
source: None,
};
let tokenizer = checked_tokenizer(&pinned, bytes, "t.json").expect("the pinned sidecar");
assert_eq!(tokenizer.image_id(), 128815);
let other = SIDECAR_JSON.replace("\"b\":1", "\"b\":2");
let message = checked_tokenizer(&pinned, other.as_bytes(), "t.json")
.expect_err("a different file")
.to_string();
assert!(
message.contains(sha256_hex(other.as_bytes())) && message.contains(sha256_hex(bytes)),
"names both hashes: {message}"
);
assert!(message.contains("focr pull sidecar-test"), "{message}");
let needs_page = SidecarArch {
sha256: None,
tokens: &[(128815, "<image>"), (128827, "<PAGE>")],
source: None,
};
let message = checked_tokenizer(&needs_page, bytes, "t.json")
.expect_err("no <PAGE> id")
.to_string();
assert!(
message.contains("maps token id 128827 to None") && message.contains("\"<PAGE>\""),
"{message}"
);
let unpinned = SidecarArch {
sha256: None,
tokens: &[],
source: None,
};
assert!(!pins_tokenizer(&unpinned));
checked_tokenizer(&unpinned, other.as_bytes(), "t.json").expect("nothing pinned");
let unpublished = SidecarArch {
sha256: Some(sha256_hex(bytes)),
tokens: &[],
source: Some("https://huggingface.co/example/model/blob/0123abc/tokenizer.json"),
};
let message = checked_tokenizer(&unpublished, other.as_bytes(), "t.json")
.expect_err("a different file")
.to_string();
assert!(
message.contains("fetch the pinned file from https://huggingface.co/example/model/"),
"{message}"
);
assert!(!message.contains("focr pull"), "{message}");
let v2 = model_arch::arch_by_id(model_arch::UNLIMITED_OCR_V2_ID).expect("v2 registered");
let message = checked_tokenizer(v2, bytes, "t.json")
.expect_err("not the v2 tokenizer")
.to_string();
assert!(message.contains("`focr pull unlimited-ocr-2`"), "{message}");
assert!(!message.contains("huggingface.co"), "{message}");
}
#[test]
fn the_doctor_judges_a_sidecar_by_the_model_its_artifact_declares() {
for arch in model_arch::registry() {
if let Some(pin) = arch.tokenizer_sha256() {
assert_eq!(tokenizer_owner(pin), Some(arch.id()), "one owner per pin");
}
}
assert_eq!(tokenizer_owner(sha256_hex(SIDECAR_JSON.as_bytes())), None);
let package = |label: &str, model_id: Option<&str>, tokenizer: Option<&[u8]>| {
let dir = temp_model_dir(label);
let mut builder = crate::quant::focrq::FocrqBuilder::new();
if let Some(id) = model_id {
builder = builder.with_model_id(id);
}
builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add tensor");
let artifact = dir.join("model.focrq");
std::fs::write(&artifact, builder.build()).expect("write artifact");
if let Some(bytes) = tokenizer {
std::fs::write(dir.join("tokenizer.json"), bytes).expect("write tokenizer");
}
artifact
};
let foreign = SIDECAR_JSON.as_bytes();
let v2 = model_arch::UNLIMITED_OCR_V2_ID;
let artifact = package("doctor_v2_bare", Some(v2), None);
let missing = sidecar_tokenizer_fault(&artifact).expect("no tokenizer");
assert_eq!(missing.model_id, v2);
assert_eq!(missing.refusal, None);
assert_eq!(missing.owner, None);
assert_eq!(
missing.tokenizer_path,
artifact.with_file_name("tokenizer.json")
);
assert_eq!(missing.recommended_command, "focr pull unlimited-ocr-2");
let artifact = package("doctor_v2_foreign", Some(v2), Some(foreign));
let mismatch = sidecar_tokenizer_fault(&artifact).expect("not v2's tokenizer");
let refusal = mismatch.refusal.expect("refused");
assert!(
refusal.contains(&format!("but {v2} requires"))
&& refusal.contains(sha256_hex(foreign)),
"{refusal}"
);
assert_eq!(mismatch.owner, None, "no model pins this file");
assert_eq!(mismatch.recommended_command, "focr pull unlimited-ocr-2");
let artifact = package("doctor_v1_foreign", None, Some(foreign));
let v1 = sidecar_tokenizer_fault(&artifact).expect("not v1's tokenizer");
assert_eq!(v1.model_id, model_arch::UNLIMITED_OCR_V1_ID);
assert_eq!(v1.recommended_command, "focr pull unlimited-ocr");
assert!(
sidecar_tokenizer_fault(&package("doctor_got_bare", Some("got-ocr2"), None)).is_none()
);
assert!(
sidecar_tokenizer_fault(&package("doctor_unknown", Some("no-such-model"), None))
.is_none()
);
let not_focrq = temp_model_dir("doctor_not_focrq").join("model.focrq");
std::fs::write(¬_focrq, unidentified_safetensors()).expect("write raw shard");
assert!(sidecar_tokenizer_fault(¬_focrq).is_none());
}
#[test]
fn an_installed_model_is_judged_by_what_its_id_resolves_to() {
let models = temp_model_dir("installed_models").join("models");
let v2 = model_arch::UNLIMITED_OCR_V2_ID;
let v2_package = models.join(v2);
std::fs::create_dir_all(&v2_package).expect("create the v2 package");
std::fs::write(
v2_package.join(format!("{v2}.v0.0.0.int8.focrq")),
b"an older release",
)
.expect("write an older release");
let unnamed = installed_model_fault(&models, v2).expect("nothing resolves");
assert_eq!(unnamed.artifact, None);
assert!(
unnamed
.refusal
.contains(&format!("no model artifact named {v2}")),
"{}",
unnamed.refusal
);
let current = versioned_quant_path(&v2_package.join(v2), ModelQuantPreference::Int8);
let mut builder = crate::quant::focrq::FocrqBuilder::new().with_model_id(v2);
builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add tensor");
std::fs::write(¤t, builder.build()).expect("write the current release");
let refused = installed_model_fault(&models, v2).expect("its header is refused");
assert_eq!(refused.artifact.as_deref(), Some(current.as_path()));
assert!(!refused.refusal.is_empty());
let got = models.join("got-ocr2");
std::fs::create_dir_all(&got).expect("create the GOT package");
std::fs::write(
got.join("got-ocr2.int8.focrq"),
compatible_test_model_builder().build(),
)
.expect("write a compatible artifact");
assert!(installed_model_fault(&models, "got-ocr2").is_none());
assert!(
installed_model_fault(&models, "not-a-model").is_none(),
"only a registered model is judged"
);
}
#[test]
fn byte_path_refuses_a_foreign_v1_sidecar_before_the_weights_contract() {
let mut builder = crate::quant::focrq::FocrqBuilder::new();
builder
.add_tensor(
"model.embed_tokens.weight",
crate::quant::focrq::WriteDType::Bf16,
vec![1, 1],
vec![0; 2],
)
.expect("add tensor");
let weights = || Weights::from_bytes(builder.clone().build()).expect("v1 container parses");
let sidecars = SidecarBundle {
tokenizer_json: Some(SIDECAR_JSON.as_bytes().to_vec()),
..SidecarBundle::default()
};
let message = OcrModel::from_weights(weights(), sidecars)
.err()
.expect("foreign sidecar refused")
.to_string();
assert!(
message.contains("in-memory tokenizer.json")
&& message
.contains("a02f8fd5228c90256bb4f6554c34a579d48f909e5beb232dc4afad870b55a8b4")
&& message.contains("focr pull unlimited-ocr"),
"{message}"
);
let message = OcrModel::from_weights(weights(), SidecarBundle::default())
.err()
.expect("census refused")
.to_string();
assert!(!message.contains("tokenizer"), "{message}");
}
#[cfg(unix)]
#[test]
fn file_path_judges_the_v1_sidecar_before_reading_weights_when_armed() {
let Some(model) = std::env::var_os("FOCR_MODEL_PATH").map(PathBuf::from) else {
eprintln!("skip-with-SUCCESS: FOCR_MODEL_PATH unset");
return;
};
let real_sidecar = model.with_file_name("tokenizer.json");
let dir = temp_model_dir("sidecar_file_path");
let link = dir.join("unlimited-ocr.focrq");
if !link.exists() {
std::os::unix::fs::symlink(&model, &link).expect("link the real artifact");
}
let v1 = model_arch::default_arch();
assert_eq!(v1.id(), model_arch::UNLIMITED_OCR_V1_ID);
std::fs::write(dir.join("tokenizer.json"), SIDECAR_JSON).expect("write foreign sidecar");
let mut read_weights = false;
let message = load_weights_from_resolved_model_after_validation(
&link,
|_| Ok(()),
|| {
read_weights = true;
},
)
.expect_err("foreign sidecar refused")
.to_string();
assert!(!read_weights, "refused before any weight byte was read");
assert!(message.contains("focr pull unlimited-ocr"), "{message}");
std::fs::copy(&real_sidecar, dir.join("tokenizer.json")).expect("copy pinned sidecar");
let tokenizer = eager_sidecar_tokenizer(v1, &link)
.expect("pinned sidecar passes the hash and every required id")
.expect("present sidecar is parsed at open");
for &(id, content) in v1.required_token_ids() {
assert_eq!(tokenizer.id_to_token(id), Some(content));
}
let bare = temp_model_dir("sidecar_absent").join("unlimited-ocr.focrq");
assert!(
eager_sidecar_tokenizer(v1, &bare)
.expect("absence is not an open error")
.is_none()
);
}
}