1. FireRed-OCR 到底解决什么问题,谁该上手
FireRed-OCR 是小红书 Super Intelligence 团队开源的一个 2B 参数文档结构解析模型,能做什么?一句话概括:把 PDF、扫描件、财报、论文这类复杂版式文档,直接转成结构清晰的 Markdown,表格、公式、多级标题都能还原。它适合谁?适合正在搭 RAG 知识库、做文档数字化、或者被通用 VLM「结构性幻觉」折磨过的开发者——表格行列错位、公式凭空捏造、阅读顺序乱跳,这些坑我基本都踩过。
它和普通 OCR 最大的区别在于「结构」二字。传统 OCR 给你一堆散字,FireRed-OCR 给你一棵文档树。它在 OmniDocBench v1.5 上拿到 92.94% 综合得分,端到端方案里领跑,甚至超过了一些参数量大它几十倍的通用多模态模型。2B 的体量意味着消费级显卡就能跑,这对个人开发者和小团队非常友好。
这篇不聊论文里的训练细节,聚焦一件事:怎么在 30 分钟内,用 TaoToken 统一 Key 把 FireRed-OCR 的推理链路跑通,拿到一份 OmniDocBench 风格的结构化输出。我会给出可复制的config.toml和settings.json骨架,把本地推理和 API 调用两条路都走一遍,最后把常见报错挨个排掉。你跟着敲命令就行。
2. 前置准备:TaoToken 统一 Key 与运行环境
2.1 为什么用 TaoToken 统一 Key
FireRed-OCR 本身是开源权重,你可以纯本地跑。但实际项目里往往不止一个模型——文档解析完可能还要接一个总结模型、一个 embedding 模型。每个模型一套 Key、一套计费、一套限流,管理起来很烦。TaoToken 的思路是用一个 Key 打通多家模型的 API 通道,接入文档在 https://taotoken.net/api 这里,配置一次就能复用。
我试过把文档解析和后续的摘要、向量化都挂在同一个 Key 下,省掉了来回切换环境变量的麻烦。下面配置里我会把 TaoToken 的 API 通道作为统一出口,本地权重作为可选加速路径。
2.2 环境依赖安装
FireRed-OCR 基于 Qwen3-VL-2B-Instruct 架构,Python 生态直接装:
pip install "transformers>=4.57.0" qwen-vl-utils accelerate pip install fastapi uvicorn python-multipart pip install bitsandbytes # 需要量化时再装硬件建议:支持 BF16 的 NVIDIA 显卡,显存 8GB 起步;想开 Flash Attention 2 的话装flash-attn,多图批量场景提速明显。Python 3.8+,PyTorch 2.0+。
2.3 获取 TaoToken Key
到控制台创建 API Key,路径是 https://taotoken.net/console ,Key 生成后在 https://taotoken.net/api-keys 管理。拿到形如sk-xxxx的字符串,先别写死在代码里,用环境变量或配置文件注入。
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"注意:Key 只存服务端环境变量,别提交到 Git 仓库。我见过有人把 Key 写进
settings.json直接推上公开仓库,几分钟就被扫走刷额度。
3. 可复制配置:config.toml 与 settings.json 骨架
3.1 config.toml 骨架
这个文件管模型加载和推理参数,放在项目根目录:
[model] name = "FireRedTeam/FireRed-OCR-2B" dtype = "bfloat16" device_map = "auto" attn_implementation = "flash_attention_2" # 无 flash-attn 时改为 "eager" max_new_tokens = 8192 quantize = "none" # 可选 "8bit" / "4bit" [api] provider = "taotoken" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" timeout = 120 max_retries = 3 [inference] image_max_side = 1600 # 长边缩放上限,太大显存吃紧 temperature = 0.0 # 结构化任务建议贪心解码 do_sample = falsetemperature=0.0这点很关键。文档解析要的是稳定复现,不是创意发挥,采样一开输出结构就可能飘。
3.2 settings.json 骨架
这个文件管服务化和批处理行为:
{ "server": { "host": "0.0.0.0", "port": 8000, "workers": 1 }, "batch": { "input_dir": "./docs", "output_dir": "./outputs", "concurrency": 2, "save_format": "markdown" }, "logging": { "level": "INFO", "file": "./logs/firered_ocr.log" }, "taotoken": { "chat_endpoint": "https://taotoken.net/api/v1/chat/completions", "model_alias": "firered-ocr" } }concurrency别贪大。2B 模型单卡并发 2 已经比较满,开太高反而因为显存抖动变慢。
3.3 加载配置的胶水代码
import os, json, tomllib from transformers import Qwen3VLForConditionalGeneration, AutoProcessor with open("config.toml", "rb") as f: cfg = tomllib.load(f) with open("settings.json", "r", encoding="utf-8") as f: settings = json.load(f) model = Qwen3VLForConditionalGeneration.from_pretrained( cfg["model"]["name"], torch_dtype="bfloat16", device_map=cfg["model"]["device_map"], attn_implementation=cfg["model"]["attn_implementation"], ) processor = AutoProcessor.from_pretrained(cfg["model"]["name"])4. 验证请求:跑通一次文档结构解析
4.1 本地推理验证
先拿一张复杂表格图试水,确认模型能出结构化 Markdown:
import torch from qwen_vl_utils import process_vision_info messages = [{ "role": "user", "content": [ {"type": "image", "image": "./examples/complex_table.png"}, {"type": "text", "text": "请将这张文档图片转换为结构化 Markdown,保留表格与标题层级。"}, ], }] text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) image_inputs, video_inputs = process_vision_info(messages) inputs = processor( text=[text], images=image_inputs, videos=video_inputs, padding=True, return_tensors="pt", ).to(model.device) with torch.no_grad(): generated_ids = model.generate(**inputs, max_new_tokens=8192, do_sample=False) trimmed = [out[len(inp):] for inp, out in zip(inputs.input_ids, generated_ids)] result = processor.batch_decode(trimmed, skip_special_tokens=True)[0] print(result)成功的话你会看到类似这样的输出片段:
# 2024年度财务摘要 | 项目 | 本期金额 | 上期金额 | |------|---------|---------| | 营业收入 | 1,234,567 | 1,098,765 | | 净利润 | 234,567 | 198,765 | ## 附注说明 公式 $E = mc^2$ 用于...表格行列对齐、标题层级闭合、公式转成 LaTeX,这三样齐了就算通过。
4.2 通过 TaoToken 通道调用
如果你不想本地加载权重,或者要把解析能力接到已有服务里,走 TaoToken 的 API 通道:
import os, base64, requests with open("./examples/complex_table.png", "rb") as f: img_b64 = base64.b64encode(f.read()).decode() resp = requests.post( "https://taotoken.net/api/v1/chat/completions", headers={ "Authorization": f"Bearer {os.environ['TAOTOKEN_API_KEY']}", "Content-Type": "application/json", }, json={ "model": "firered-ocr", "messages": [{ "role": "user", "content": [ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}}, {"type": "text", "text": "转换为结构化 Markdown。"}, ], }], "temperature": 0, "max_tokens": 8192, }, timeout=120, ) print(resp.json()["choices"][0]["message"]["content"])返回的content就是结构化 Markdown。想先在线感受一下模型对话效果,可以到 https://taotoken.net/models 试跑,确认输出风格符合预期再接进代码。
4.3 封装成 FastAPI 服务
生产环境建议包一层:
from fastapi import FastAPI, File, UploadFile import uvicorn app = FastAPI() @app.post("/ocr") async def ocr_endpoint(file: UploadFile = File(...)): contents = await file.read() with open("temp.png", "wb") as f: f.write(contents) # 复用 4.1 的推理逻辑,返回 result return {"markdown": result} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)启动后curl -F "file=@test.png" http://localhost:8000/ocr就能拿到结果。
5. 本篇常见报错排查
5.1 显存不足 OOM
报错torch.cuda.OutOfMemoryError。先降image_max_side到 1024,再不行上 8bit 量化:
model = Qwen3VLForConditionalGeneration.from_pretrained( "FireRedTeam/FireRed-OCR-2B", load_in_8bit=True, device_map="auto", )4bit 量化显存能再降一半,但公式识别精度会掉一点,自己权衡。
5.2 flash_attention_2 加载失败
报ImportError: flash_attn。要么装flash-attn,要么把config.toml里attn_implementation改成"eager"。eager 慢一些但稳。
5.3 API 返回 401 / 403
Key 没读到或过期。检查echo $TAOTOKEN_API_KEY是否有值,请求头是不是Bearer前缀漏了空格。到 https://taotoken.net/api-keys 重新生成一个对比测试。
5.4 输出 Markdown 表格错位
多半是图片分辨率太低或长边被压过头。把image_max_side提到 1600,或者对原图先做一次锐化预处理。表格线模糊时模型容易把两列并成一列。
5.5 公式输出成乱码
检查max_new_tokens是否被截断。公式较长时 8192 可能不够,调到 12288。另外确认解码时skip_special_tokens=True,否则会混入控制符。
5.6 批量处理卡死
concurrency设太高导致显存抖动。降到 1 或 2,并在每张图之间torch.cuda.empty_cache()。长期跑批任务建议上 Coding Plan 那类按量通道,避免本地显存反复申请释放。
6. 把链路接进你的项目
到这里,本地推理和 TaoToken API 两条路都通了。我的建议是:开发调试阶段用本地权重,快速迭代 prompt 和参数;上线后如果并发上来了,切到 TaoToken 的 API 通道,用统一 Key 管理文档解析和后续的摘要、向量化模型,省掉多套凭证的维护成本。
几个实用技巧收尾。第一,结构化任务永远temperature=0,别让模型自由发挥。第二,表格密集的文档把image_max_side拉到 1600 以上,精度提升肉眼可见。第三,批量任务先跑 5 张样本确认输出格式稳定,再全量放开,不然错误会成倍放大。第四,把config.toml和settings.json纳入版本管理,但 Key 走环境变量,这个习惯能帮你躲过很多安全事故。
需要长期跑编码和 Agent 类任务的,可以看看 https://taotoken.net/coding-plan ,按量通道对高频调用更划算。接入文档在 https://taotoken.net/doc ,配置细节那里写得更全。