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,导出了ModelConfig、TextConfig、VisionConfig、Model、VisionModel、LanguageModel与处理器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 种类别枚举:
Caption、Footnote、Formula、List-item、Page-footer、Page-header、Picture、Section-header、Table、Text、Title; - 按类别分流文本格式:图片类省略 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 10243) 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 4096Python 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_layout与parser_image_default两个场景因为与主文档解析 run 复用同一张图和同一 Prompt,Notebook 会写出别名产物(alias artifacts); - SVG 预览渲染在 macOS 上使用
qlmanage完成。
源码解读:DOTS 在 MLX-VLM 中的实现
以下结合 mlx_vlm/models/dots_ocr 的源码,说明上述命令背后发生的事情。
模型配置:文本塔 + 视觉塔
config.py 定义了三个 dataclass,可看出 DOTS 是一个典型的双塔 VLM 结构:
- TextConfig(
model_type="dots_ocr"):默认hidden_size=1536、intermediate_size=8960、num_hidden_layers=28、num_attention_heads=12、num_key_value_heads=2(即 12 头 Q / 2 头 KV 的 GQA)、vocab_size=151936、max_position_embeddings=131072、rope_theta=1000000.0。rope_scaling若提供,__post_init__会校验必须含factor/type两个键且type仅支持linear。 - VisionConfig(
model_type="dots_vit"):embed_dim=1536、hidden_size=1536、intermediate_size=4224、num_hidden_layers=42、num_attention_heads=12、patch_size=14、spatial_merge_size=2、temporal_patch_size=1、post_norm=True。也就是说视觉编码器有 42 层 Transformer,比文本塔还深。 - ModelConfig:聚合
text_config/vision_config,并定义image_token_id=151665、video_token_id=151656。from_dict对上游 checkpoint 的配置做了兼容:若缺text_config会把顶层字段全部归入text_config(仅剔除vision_config),若vision_config缺model_type则自动补"dots_vit"。
语言侧默认值与 Qwen2.5 系词表一致(vocab_size=151936),这与其处理器复用 Qwen2.5-VL 体系相呼应(见下节)。
处理器:复用 Qwen2.5-VL 的图像预处理
processing_dots_ocr.py 中的DotsVLProcessor直接继承transformers的Qwen2_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的流程是:
- 若没有
pixel_values,退化为纯文本嵌入; - 有图像时,要求必须提供
image_grid_thw(否则抛ValueError),并把pixel_values转换为与 patch embed 权重相同的 dtype; - 视觉塔输出图像特征后,
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_type(dots_ocr),经get_model_and_args定位到mlx_vlm.models.dots_ocr模块,调用ModelConfig.from_dict(config)构造配置,再依次用Model、VisionModel、LanguageModel三个模块做权重清洗(sanitize_weights)。若config.json含quantization字段,会对满足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=14、spatial_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_ocr→DotsVLProcessor的 AutoProcessor 注册关系纳入断言。
使用建议(Notes 原文与补充)
文档 Notes 部分给出的两条建议:
- 长文档与版面密集的页面,调大
--max-tokens(三个示例分别用了 5000 / 1024 / 4096); - 若需要严格的结构化输出,Prompt 中必须显式声明 schema 与排序规则(即上面版面 JSON 示例中第 2、4、5 条的写法)。
结合源码可以补充三点实操注意:
- 输入仅限图像。处理器中的
DotsDummyVideoProcessor会主动拒绝视频输入,不要把dots.ocr/dots.mocr当视频理解模型用; temperature=0.0适合结构化任务。文档所有 Python 示例都使用temperature=0.0,对版面 JSON 这类需要严格 schema 的输出,贪心解码更稳定;- 图像数量由
apply_chat_template的num_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 三段式调用(load→apply_chat_template→generate),再参考 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),仅供参考