news 2026/9/17 19:20:07

MLX-VLM 中的 DOTS OCR 实战:文档解析、版面分析与结构化 JSON 提取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MLX-VLM 中的 DOTS OCR 实战:文档解析、版面分析与结构化 JSON 提取

MLX-VLM 中的 DOTS OCR 实战:文档解析、版面分析与结构化 JSON 提取

【免费下载链接】mlx-vlmMLX-VLM is a package for inference and fine-tuning of Vision Language Models (VLMs) on your Mac using MLX.项目地址: https://gitcode.com/GitHub_Trending/ml/mlx-vlm

在 Apple 芯片 Mac 上用 MLX 运行 OCR 与文档解析模型,是 MLX-VLM(一个基于 MLX 的视觉语言模型推理与微调框架)里很实用的一类场景。本文以仓库中的 DOTS OCR 模型文档 为主体,完整覆盖dots.ocr/dots.mocr模型的 CLI 用法、Python 脚本写法与配套 Notebook 演示,并结合mlx_vlm/models/dots_ocr/目录下的源码实现(模型结构、视觉编码器、处理器),讲清这套模型在 MLX-VLM 中是如何被加载、组装与调用的,读完即可在自己的 Mac 上复现版面 JSON 提取、基础 OCR 与 Markdown 转换三类任务。

模型定位:DOTS 是什么

DOTS 系列是面向文档解析(document parsing)、版面分析(layout analysis)与结构化抽取的视觉语言 OCR 模型。仓库文档给出的定位是:

  • 主要用途:OCR、版面解析、表格抽取、公式抽取、结构化 JSON 输出。
  • 两个检查点
    • dots.ocr:原始的 DOTS OCR 模型;
    • dots.mocr:在dots.ocr基础上扩展的模型,具备更强的多语言解析与结构化图形生成(如 SVG)能力。

对应的模型标识符(HF 仓库名)在文档示例中为rednote-hilab/dots.mocr与量化版mlx-community/dots.mocr-4bit。MLX-VLM 中该模型由 dots_ocr 模型目录 实现,模块导出入口在 mlx_vlm/models/dots_ocr/init.py,导出了ModelConfigTextConfigVisionConfigModelVisionModelLanguageModel与处理器DotsVLProcessor

安装

文档给出的安装方式:

uv pip install mlx-vlm

安装后即可通过mlx_vlm.generateCLI 或 Python API 调用该模型(模型权重按--model指定的仓库名自动下载)。

CLI 实战:三类典型任务

1) 版面 JSON 提取(详细 Prompt)

这是dots.mocr的招牌用法:让模型输出整页版面信息,每个元素包含 bbox、类别和文本内容,整体是一个 JSON 对象。文档给出的完整命令(注意 Prompt 直接内嵌在命令行中):

uv run mlx_vlm.generate --model rednote-hilab/dots.mocr --prompt "Please output the layout information from the PDF image, including each layout element's bbox, its category, and the corresponding text content within the bbox. 1. Bbox format: [x1, y1, x2, y2] 2. Layout Categories: The possible categories are ['Caption', 'Footnote', 'Formula', 'List-item', 'Page-footer', 'Page-header', 'Picture', 'Section-header', 'Table', 'Text', 'Title']. 3. Text Extraction & Formatting Rules: - Picture: For the 'Picture' category, the text field should be omitted. - Formula: Format its text as LaTeX. - Table: Format its text as HTML. - All Others (Text, Title, etc.): Format their text as Markdown. 4. Constraints: - The output text must be the original text from the image, with no translation. - All layout elements must be sorted according to human reading order. 5. Final Output: The entire output must be a single JSON object." --image "path_to_image.jpg" --max-tokens 5000

这个 Prompt 的设计要点值得保留:

  • bbox 格式固定为[x1, y1, x2, y2]
  • 11 种类别枚举CaptionFootnoteFormulaList-itemPage-footerPage-headerPictureSection-headerTableTextTitle
  • 按类别分流文本格式:图片类省略 text 字段,公式输出 LaTeX,表格输出 HTML,其余输出 Markdown;
  • 两条硬约束:文本必须是图像原文(禁止翻译)、元素按人类阅读顺序排序;
  • 最终输出为单一 JSON 对象,便于下游程序直接解析。

