Mix 模式的 OCR 上下文预算与选择算法
16K 预算如何分配:行格式、九宫格轮询、评分补齐、两级去重,以及统计字段和 artifact 的边界。
Mix 的 OCR 上下文预算是独立于图片和系统提示词的 token 预算。默认 16,384,范围 0–32,768,对应 --mix-ocr-context-tokens;0 表示不注入 OCR。预算只计算注入的 OCR 文本和固定头,不包含图片 token、system prompt 或 -p 的内容。
行格式
_compact_ocr_line 先把 item 的 text 做 \s+ → 空格 归一化。超过 400 字符时保留前 399 个字符再追加省略号。行格式是:
#idx @cx%,cy%,w%,h% kind text
cx/cy/width/height 是相对图片尺寸的整数百分比:cx/cy 限制在 0–100,宽高限制在 1–100;kind 取 item 的 type,缺省为 text。分桶键是 min(2, cx // 34) : min(2, cy // 34),把页面切成 3×3。
当 layout=None 或 layout["img_size"] 缺失时,位置退回绝对像素 @x1,y1,x2,y2,分桶统一为 unknown。上下文头的 coordinate 字段仍写成百分比格式,这是当前实现里的语义不一致:非 Web 的 Mix 传入绝对坐标,但头部的说明没有跟着切换。
两级去重
OCR 阶段先做一次普通去重:key 为 (text.casefold(), bbox 每项除以 4 后取整),同一 key 保留 confidence 更高的项。
上下文阶段再做一次去重:key 为 (text.casefold(), bucket),seen 命中就直接跳过,因此保留的是第一次出现的项,不比较 confidence。前者按 4px 粒度保留位置差异,后者只按九宫格分桶;有 img_size 时,同一段文字出现在不同格子会被保留,没有 img_size 时全部落在 unknown,跨区域重复文字会被合并。
上下文去重的 key 使用归一化后的完整文本,行输出则在 400 字符处截断。两个不同的长文本如果前 399 字符相同、后面不同,key 不同,会各自保留,但注入到 VLM 的行看起来一样。
选择顺序
build_bounded_ocr_context 先为每条记录算分:
- 文本长度
<= 120字符:+2; type == "ui_text":+3;- 命中
\d、[$€£¥%]或英文词date、total、amount、warning、error:+3; - 原始索引在前 24 或后 12:
+2。
try_add 收到一条记录时,先检查它是否已入选;未入选就对 "\n" + line 调一次 tokenizer.encode,加上当前 used_tokens 后仍不超过预算才写入 selected。固定头 "压缩 OCR 上下文:" + JSON 先编码,占用同一份预算。
选择分三段:
- 头 24 条、尾 12 条依次尝试。页面的标题、导航、页脚和结论通常落在这两个窗口。
- 按
bucket建桶,桶键排序后轮询:每轮每个桶尝试当前游标的一条记录,游标无论成功与否都前进;一整轮没有任何新增时停止。长页面不会因为顶部文字多就占满预算,但每个桶在一轮里只会前进一条,后面的条目主要靠第三段处理。 - 剩下的记录按
(-score, 原始索引)排序,依次尝试。预算已满时仍会编码,再被used_tokens + line_tokens > budget拒绝。
最终输出按原始索引排序,保持 OCR 的阅读顺序;injectedTokens 用最终上下文重新编码,可能低于预算。
复杂度
记录构建是 O(n)。主要成本在 tokenizer:选中记录最多编码一次;未选中的普通记录最多两次(桶轮询、评分),头尾记录最多三次,小页面头尾窗口重叠时可到四次。最后还要对完整上下文再编码一次。
tokenizer 是 use_fast=False,大页面下这部分是主要耗时。评分阶段在预算耗尽后没有提前退出,try_add 先编码再判断预算,因此仍会对所有未入选记录做一次无效编码。
统计字段
ocr.context 的字段含义:
originalItems:输入 item 数;includedItems:最终 selected 数量;omittedItems:len(items) - includedItems,包含空文本、上下文去重和预算不足三类;originalChars:归一化和截断前的原始字符数;injectedTokens:最终上下文重新编码后的 token 数;truncated:includedItems < originalItems,空文本和重复项也会让它为真;strategy:spatial-priority-token-budget-v1。
因此 truncated 不能区分“预算截断”“去重丢弃”和“空文本跳过”。400 字符截断也不在这个字段里体现;只有完整 artifact 保留原文。
Artifact
--json 下,Mix 无论大小都会写三份文件:
_mix_<backend>[_web]_ocr.txt:完整 OCR 全文;_mix_<backend>[_web]_ocr_items.json:完整 items 和 layout;_mix_<backend>[_web]_output.json:VLM 结果、内联 items 和ocr.context。
内联 items 超过 200 条或 JSON 超过 50,000 字节时只保留前 80 条,ocr.itemsTruncated 标记为真。普通 OCR 的阈值不同:text 超过 50,000 字符、items JSON 超过 50,000 字节或 items 超过 500 条时,才拆出 artifact,内联 text 取前 20,000 和后 2,000 字符,items 保留前 80 条。文件名没有时间戳,重跑会覆盖同名文件。
已知限制
选择是逐行贪心,不保证全局最优;预算不考虑 -p 的相关性。评分正则只匹配数字、货币符号和英文关键词,中文的“日期”“金额”“警告”如果没有数字或货币符号,不会获得加分。
非 Web 的 Mix 在缺少 img_size 时使用绝对像素坐标,但上下文头声明百分比格式。分桶同时退化为 unknown,跨区域同文去重会比 Web 模式更激进。表格块仍按普通文本处理,没有独立的表格预算;统计字段也不记录单条被丢弃的原因。