☰
从 ViT 到 M-RoPE:TeleOCR 的 1.2B 架构里藏着哪些细节
2026/10/16 14:54:06 网站建设 项目流程

从 ViT 到 M-RoPE:TeleOCR 的 1.2B 架构里藏着哪些细节

【免费下载链接】TeleOCR项目地址: https://ai.gitcode.com/XingChen-AGI/TeleOCR

当 OmiDocBench 榜单上出现一个只有 1.2B 参数的模型,以 96.87 的综合得分压过 235B 的 Qwen3-VL(89.78)和 GPT-5.2(86.52)时,绝大多数人的第一反应是"这不科学"。参数差了近两百倍,凭什么能在文本识别、表格还原、公式转写这些硬指标上全面反超?

答案不在魔法里,而在架构细节中。TeleOCR 并非从零发明了一套新范式,而是站在 Qwen2.5-VL 的肩膀上,把"轻量"二字做到了极致:32 层窗口注意力 ViT 负责把像素吃干榨净,28 层 GQA Decoder 用 M-RoPE 三维位置编码把版面几何关系编码进注意力,再用 4 倍 token 压缩的 PatchMerger 把视觉特征廉价地喂给语言模型。这篇文章直接翻开仓库源码,逐行拆解这份 1.2B 的参数预算到底花在了哪里。

一张 config.json 看懂 1.2B 的参数分布

TeleOCR 的完整配置都写在仓库根目录的 config.json 里。用hidden_size × 层数粗算一下参数体量:

模块关键配置参数规模
ViT 视觉编码器depth=32,hidden_size=1280,intermediate_size=3420,16 头≈ 6 亿
Decoder 语言模型28 层,hidden_size=1024,intermediate_size=3072,16 头≈ 5 亿
PatchMerger + Embeddingout_hidden_size=1024,词表 151936,tie_word_embeddings=true≈ 1.5 亿(共享后大幅压缩)

这份配置最扎眼的地方在于:语言模型的 hidden_size(1024)刻意做得比视觉编码器(1280)小,这不同于多数 VLM"语言侧为主"的惯例。文档解析的瓶颈在"看懂版面"而非"生成文本",把算力向视觉侧倾斜,是 1.2B 能打 235B 的第一个信号。同时tie_word_embeddings: true(config.json 第 95 行)让词表 embedding 与 LM Head 权重共享,151936 词表的词嵌入只存一份,为 Decoder 侧又省下近亿参数。

32 层 ViT:窗口注意力 + 每隔 8 层一次的全局拥抱

视觉编码器的实现在 modeling_naviocr.py 的Qwen2_5_VisionTransformerPretrainedModel(第 307 行)中。整条流水线是:Conv3d Patch Embed → 32 层 Vision Block → PatchMerger。

Patch Embed 是一个 3D 卷积。Qwen2_5_VisionPatchEmbed(第 68 行)用temporal_patch_size=2, patch_size=14的 Conv3d 直接把输入的 RGB 像素切成 14×14 的 patch,每 2×14×14 的体素块映射成 1280 维向量。对单张文档图来说,temporal 维取 2 是为了与 Qwen2.5-VL 的视频预训练结构对齐,保持权重可继承。

注意力是"窗口 + 全局"的混合。32 层并非全部做全局注意力,fullatt_block_indexes配置为[7, 15, 23, 31](config.json),即每隔 7 层才做一次全局注意力,其余 28 层全部限制在局部窗口内。窗口的计算逻辑在get_window_index(第 366 行):

vit_merger_window_size = self.window_size // self.spatial_merge_size // self.patch_size # = 112 // 2 // 14 = 4,即每个窗口覆盖 4×4 个合并后的 patch

再结合 forward 循环中的分派逻辑(第 448-452 行):

for layer_num, blk in enumerate(self.blocks): if layer_num in self.fullatt_block_indexes: cu_seqlens_now = cu_seqlens # 全局注意力 else: cu_seqlens_now = cu_window_seqlens # 4×4 窗口注意力