--max-tokens 5000用于覆盖整页密集文本的输出长度。

2) 基础 OCR

最简单直接的全文字抽取:

uv run mlx_vlm.generate \ --model rednote-hilab/dots.mocr \ --image receipt.jpg \ --prompt "Extract all text from this image." \ --max-tokens 1024

3) Markdown 文档转换

使用 4bit 量化权重做整页转 Markdown,保持阅读顺序:

uv run mlx_vlm.generate \ --model mlx-community/dots.mocr-4bit \ --image page.png \ --prompt "Convert this page to clean Markdown while preserving reading order." \ --max-tokens 4096

Python API 实战

三个脚本示例都遵循同一套模式:load加载模型与处理器 →apply_chat_template套用聊天模板 →generate生成。以下按文档原样给出。

1)layout_json.py:版面 JSON 提取

from mlx_vlm import generate, load from mlx_vlm.prompt_utils import apply_chat_template MODEL = "mlx-community/dots.mocr-4bit" IMAGE_PATH = "path_to_image.jpg" PROMPT = """Please output the layout information from the PDF image, including each layout element's bbox, its category, and the corresponding text content within the bbox. 1. Bbox format: [x1, y1, x2, y2] 2. Layout Categories: The possible categories are ['Caption', 'Footnote', 'Formula', 'List-item', 'Page-footer', 'Page-header', 'Picture', 'Section-header', 'Table', 'Text', 'Title']. 3. Text Extraction & Formatting Rules: - Picture: For the 'Picture' category, the text field should be omitted. - Formula: Format its text as LaTeX. - Table: Format its text as HTML. - All Others (Text, Title, etc.): Format their text as Markdown. 4. Constraints: - The output text must be the original text from the image, with no translation. - All layout elements must be sorted according to human reading order. 5. Final Output: The entire output must be a single JSON object.""" model, processor = load(MODEL) formatted_prompt = apply_chat_template( processor, model.config, PROMPT, num_images=1, ) result = generate( model=model, processor=processor, prompt=formatted_prompt, image=IMAGE_PATH, max_tokens=5000, temperature=0.0, ) print(result.text)

2)basic_ocr.py:基础 OCR

from mlx_vlm import generate, load from mlx_vlm.prompt_utils import apply_chat_template MODEL = "mlx-community/dots.mocr-4bit" IMAGE_PATH = "receipt.jpg" PROMPT = "Extract all text from this image." model, processor = load(MODEL) formatted_prompt = apply_chat_template( processor, model.config, PROMPT, num_images=1, ) result = generate( model=model, processor=processor, prompt=formatted_prompt, image=IMAGE_PATH, max_tokens=1024, temperature=0.0, ) print(result.text)

3)markdown_document_conversion.py:Markdown 转换

from mlx_vlm import generate, load from mlx_vlm.prompt_utils import apply_chat_template MODEL = "mlx-community/dots.mocr-4bit" IMAGE_PATH = "page.png" PROMPT = "Convert this page to clean Markdown while preserving reading order." model, processor = load(MODEL) formatted_prompt = apply_chat_template( processor, model.config, PROMPT, num_images=1, ) result = generate( model=model, processor=processor, prompt=formatted_prompt, image=IMAGE_PATH, max_tokens=4096, temperature=0.0, ) print(result.text)

注意三个示例中max_tokens的取值差异:版面 JSON 5000、基础 OCR 1024、Markdown 转换 4096——输出长度上限应与任务的内容密度匹配,这也是文档 Notes 部分的第一条建议。

Notebook 演示:dots_mocr_demo.ipynb

