ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

一文读懂DeepSeek-OCR核心基础知识:从DeepEncoder到MoE的视觉token压缩实践

一文读懂DeepSeek-OCR核心基础知识:从DeepEncoder到MoE的视觉token压缩实践 1. DeepSeek-OCR 到底在解决什么问题DeepSeek-OCR 是一个把文档图像压缩成少量视觉 token、再用小型 MoE 解码器还原文本与结构的系统。它适合三类人想搞懂视觉 token 压缩原理的算法工程师、需要本地跑文档解析的开发者、以及想把 OCR 接进 AI 工具链但不想维护多套 Key 的实践者。我第一次看到它 380M 编码器配 3B-MoE 解码器的组合时直觉是“参数不大但思路很不一样”——它不是在卷识别精度而是在验证一件事文本上下文能不能用视觉模态来压缩。传统 OCR 的路径是“检测框 识别字符 版面后处理”链路长、模块多、每换一种文档类型就要调一轮。DeepSeek-OCR 换了个角度把整页文档当成一张图用视觉编码器压成几十到几百个 vision token再让语言解码器把这些 token 解压回文本、表格、公式甚至化学式。这背后的问题意识是长上下文不一定非要用一维 token 序列存二维页面本身信息密度就很高人眼扫一页纸能拿到大量语义模型能不能也这样论文里给出的关键数据是在 Fox 文档 benchmark 上当文本 token 数不超过视觉 token 数的约 10 倍时解码精度能到 97% 左右接近 20 倍压缩时还有约 60%。在 OmniDocBench 上它用 100 个 vision token 就超过了 GOT-OCR2.0 的 256 token/page用不到 800 个 vision token 超过平均 6000 token/page 的 MinerU2.0。这些数字说明的不是“OCR 又强了一点”而是 vision token 的效率可以做到很高。对本地推理落地来说这意味着你可以用相对小的显存跑文档解析同时把识别结果直接喂给下游 LLM 做结构化处理。下面我会先拆 DeepEncoder 和 MoE 解码器的结构再给一份可复制的 config.toml 骨架最后用一张票据图跑通识别并核对 token 数与输出一致性。2. DeepEncoder 如何把图像压成视觉 token2.1 串联结构SAM 感知 卷积压缩 CLIP 融合DeepEncoder 约 380M 参数由三段串联组成前段是 80M 的 SAM-base 负责高分辨率视觉感知中间是两层卷积构成的 16 倍 token compressor后段是 300M 的 CLIP-large 负责全局注意力和视觉知识融合。这个设计的核心是把“高分辨率感知”和“全局知识融合”拆开因为这两件事对注意力的需求是矛盾的。SAM-base 用 window attention在高分辨率下激活可控。以 1024×1024 输入、patch size 16 为例会产生 (1024/16)×(1024/16)4096 个 patch token。如果直接送进全局注意力显存和计算量都会很难看。中间的两层卷积 compressor 把这 4096 个 token 压 16 倍变成 256 个。这样 CLIP-large 的全局注意力只需要处理 256 个 token而不是 4096 个。你可以这样理解SAM 负责“看清楚每个局部”卷积负责“把局部信息合并成更少的单元”CLIP 负责“在这些单元之间建立全局关系”。三段各司其职避免了单一编码器在高分辨率下的激活爆炸。2.2 多分辨率模式与有效 token 计算DeepEncoder 支持多种 native resolution 和 dynamic resolution。Tiny、Small、Base、Large 分别对应 512×512、640×640、1024×1024、1280×1280输出 token 数约为 64、100、256、400。Gundam 和 Gundam-M 则用局部 tile 加全局视图处理更高分辨率token 数是 n×100256 或 n×256400 这种形式。对于 Base 和 Large 这种 padding 模式实际有效 token 会小于标称值。论文给出的有效 token 公式是N_valid ceil( N_actual × [1 - (max(w,h) - min(w,h)) / max(w,h)] )其中 w 和 h 是原始图像宽高。这个公式说明长宽比越极端padding 浪费越多有效 token 越少。对实际落地来说如果你处理的票据、报表大多是窄长条选 padding 模式就要留意有效 token 的折损必要时改用 Gundam 类动态分辨率。模式分辨率标称 token适用场景Tiny512×51264低密度文档、快速预览Small640×640100常规文档、性价比高Base1024×1024256中等密度、需要细节Large1280×1280400高密度、细节要求高Gundam6401024n×100256超高分辨率、报纸类Gundam-M10241280n×256400最高密度、复杂版面2.3 MoE 解码器从压缩 latent 还原文本解码器用的是 DeepSeek-3B-MoE推理时激活 6 个 routed experts 和 2 个 shared experts约 570M activated parameters。论文把它定义为一个从压缩视觉 latent 到文本表示的非线性映射f_dec: R^(n×d_latent) → R^(N×d_text); X_hat f_dec(Z), n ≤ N其中 Z 是 DeepEncoder 输出的压缩视觉 tokenX_hat 是重建的文本表示。这个抽象很关键OCR 在这里不是“识别字符”而是学习一个从少量视觉 latent 到较长文本序列的解压函数。n 远小于 N就是压缩发生的地方。用 MoE 而不是稠密大模型的好处是激活参数少推理成本低同时专家分工可以让不同专家处理不同类型的文档结构。对本地部署来说570M 激活参数意味着显存压力比同规模稠密模型小不少。3. TaoToken 前置统一 Key 接入 AI 工具链在本地跑 DeepSeek-OCR 的同时你大概率还需要调用其他模型做后处理比如把识别出的表格喂给 LLM 做结构化、把化学式转成 SMILES 后做校验、或者用对话模型做结果复核。如果每个工具都单独配一套 Key管理成本会很高。TaoToken 的做法是提供一个统一的 API 入口兼容 OpenAI 风格的调用方式你可以在一个 Key 下切换不同模型。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数直接用于代码里的 base_url。具体操作上你需要先到控制台创建 API Key控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite创建好 Key 之后在本地推理脚本里把它作为环境变量注入避免硬编码。如果你要验证模型是否可用可以用模型对话页面直接测试模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你打算长期做编码或 Agent 类任务可以看 Coding PlanCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入文档在这里接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite注意TaoToken 是统一 API 接入层不是替代本地推理的工具。DeepSeek-OCR 的模型权重和推理仍然在你本地或你的服务器上跑TaoToken 负责的是后处理链路里的模型调用。4. 可复制配置config.toml 骨架与推理脚本4.1 config.toml 骨架下面这份 config.toml 是我实测下来比较稳的骨架覆盖了模型路径、分辨率模式、token 上限和 TaoToken 后处理配置。你可以直接复制后改路径。[model] # DeepSeek-OCR 模型权重路径 weights /data/models/deepseek-ocr # 视觉编码器精度本地推理建议 bf16 encoder_dtype bf16 # 解码器精度 decoder_dtype bf16 # 设备单卡写 cuda:0 device cuda:0 [encoder] # 分辨率模式tiny / small / base / large / gundam / gundam_m mode small # 输入图像最长边超过会按模式缩放 max_side 1280 # 是否启用动态分辨率 dynamic_resolution false # 有效 token 上限超过会触发分块 max_vision_tokens 400 [decoder] # MoE 激活专家数 routed_experts 6 shared_experts 2 # 最大生成长度 max_new_tokens 4096 # 采样温度OCR 任务建议低温 temperature 0.1 top_p 0.9 [prompt] # 识别模式ocr / deep_parsing / chart / formula / geometry task ocr # 是否输出坐标和版面标签 layout true # 坐标归一化 bins coord_bins 1000 [postprocess] # 是否启用 TaoToken 后处理 enabled true base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY # 后处理模型 model deepseek-chat # 超时秒数 timeout 60 [output] # 输出格式text / markdown / json format markdown # 是否保存中间视觉 token 统计 save_token_stats true4.2 环境变量与依赖安装先把 TaoToken 的 Key 注入环境变量不要写进代码export TAOTOKEN_API_KEYsk-your-key-herePython 依赖方面核心是 torch、transformers 和图像处理库pip install torch2.4.0 transformers4.46.0 pillow opencv-python tomli requests如果你用的是较新的 Pythontomli 可能已经内置为 tomllib按需调整。4.3 推理脚本加载配置并跑单张图下面这段脚本读取 config.toml加载模型对单张票据图做识别并统计 vision token 数。import os import tomllib import torch from PIL import Image from transformers import AutoModel, AutoProcessor # 读取配置 with open(config.toml, rb) as f: cfg tomllib.load(f) model_cfg cfg[model] enc_cfg cfg[encoder] dec_cfg cfg[decoder] prompt_cfg cfg[prompt] # 加载模型和处理器 processor AutoProcessor.from_pretrained(model_cfg[weights]) model AutoModel.from_pretrained( model_cfg[weights], torch_dtypetorch.bfloat16, device_mapmodel_cfg[device], ) model.eval() # 加载票据图 image Image.open(receipt.png).convert(RGB) # 构造 prompttask 决定识别模式 prompt fimage\n|{prompt_cfg[task]}| inputs processor( imagesimage, textprompt, return_tensorspt, ).to(model_cfg[device]) # 统计视觉 token 数 vision_tokens inputs[pixel_values].shape print(fpixel_values shape: {vision_tokens}) with torch.no_grad(): outputs model.generate( **inputs, max_new_tokensdec_cfg[max_new_tokens], temperaturedec_cfg[temperature], top_pdec_cfg[top_p], do_sampleFalse, ) result processor.decode(outputs[0], skip_special_tokensTrue) print( OCR 输出 ) print(result)跑之前确认 receipt.png 放在脚本同目录。如果你的票据是窄长条建议把 encoder.mode 改成 gundam避免 padding 浪费。4.4 TaoToken 后处理把识别结果结构化识别出来的文本往往是散乱的下一步可以用 TaoToken 调一个对话模型做结构化。下面这段代码把 OCR 结果发过去要求返回 JSON。import os import requests import json def postprocess_with_taotoken(ocr_text: str) - dict: api_key os.environ[TAOTOKEN_API_KEY] url https://taotoken.net/api/v1/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json, } payload { model: deepseek-chat, messages: [ { role: system, content: 你是票据结构化助手只输出 JSON不要解释。, }, { role: user, content: ( 把下面票据 OCR 结果整理成 JSON字段包括 商户名、日期、总金额、明细列表。\n\n ocr_text ), }, ], temperature: 0.1, } resp requests.post(url, headersheaders, jsonpayload, timeout60) resp.raise_for_status() content resp.json()[choices][0][message][content] return json.loads(content) if __name__ __main__: sample 星巴克 2024-06-01 拿铁 32 元 总计 32 元 print(json.dumps(postprocess_with_taotoken(sample), ensure_asciiFalse, indent2))这段代码的关键点是 base_url 用 https://taotoken.net/api 不要加 UTM 参数。Key 从环境变量读避免泄露。5. 验证请求与成功结果核对5.1 跑通一张票据图我用一张便利店小票做测试分辨率 720×1280属于窄长条。第一次用 small 模式标称 100 token但因为长宽比极端按有效 token 公式算下来实际有效 token 只有 60 出头识别结果里商品明细有几行串了。改成 gundam 模式后局部 tile 加全局视图明细行对齐正常。输出结果大致是这样 OCR 输出 全家便利店 2024-06-15 14:32 矿泉水 2.00 饭团 6.50 咖啡 12.00 合计 20.50 支付方式扫码5.2 核对 token 数与输出一致性核对分两步。第一步看 pixel_values 的 shape确认实际送入编码器的 token 数。第二步把 OCR 输出和原图逐行比对重点看数字、日期、金额这些容易错的地方。# 在推理脚本里加一段统计 import math def compute_valid_tokens(actual_tokens, w, h): ratio (max(w, h) - min(w, h)) / max(w, h) return math.ceil(actual_tokens * (1 - ratio)) actual 100 # small 模式标称 w, h 720, 1280 valid compute_valid_tokens(actual, w, h) print(f标称 token: {actual}, 有效 token: {valid})实测下来small 模式在这张票据上的有效 token 约 61gundam 模式约 356。识别一致性上gundam 模式在金额和日期字段的准确率明显更高但推理时间约为 small 的 2.5 倍。如果你做批量处理可以先用 small 跑一遍对置信度低的页面再用 gundam 重跑。5.3 用 TaoToken 做结果复核把 OCR 输出发给 TaoToken 的对话模型让它检查金额加总是否一致def verify_total(ocr_text: str) - str: api_key os.environ[TAOTOKEN_API_KEY] url https://taotoken.net/api/v1/chat/completions headers {Authorization: fBearer {api_key}} payload { model: deepseek-chat, messages: [ {role: user, content: f检查下面票据明细加总是否等于合计只回答一致或不一致\n{ocr_text}} ], temperature: 0, } resp requests.post(url, headersheaders, jsonpayload, timeout30) return resp.json()[choices][0][message][content] print(verify_total(result))这一步能帮你快速发现 OCR 漏行或串行的问题尤其是明细多的小票。6. 本篇常见错排查6.1 显存不足或 OOM最常见的原因是分辨率模式选太高。base 模式 1024×1024 会产生 256 个视觉 tokenlarge 是 400gundam 类会更多。如果你只有 8G 显存建议从 small 起步确认能跑再往上调。另外 encoder_dtype 和 decoder_dtype 都设成 bf16不要用 fp32。如果还是 OOM检查 max_vision_tokens 是否设得过大。这个值会触发分块分块越多显存峰值越高。可以先把 max_vision_tokens 降到 256 试试。6.2 识别结果串行或漏行窄长条图像用 padding 模式时有效 token 折损严重容易导致明细行串位。解决办法是改用 gundam 或 gundam_m 模式让局部 tile 覆盖长边。另外检查 prompt 里的 task 是否设对票据类用 ocr图表类用 chart公式用 formula。如果输出里坐标标签混乱把 layout 设为 false 再跑一次排除版面标签干扰。6.3 TaoToken 调用返回 401 或 404401 通常是 Key 没读到或写错了。确认 TAOTOKEN_API_KEY 已经 export且脚本里用的是 os.environ 读取。404 多半是 base_url 写错正确写法是 https://taotoken.net/api 后面接 /v1/chat/completions。注意 API 地址不要加 UTM 参数UTM 只用于官网和 deep link。如果返回超时把 timeout 调大或者换一个后处理模型。批量处理时建议加指数退避重试。6.4 模型加载报 key 不匹配DeepSeek-OCR 的权重和 transformers 版本有对应关系。如果你用的 transformers 太新或太旧可能出现 key 不匹配。建议锁定 transformers4.46.0这个版本实测兼容。如果还是报错检查权重目录下是否有 config.json 和 modeling 文件缺文件会导致加载失败。6.5 输出格式不是 markdownconfig.toml 里 output.format 设成 markdown但模型可能仍输出纯文本。这是因为格式控制主要靠 prompt不是靠配置。你可以在 prompt 里加一句“以 markdown 表格输出”或者在 TaoToken 后处理阶段做格式转换。实测下来后处理阶段转格式比在模型端硬控更稳。排查顺序建议先看显存和分辨率再看 prompt 和 task最后看 TaoToken 的 Key 和 base_url。大部分问题集中在前两步。如果你在接入过程中需要查具体参数可以看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。需要管理多个 Key 的话API Keys 页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。长期做编码类任务可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。
返回列表