这套设计对文档场景极其关键:一个 1036×1036 的分辨率下,ViT 会产生 5476 个 patch token,全局注意力是 O(N²) 的平方开销;而 4×4 窗口把每次注意力的视野锁死在相邻 16 个 token 内,28 层窗口 + 4 层全局的组合,让模型用约 1/8 的注意力预算拿到了"局部纹理精细、全局结构在场"的版面理解能力。文档 OCR 恰好是这种需求的完美匹配:字符细节靠局部,阅读顺序与表格行列结构靠全局。

PatchMerger 是 4 倍 token 压缩器。视觉特征在进入语言模型前,要经过Qwen2_5_VLPatchMerger(第 108 行):

self.hidden_size = context_dim * (spatial_merge_size**2) # 1280 * 4 = 5120 self.ln_q = Qwen2RMSNorm(context_dim, eps=1e-6) self.mlp = nn.Sequential( nn.Linear(self.hidden_size, self.hidden_size), nn.GELU(), nn.Linear(self.hidden_size, dim), # 5120 → 1024 )

spatial_merge_size=2意味着每 2×2 相邻 patch 被打包成一个 5120 维的向量,经 LN + 两层 MLP 压缩回语言模型的 1024 维。token 数直接除以 4:一张 1036×1036 的图从 5476 个视觉 token 降到 1369 个,KV Cache 和自回归解码的长度压力同步缩减——这是 1.2B 模型能把 4096 个新 token 的生成控制在消费级显卡上的直接原因。

28 层 Decoder:GQA 省显存,M-RoPE 管几何

视觉特征压缩完毕后,进入 28 层Qwen2_5_VLDecoderLayer。两个最值得展开的细节:GQA 和 M-RoPE。

GQA(Grouped Query Attention):16 个头共享 8 组 KV。在Qwen2_5_VLAttention(第 615 行)中,配置明确num_attention_heads=16, num_key_value_heads=8:

self.num_key_value_groups = self.num_heads // self.num_key_value_heads # = 2 self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=False) self.k_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False) self.v_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)

KV 头数减半意味着每层 KV Cache 减半。文档解析的生成序列动辄数千 token(公式、表格展开都很长),KV 占用随序列线性增长,GQA 在这里省下的不是几个百分点,而是把长序列解码从"显存爆炸"拉回"可部署"的直接手段。另一处细节是q_norm/k_norm(第 656-657 行):query 和 key 在进入注意力前各自过一层 Qwen3RMSNorm(RMSNorm 应用到 head 维度),这是 Qwen3 系模型训练稳定性增强的标配,被完整继承了下来。

M-RoPE:三维位置编码让注意力"知道"版面长什么样。这是全文最核心的机制。先看位置编码的生成:Qwen2_5_VLRotaryEmbedding.forward(第 516 行)把频率矩阵扩展成 3 份:

inv_freq_expanded = self.inv_freq[None, None, :, None].float().expand(3, position_ids.shape[1], -1, 1)

而apply_multimodal_rotary_pos_emb(第 549 行)按mrope_section把隐藏维度切成三段分别旋转:

mrope_section = mrope_section * 2 # [16, 24, 24] → [32, 48, 48] cos = torch.cat([m[i % 3] for i, m in enumerate(cos.split(mrope_section, dim=-1))], dim=-1)

配置中的mrope_section: [16, 24, 24](config.json 第 23 行)配合rope_theta: 1000000.0,含义是:128 维的 head 中,前 16 维承载时序位置,随后各 24 维分别承载高度、宽度方向的位置,三者用不同频率的旋转角编码。文本 token 的三个位置索引相同,退化为普通 1D RoPE;而图像 token 的位置索引来自_get_vision_position_ids逻辑(第 1064-1146 行):

position_ids = torch.ones(3, input_ids.shape[0], input_ids.shape[1], ...) # 对每个图像 patch token 生成 (t_index, h_index, w_index) 三元组 llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx)

最终传入 Decoder 的position_ids是(3, batch, seq)的张量,视觉 token 的每个维度位置各不相同。这意味着注意力在做 Q·K 计算时,旋转相位差里天然编码了"这两个 patch 在纸面上相距多高、多宽"。对弯折、透视变形的手机拍摄文档来说,模型不需要显式的矫正模块,位置编码本身就承载了扭曲的几何关系——这正是 README 里"无需 dewarping 预处理"的直接架构根源。