文档还指向一个完整的交互式演示 examples/dots_mocr_demo.ipynb,按文档描述:

  • 该 Notebook 只用 MLX-VLM 复现了上游dots.mocrREADME 中的全部场景;
  • 使用仓库自带本地演示素材(位于 examples/images),避免依赖外网图片;
  • 会保存原始输出、叠加标注图(overlays)、渲染后的 SVG 预览与一张拼图总览(contact sheet);
  • 当模型生成的 SVG 非法时,Notebook 会回退为可读的文本叠加层,而不是直接渲染器报错页;
  • 其中demo_hf_layoutparser_image_default两个场景因为与主文档解析 run 复用同一张图和同一 Prompt,Notebook 会写出别名产物(alias artifacts);
  • SVG 预览渲染在 macOS 上使用qlmanage完成。

源码解读:DOTS 在 MLX-VLM 中的实现

以下结合 mlx_vlm/models/dots_ocr 的源码,说明上述命令背后发生的事情。

模型配置:文本塔 + 视觉塔

config.py 定义了三个 dataclass,可看出 DOTS 是一个典型的双塔 VLM 结构:

  • TextConfigmodel_type="dots_ocr"):默认hidden_size=1536intermediate_size=8960num_hidden_layers=28num_attention_heads=12num_key_value_heads=2(即 12 头 Q / 2 头 KV 的 GQA)、vocab_size=151936max_position_embeddings=131072rope_theta=1000000.0rope_scaling若提供,__post_init__会校验必须含factor/type两个键且type仅支持linear
  • VisionConfigmodel_type="dots_vit"):embed_dim=1536hidden_size=1536intermediate_size=4224num_hidden_layers=42num_attention_heads=12patch_size=14spatial_merge_size=2temporal_patch_size=1post_norm=True。也就是说视觉编码器有 42 层 Transformer,比文本塔还深。
  • ModelConfig:聚合text_config/vision_config,并定义image_token_id=151665video_token_id=151656from_dict对上游 checkpoint 的配置做了兼容:若缺text_config会把顶层字段全部归入text_config(仅剔除vision_config),若vision_configmodel_type则自动补"dots_vit"

语言侧默认值与 Qwen2.5 系词表一致(vocab_size=151936),这与其处理器复用 Qwen2.5-VL 体系相呼应(见下节)。

处理器:复用 Qwen2.5-VL 的图像预处理

processing_dots_ocr.py 中的DotsVLProcessor直接继承transformersQwen2_5_VLProcessor,关键行为包括:

  • 图像 token 默认为<|imgpad|>image_token_id硬编码为151665,与ModelConfig.image_token_id对应;
  • 视频处理器是DotsDummyVideoProcessor——它的__call__直接抛出NotImplementedError("DOTS MLX processors do not support video inputs.")。因此dots_ocr在 MLX-VLM 中仅支持图像输入,不支持视频,这一点与仓库文档中"OCR、文档解析"的定位一致;
  • from_pretrained会同时加载 tokenizer(并尝试从本地目录加载chat_template)与 image processor(use_fast=False,失败则回退默认);
  • 文件末尾install_auto_processor_patch("dots_ocr", DotsVLProcessor)dots_ocr这个model_type注册进 AutoProcessor 分发,使得load()时能按config.json里的model_type找到正确的处理器。

在 Prompt 组装层面,mlx_vlm/prompt_utils.py 第 62 行将"dots_ocr"映射为MessageFormat.LIST_WITH_IMAGE_FIRST,即图像 token 先于文本出现在消息结构里,apply_chat_template(processor, model.config, PROMPT, num_images=1)会据此把<image>占位正确填入聊天模板。

模型主体:视觉特征如何并入文本嵌入

dots_ocr.py 中的Model只做两件事的组合:

self.vision_tower = VisionModel(config.vision_config) self.language_model = LanguageModel(config.text_config) # 来自 llava_bunny.language

其中语言侧复用了 mlx_vlm/models/llava_bunny/language.py 的LanguageModel,视觉侧是dots_ocr/vision.py中的VisionModel

