【免费下载链接】d1-Omni-2B
d1-Omni-2B 是 Liquid AI 开源的 2.17B 多模态决策模型,基于 LFM3-Omni-2B 底座构建,能像 OCR + 规则引擎那样从发票文本与图片中直接读出金额、日期和文本 span,但它是"一次前向传播读出校准答案"的抽取式模型:不逐 token 生成、不拼 prompt 模板,毫秒级返回带 found 概率的结构化结果(README.md)。本文带你从零理解它的抽取能力:问什么、怎么答、值从哪来。
1 分钟上手:从发票文本读出 4 个字段 📄
给 d1-Omni-2B 一段发票文本,同时提 4 个命名问题:开票方(text)、应付金额(number)、到期日(date)、税额(number),一次调用全部返回:
invoice = "ACME SA\nInvoice 2026-117\nTotal due: 1 234,50 EUR\nPay before 03/04/2026 (day/month/year)"
out = model.system_one(invoice, {
"payee": {"type": "text", "instructions": "Who issued the invoice?"},
"total": {"type": "number", "instructions": "How much is due?"},
"due": {"type": "date", "instructions": "By when must it be paid?"},
"vat": {"type": "number", "instructions": "How much VAT is charged?"},
})
a = out["answers"]
print(a["payee"]["text"], a["total"]["number"], a["due"]["date"], a["vat"]["number"])
# ACME SA 1234.5 2026-04-03 None
total 返回 1234.5——注意原文写作 1 234,50(欧洲式千分位空格 + 逗号小数),它被严格解析成了标准浮点数;due 被规范为 ISO 8601 的 2026-04-03;发票里没有税额,vat 直接返回 None 而不是编造。
每个抽取答案的字段:
| 字段 | 含义 |
|---|---|
text / number / date | 按问题类型给出:原文文本 / 解析后的数值 / ISO 8601 日期 |
span | 值在状态文本中的字符区间 [start, end),即"原文 span" |
found | 找到的校准概率;低于 0.5 时值返回 None |
unit | number 专属,如 EUR(原文写出的币种) |
path | 状态为 JSON 时,值所在的 RFC6901 字段路径 |
它为什么"快":读出答案,而不是生成答案 ⚡
传统做法是让大模型生成 JSON,再解析、校验、重试。d1-Omni-2B 走的是另一条路(runner.py):
- 状态(文本 / JSON / 图片 / 音频)+ 所有命名问题拼成一个 prompt,一次前向传播;
- 每个问题的答案从它自己的答案槽位的 logits 上读出,不生成任何 token,
usage里output_tokens恒为 0; - 答案结构是定死的 schema——"JSON 写错格式"这件事在机制上不可能发生;
- 所有概率都经过 calibration.json 校准,
found、confidence可以直接当阈值用。
速度上(warm 调用中位数,见 README.md):
| 场景 | NVIDIA H100 | AMD MI325X | Apple M5 Pro |
|---|---|---|---|
| 1 个文本问题 | 5.8 ms | 3.7 ms | 21.1 ms |
| 3 个问题 | 7.7 ms | 5.6 ms | 28.9 ms |
| 384 px 图片 | 12.1 ms | 8.6 ms | 40.2 ms |
| 64 个状态批量 | 1,154 /s | 1,697 /s | 110 /s |
span 指针:值是怎么"指"出来的 🎯
这是抽取能力最核心的机制(pointer.py)。对 text / number / date 问题,模型不是写出值,而是对状态文本的每个 token 位置打一对分数(起点、终点):
- 先读
found概率:低于 0.5 判"没找到",答案直接为None; - 找到后,选出最可能的起止位置对,且跨距受上限约束——
text最长 512 个 token,number和date各 32 个(pointer.py); - 把这对位置映射回原始文本,切出字符级 span,顺手去掉首尾空白,若 span 落在某个 JSON 字符串内部,还会还原其转义字符(json_spans.py)。
所以你拿到的 span 里的值,是原文的逐字切片——"读到的"永远等于"写着的",不会多出一个空格、少一位数字。
金额解析:1 234,50 怎么变成 1234.5 💶
number 答案 = span 原文 + 一个严格解析器(parsing.py),它只把"写着的值"读出来,不做算术、不做单位换算:
- 小数点判断看证据:span 里写出的币种符号(
EUR用逗号、USD用点)、文档里其他数字的写法、声明的 locale(en/fr/de……),证据互相矛盾时返回None,绝不猜; - 千分位与小数并存:
1 234,50、1.234,56都能正确区分"哪个符号是分节、哪个是小数"; - 币种与规模词:
$、€、元、₹等会拆出来记入unit;thousand/million/万/亿/crore等规模词会被换算到数值上; - 括号负数:会计式
(123.45)解析为负数。
日期解析:03/04/2026 到底是哪一天 📅
date 答案统一为 ISO 8601。难点从来不是格式,而是日/月顺序,解析器(parsing.py)同样按证据裁决:
- 文档里声明了格式(如示例中的
day/month/year,或dd/mm/yyyy、jj/mm/aaaa这类模板)→ 直接跟随; - 否则看声明的 locale(美式 → 月在前,欧式 → 日在前);
- 再否则看文档里其他无歧义的日期(比如某处是
25/11/2026,25 只能是日); - 证据冲突时,宁可报歧义,也不硬猜。
它还认 16 种语言的月份名(法语 janvier、德语 Januar、俄语 января……)、星期名,以及中日韩写法 2026年10月20日、2026년 10월 20일;带时间的日期(2026-04-03T14:30+02:00)会原样保留时刻与时区,不补默认值。
给 JSON 状态抽取:答案自带字段路径 🔍
状态(state)不只能是字符串,还可以是任意 JSON 值。此时答案会多一个 path 字段——RFC6901 风格的路径(如 /header/total),精确指出值所在的字段(json_spans.py 负责定位 JSON 里每个字符串值的 span)。对票据流水线的自动化校验和人工抽检来说,span + path 意味着每个答案都能回溯到原始文档的具体位置。
从发票图片抽取:同一套问题,多一个 media 🖼️
发票是扫描件或照片?把图片放进 media,视觉编码器会把它展开成 token 混入序列,与文本走同一条前向路径(media.py)。除了 text / number / date 这类读取问题,图片还能直接做对象定位:
out = model.system_one(None, {
"cats": {"type": "number", "instructions": "How many cats are there?", "range": [0, 10], "step": 1},
"remote": {"type": "box", "instructions": "Where is the remote control on the left?"},
"every_cat": {"type": "boxes", "instructions": "Every cat.", "max": 4},
}, media=[image])
# cats: 2.0 every_cat: 2 只 remote: {'top': 0.15, 'bottom': 0.24, 'left': 0.06, 'right': 0.27}
box:返回单个对象的四条边(占图片的归一化比例),可定位"发票金额栏在哪里"这类区域;boxes:枚举图中所有实例,max限制读取数量;number+range+step:计数与数量估计,带interval(p10–p90)置信区间。
一个提醒:span 指针指向的是文本状态的字符区间。当状态是图片(state=None)时,你得到的是解析好的值(金额、日期)和框坐标,而不是原文切片。
批量与性能调优:一张发票 5.8 ms,一千张发票一次调用 📦
- 批量:
system_one_batch([(state, questions[, media]), ...])一次调用处理多张发票,长度相近的请求自动拼进同一批; set_size:多个问题共享同一次状态读取,默认 16(训练时的分组大小);调更大会更快,但文本任务上会损失一点精度(runner.py);- 图片像素上限:本 checkpoint 记录为 1,638,400 像素(config.json 中
d1_vision_max_pixels),大图会被等比缩小后再编码。
实用建议:生产里怎么用才稳 ✅
- 信任
found,别猜值:found < 0.5即缺失(api.py)。建议在自己的票据样本上校准这个阈值,再决定哪些字段需要人工复核; - 善用
unit:number会自动带出币种(EUR、USD……),跨币种对账不用再解析原文; date直接入仓:ISO 8601 是通用格式,下游系统零转换;- 用
span/path做审计:自动化流水线里可抽检"答案原文与文档是否一致",出问题时秒级定位; - 按场景选模型:纯文本 / 单框任务上,无音频输入的 d1-2B 更强、校准更好;只有需要语音理解时才上 d1-Omni-2B(长语音段落精度会明显下降,拉丁文字之外的语言尤甚)。
关键文件速查
| 模块 | 作用 |
|---|---|
| runner.py | system_one 引擎:拼装 prompt、一次前向、槽位读取 |
| pointer.py | span 指针:位置打分 → 最可能的起止位置 → 原文切片 |
| parsing.py | 数值 / 日期的严格解析:币种、locale、日/月顺序 |
| json_spans.py | JSON 状态中字符串值的 span 与 RFC6901 路径 |
| api.py | 答案组装:found 阈值、unit、path |
| config.json | checkpoint 参数:d1_codes、d1_set_size、d1_vision_max_pixels |
| calibration.json | 概率校准表,让 found / confidence 可当阈值用 |
总结
d1-Omni-2B 的抽取能力 = 单次前向读出校准答案 + span 指针在状态文本里切出原文区间 + 严格解析器把"写着的值"读成标准数值和日期,JSON 状态还能额外给出字段 path。从一段发票文本、一份 JSON 到一张发票照片,它都能读出金额、日期和文本 span,单条文本决策只需毫秒级。对新手来说,最上手的一步就是先按"1 分钟上手"的 4 问题示例跑通,然后盯着 span 和 found 两个字段理解——你会立刻明白:这是一个可回溯、可校准、不会胡说的抽取模型,而不是一台只会生成 JSON 的黑盒。
【免费下载链接】d1-Omni-2B
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