顺带一提,Decoder 的 MLP 用的是 SwiGLU 结构(Qwen2MLP,第 533 行,gate/up/down 三投影 + silu 门控),FFN 比 Vision 侧多一组投影,是标准 Qwen 系配方,此处不赘述。

动态分辨率适配与贪心解码:精度与速度的平衡术

分辨率不是固定死的。仓库的 preprocessor_config.json 给出了两个关键阈值:min_pixels: 3136、max_pixels: 12845056。Qwen2VLImageProcessor 会根据输入图的长宽比,在上下限之间把图缩放到"像素数恰好达标"的尺寸,再切成 patch。1.2B 模型之所以能同时处理数字文档与手机实拍图,正是因为这层动态缩放:分辨率上限开到 1280 万像素(约 3584×3584),足够承载弯折文档中变形字符的细节;下限 3136 又保证小图不会被过度上采样。README 的 Quick Start 中布局示例统一resize((1036, 1036))只是为了演示,实际推理完全交给 processor 按像素预算动态决定。

生成阶段直接用贪心解码。仓库 generation_config.json 里do_sample: true、temperature: 0.1、top_k: 1的参数组合,本质上是"极低温采样",而 README 官方推理代码(README.md 第 339-344 行)更进一步:

output_ids = model.generate( **inputs, use_cache=True, max_new_tokens=4096, do_sample=False, # 贪心解码,逐 token 取 argmax )

do_sample=False意味着每个 token 都是当前条件下概率最大的那个,输出完全确定、可复现。对 OCR 这类"答案唯一"的任务,采样带来的随机性只会引入字符漂移;而贪心配合repetition_penalty: 1.05抑制长公式、长表格序列中的复读,在确定性之外把质量下限兜住。这也是社区实测中"同一张图多次推理结果一致"的直接原因——对生产流水线的后处理解析(比如把 OTSL 转 HTML)来说,确定性输出意味着整条管线可以稳定复现。

结构化的"确定性"不止体现在解码:表格输出走 OTSL 中间格式(README 中仅 6 个特殊 token 描述单元格与行列跨度),由后处理函数convert_otsl_to_html确定性地转成 HTML,绕开了让模型直接生成 HTML 的语法错误风险;公式输出则由后处理统一包装成$$...$$块(README 第 312-317 行)。架构上省参数、解码上保确定,两条线合起来才凑出了"轻量 + 高分"的完整答案。

1.2B 打 235B:参数的乘法效应

最后回到那个"不科学"的榜单。以 README.md 公布的 OmniDocBench v1.6 数据看:

模型参数量Overall ↑Table TEDS ↑Read Order Edit ↓
TeleOCR1.2B96.8797.050.122
Qwen3-VL-235B235B89.7883.070.166
GPT-5.2—86.5282.950.193

235B 的通用大模型在"表格结构还原"这一项上被 1.2B 的专用模型拉开 14 个点,原因恰恰是本文拆出的这些细节:窗口注意力让视觉侧在有限参数下看到了足够多的 patch 细节,M-RoPE 把版面几何直接烙进位置编码,GQA + PatchMerger + 贪心解码把算力和显存省下来投给更长的序列和更高的分辨率。同样的结论也出现在 Wild_OmniDocBench(88.53,第一)、PureDocBench(Clean 86.90,第一)和 ICDAR2026 Sci-ImageMiner(加权 41.81,第一)上——弯折文档、退化文档这些"脏活累活",正是几何感知建模最占优势的战场。

1.2B 的架构说明了一件事:在文档解析这个垂直赛道上,架构和数据的针对性,比无脑堆参数量更接近正确答案。而这份答案的全部细节,都摊开在仓库的 1754 行模型代码与 141 行配置里,任何人随时可以翻开对照。

【免费下载链接】TeleOCR项目地址: https://ai.gitcode.com/XingChen-AGI/TeleOCR

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询