前向时get_input_embeddings的流程是:

  1. 若没有pixel_values,退化为纯文本嵌入;
  2. 有图像时,要求必须提供image_grid_thw(否则抛ValueError),并把pixel_values转换为与 patch embed 权重相同的 dtype;
  3. 视觉塔输出图像特征后,merge_input_ids_with_image_features静态方法负责把特征"塞回"文本序列:在input_ids中定位所有image_token_id(若为 0 个则尝试video_token_id),用累积和生成每个图像 token 对应的特征下标,按 batch 逐个校验"图像 token 位置数 == 图像特征数"(不等则抛ValueError),最后用mx.where将图像 token 位置的文本嵌入替换为视觉特征。

此外还支持cached_image_features关键字参数,传入时直接复用缓存的视觉隐状态、跳过视觉塔前向——这与仓库的 vision cache 机制相衔接(mlx_vlm/tests/test_vision_cache.py 第 137 行把"dots_ocr.dots_ocr"列入了覆盖的模型)。

视觉编码器:14×14 patch、2×2 空间合并、2D 旋转位置编码

vision.py 中的VisionModel是一条完整的 ViT 流水线:

  • Patch 嵌入DotsPatchEmbed):Conv2d(3, 1536, kernel=14, stride=14)把图像切成 14×14 的 patch 并映射到 1536 维,随后过RMSNorm
  • 2D 旋转位置编码VisionRotaryEmbedding+get_pos_ids_by_grid):按image_grid_thw为每个 patch 计算 (h, w) 二维位置 id,注意位置 id 会先按spatial_merge_size=2做 2×2 分组重排,使旋转编码与后面的空间合并对齐;
  • 变长序列打包cu_seqlens):多张图的 patch 序列被拼接后,用累积序列长度数组切分,VisionAttention内部按cu_seqlens逐段调用mx.fast.scaled_dot_product_attention,保证跨图 patch 之间互不干扰;
  • 42 个DotsVisionBlock:RMSNorm → 注意力 → 残差 → RMSNorm → SwiGLU FFN(fc1/fc2/fc3三线性层,silu(fc1(x)) * fc3(x))→ 残差;
  • 输出端post_norm=True时过post_trunk_norm,再过PatchMerger——它先做 LayerNorm,再按spatial_merge_size=2把相邻 2×2=4 个 patch 特征拼成一个 6144 维向量,经两层线性层(夹 GELU)投影回 1536 维,即最终送入语言模型的每"合并块"特征。

VisionModel.sanitize还处理了权重形状兼容:patch_embed.patchifier.proj.weight若非 NCHW 布局会自动transpose(0, 2, 3, 1),并跳过position_ids类键——这是为兼容上游 checkpoint 布局而做的权重清洗。Model.sanitize则负责键名改写:model.vision_tower.*vision_tower.*model.*language_model.model.*lm_head.*language_model.model.lm_head.*

加载链路:load()dots_ocr.Model

从源码结构看,load()内部通过 mlx_vlm/encoder_loader.py 的load_encoder_model完成装配:读取config.json得到model_typedots_ocr),经get_model_and_args定位到mlx_vlm.models.dots_ocr模块,调用ModelConfig.from_dict(config)构造配置,再依次用ModelVisionModelLanguageModel三个模块做权重清洗(sanitize_weights)。若config.jsonquantization字段,会对满足weight.size % 64 == 0且存在.scales键的模块调用nn.quantize完成量化权重反量化/量化装配——这正是mlx-community/dots.mocr-4bit这类 4bit checkpoint 能直接load运行的机制。

测试印证

  • mlx_vlm/tests/test_models.py 的test_dots_ocr用缩小尺寸(hidden_size=64、2 层、patch_size=14spatial_merge_size=2)构造TextConfig/VisionConfig/ModelConfig,实例化dots_ocr.Model,并验证语言塔行为与pixel_values=(4, 3*14*14)image_grid_thw=[[1, 2, 2]]的视觉前向路径;
  • mlx_vlm/tests/test_processors.py 的TestDotsVLProcessor(约第 945 行起)验证DotsVLProcessor的构造与from_pretrained行为,第 3718 行附近还将dots_ocrDotsVLProcessor的 AutoProcessor 注册关系纳入断言。

使用建议(Notes 原文与补充)

文档 Notes 部分给出的两条建议:

  • 长文档与版面密集的页面,调大--max-tokens(三个示例分别用了 5000 / 1024 / 4096);
  • 若需要严格的结构化输出,Prompt 中必须显式声明 schema 与排序规则(即上面版面 JSON 示例中第 2、4、5 条的写法)。

结合源码可以补充三点实操注意:

  1. 输入仅限图像。处理器中的DotsDummyVideoProcessor会主动拒绝视频输入,不要把dots.ocr/dots.mocr当视频理解模型用;
  2. temperature=0.0适合结构化任务。文档所有 Python 示例都使用temperature=0.0,对版面 JSON 这类需要严格 schema 的输出,贪心解码更稳定;
  3. 图像数量由apply_chat_templatenum_images声明。单页文档传num_images=1;多图场景按实际张数调整,模型会依据image_grid_thw与图像 token 数量做严格校验,数量不匹配会直接报错。

小结

DOTS OCR 在 MLX-VLM 中的形态是一个"Qwen 系语言塔 + 42 层自研 ViT 视觉塔"的图像 OCR 模型:处理器复用 Qwen2.5-VL 的图像预处理链路,视觉特征经 14×14 patch 化与 2×2 空间合并后按image_token_id位置注入文本嵌入。对使用者而言,只需掌握mlx_vlm.generate的三个 CLI 模式(版面 JSON、基础 OCR、Markdown 转换)与对应的 Python 三段式调用(loadapply_chat_templategenerate),再参考 examples/dots_mocr_demo.ipynb 的完整演示,即可在 Mac 上完成从扫描页到结构化文档的解析管线。

【免费下载链接】mlx-vlmMLX-VLM is a package for inference and fine-tuning of Vision Language Models (VLMs) on your Mac using MLX.项目地址: https://gitcode.com/GitHub_Trending/ml/mlx-vlm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 19:18:01

VS Code 操作 MySQL:连接、SQL 管理与执行计划实战

写业务代码的时候最烦的不是逻辑绕&#xff0c;而是为了确认一条数据&#xff0c;得从 VS Code 切到 MySQL 图形客户端&#xff0c;查完再切回来&#xff0c;思路刚断了一截&#xff0c;回来还得重新把上下文捡起来。我统计过自己一天的窗口切换次数&#xff0c;密集的时候一小…

作者头像 李华
网站建设 2026/9/17 19:16:15

单因素与两因素方差分析:原理、Python实操与常见坑

去年有回&#xff0c;运营同学抱着一份数据来找我&#xff1a;三个落地页版本&#xff0c;各跑了小半个月&#xff0c;回收了每版 300 条左右的用户评分&#xff0c;开门见山就问“到底哪个版本该上线”。我的第一反应不是去看均值谁高谁低&#xff0c;而是先问了自己一句&…

作者头像 李华
网站建设 2026/9/17 19:14:42

Windows电池健康度精准检测与深度校准指南

1. 为什么“电池健康度”不是个虚概念&#xff0c;而是能精准量化的硬件状态指标很多人以为笔记本电池健康度只是厂商宣传话术里的一个模糊词汇&#xff0c;就像手机里“剩余寿命85%”这种提示&#xff0c;点开就看个数字&#xff0c;关掉就忘。但其实Windows系统从Vista时代起…

作者头像 李华
网站建设 2026/9/17 19:12:52

UTF-8与GB18030容错性对比:字节丢失后谁能快速恢复

遇到过这种情况吗&#xff1f;日志里一段GB18030编码的中文&#xff0c;传输过程中丢了半个字节&#xff0c;结果后面整段文字直接变成一团乱码&#xff1b;同一份内容换成UTF-8&#xff0c;丢字节后往往只是在损坏位置冒出几个替换符&#xff0c;后面的句子还能正常读。这不是…

作者头像 李华