为什么不用单张高分辨率图?
一、为什么不用单张高分辨率图?
传统 VLM 如果直接输入:
比如:
- 1024×1024
- 1536×1536
ViT patch 数会急剧上升。
假设 patch size = 14(ViT 常见)
那么 token 数近似:
728×728
[
(728/14)^2 = 52^2 = 2704
]
1024×1024
[
(1024/14)^2 \approx 73^2 = 5329
]
几乎翻倍。
而 ViT self-attention复杂度是:
O(N^2)
token翻倍,attention计算可能接近 4倍。
所以 Step3-VL 的策略是:
不要让一张图特别大,而是拆成:
一个全局图 + 多个局部图
这样可以:
- 保持 global understanding
- 获得 local detail
- batch 并行计算更友好
论文里明确提到:
exploits batch-dimension parallelism, avoiding variable-length packing complexity. (HyperAI)
即:
他们把不同 crop 当 batch 维度并行送进 ViT,而不是拼成长序列。
二、Step3-VL 的 Multi-crop 是怎么切的?
原图(假设任意分辨率):
1 | Original Image |
先做两个分支:
分支1:Global View
缩放到:
1 | 728 × 728 |
作用:
保留全局布局:
比如文档:
1 | +----------------------+ |
模型能理解:
- 标题在哪
- 图片在哪
- 表格在哪
- 阅读顺序是什么
分支2:Local Crops
从原图切多个局部窗口:
每个:
1 | 504 × 504 |
比如:
1 | +---------------------------+ |
这些 crop 通常覆盖:
- 左上
- 中间
- 右下
- 重叠区域
(具体 crop 数会随原图尺寸变化,推理代码动态决定)
作用:
放大看细节:
比如 OCR:
原图:
1 | Invoice #20250418 |
global里可能太小。
local crop里变成:
1 | Invoice |
ViT 就更容易识别。
三、ViT处理流程(Step3-VL)
下面是视觉编码完整流程:
Step 1:图像切分
1 | Original Image |
即:
最终形成:
1 | [ |
这些作为 batch 输入。
Step 2:Patch Embedding
每个 crop 输入 PE-lang ViT:
论文中视觉编码器叫:
PE-lang (Perception Encoder),约 1.8B 参数。(GitHub)
流程:
1 | 504×504 crop |
例如:
若 patch size = 14:
global
1 | 728/14 = 52 |
得到:
1 | 52×52 patches |
token 数:
1 | 2704 tokens |
local crop
1 | 504/14 = 36 |
得到:
1 | 36×36 patches |
token 数:
1 | 1296 tokens |
四、ViT Transformer Encoder
每个 crop token进入 Transformer blocks:
1 | Patch Tokens |
这里做:
- Self-attention
- MLP
- LayerNorm
输出视觉特征:
1 | [T_crop, hidden_dim] |
五、Step3-VL 的空间位置编码(很关键)
Step3-VL 没用 fancy 2D position。
而是用了:
1D RoPE
并通过插入 newline token 保留二维结构:
论文原文:
Spatial structure is encoded by appending newline tokens to patch rows. (HyperAI)
例如 patch grid:
原始:
1 | p1 p2 p3 |
展开成:
1 | p1 p2 p3 <NL> |
这样 LLM 后续仍知道:
哪些 token 属于同一行。
这是 Step3-VL 非常工程化的设计。
六、多个 crop 怎么融合?
ViT输出:
1 | Global Features |
然后 concat:
1 | +----------------+ |
得到:
1 | Visual Token Sequence |
七、Projector 压缩
然后进入 projector:
Step3-VL 使用:
two consecutive stride-2 layers
实现:
16× spatial downsampling (GitHub)
流程:
1 | Visual Features |
目的:
减少 token 数,方便送入 decoder。
八、最终送给LLM
最终:
1 | IMAGE TOKENS |
九、为什么是 728 和 504?
这两个数字本质上是token budget + batch效率 + patch grid整除性的折中。
如果 patch=14:
728
[
728 = 52 \times 14
]
504
[
504 = 36 \times 14
]
所以:
能完美整除 patch size,不需要 padding。
对应:
- global:52×52
- local:36×36
非常适合 GPU kernel 对齐和 batch 并行。
这通常不是巧合,而是工程优化结果。
十、和传统单图ViT的区别
| 方法 | 优点 | 缺点 |
|---|---|---|
| 单张高分辨率 | 信息完整 | token爆炸 |
| 下采样单图 | 便宜 | 丢细节 |
| Step3-VL multi-crop | 全局+局部兼顾 | crop调度更复杂 |
所以 Step3-VL 的核心思想可以概括成一句话:
用 global crop 保证“看懂整张图”,用 local crops 保证“看清细节”,再通过 batch-parallel ViT 编码实现高吞吐。
这也是它在:
- OCR
- GUI
- 文档理解
- Counting
- Grounding
这些任务上表现很强的重要原因之一。
Q:local crop是怎么切分的?其数量由什么决定?
Step3-VL 论文和代码里只公开了:
728×728 global + multiple 504×504 local crops
但是没有把 local crop 的具体切分算法(如 stride、多大 overlap、最多几个 crop)直接写死公开出来。不过从其 transformers processor 实现方式、multi-patch preprocessing,以及这类 VLM 的通用 crop pipeline,可以比较准确地推断它的机制。
结论先说:
Step3-VL 的 local crop 本质上是 adaptive tiling(自适应网格切分),crop 数量主要由:
- 原图分辨率
- 原图长宽比
- 最大视觉 token budget
- processor 允许的 max_num_patches / max_num_crops
共同决定。
一、local crop 怎么切?
Step3-VL 不是“目标检测式 crop”(不是先找 ROI 再裁)。
它更像:
uniform sliding-window tiling
即:
把原始高分辨率图像切成多个固定大小的窗口:
每个窗口:
1 | 504 × 504 |
比如原图:
假设:
1 | 2016 × 1512 |
会先在原图上建立一个 grid。
可以理解成:
数学上:
若:
原图:
[
W \times H
]
crop size:
[
C=504
]
那么理论 grid 数:
宽方向:
[
N_w = \lceil W / 504 \rceil
]
高方向:
[
N_h = \lceil H / 504 \rceil
]
总 crop:
[
N = N_w \times N_h
]
比如:
Case 1:1000×1000
宽:
[
\lceil1000/504\rceil = 2
]
高:
[
\lceil1000/504\rceil = 2
]
所以:
[
2\times2 =4
]
即:
1 | +---------+---------+ |
Case 2:2000×1000
宽:
[
\lceil2000/504\rceil = 4
]
高:
[
\lceil1000/504\rceil = 2
]
得到:
[
8 crops
]
这和很多 document VLM 是一致的。
二、是不是完全无重叠?
通常不是。
为了避免边界信息丢失,通常会加 overlap。
即 stride 可能不是:
[
504
]
而是:
比如:
[
420\sim480
]
这样 crop 之间有 5~20% overlap。
例如:
1 | crop1: [0,504] |
会重叠:
84 pixels。
论文虽然没写 overlap,但 multi-crop 来源参考的是 DINO-style crops。(ZNAK автошкола)
工程上几乎一定有 overlap,否则:
- 小字可能刚好切边
- OCR性能下降
- grounding 会不稳定
三、crop 数量由什么决定?
核心是:
1. 原图尺寸
这是最直接的。
图片越大:
crop 越多。
例如:
| 原图 | local crops |
|---|---|
| 800×800 | 4 |
| 1600×1600 | 16 |
| 2500×2500 | 25 |
近似:
[
N\propto \frac{WH}{504^2}
]
2. 长宽比
长图会产生更多 crop。
例如:
横向长图
4000×1000
可能:
1 | 8 × 2 =16 crops |
纵向文档
1000×4000
可能:
1 | 2 × 8 =16 crops |
所以:
文档类任务 crop 通常比普通照片更多。
四、真正限制数量的是 token budget
这是最关键的。
因为 Step3-VL 不可能无限切。
ViT token 数:
每个 local crop:
因为 patch=14:
[
504/14=36
]
所以:
每个 crop:
[
36^2 =1296
]
global:
[
52^2=2704
]
如果有 K 个 local:
总 token:
[
2704 + 1296K
]
比如:
如果:
K=8
总:
[
13072
]
如果:
K=16
总:
[
23440
]
token 很快爆炸。
所以 processor 一般会限制:
类似:
1 | max_num_crops = K_max |
或者:
1 | max_visual_tokens = T_max |
Step3-VL README 提到 inference 的 processor 默认启用 multi-patch preprocessing,和 vLLM/sglang 对齐。(Hugging Face)
因此:
真正 crop 数量通常是:
[
K = \min(K_{grid}, K_{budget})
]
即:
网格理论数量 vs token预算上限。
五、如果 grid 太多怎么办?
一般会:
策略1:降低 grid 密度
比如:
原来:
1 | 4×8 = 32 crops |
变成:
抽样:
1 | 2×4 = 8 crops |
策略2:优先覆盖全图
例如:
先保留:
- corners
- center
像这样:
1 | A---B---C |
只取:
1 | A C E G I |
保证 spatial coverage。
六、Step3-VL 的实际 pipeline(推断)
完整流程大概率是:
1 | Original Image |
七、在 vLLM 中对应什么?
你前面看过 Step3-VL 的 vLLM 实现。
Step3-VL 在 vLLM 中最终会把:
1 | [ |
通过类似:
finalize_encoder_cudagraph_output()
做 batch flatten,然后插回 <im_patch> placeholder。
所以:
crop 数量最终决定了:
placeholder 数量:
[
1 + K
]
即:
1 | <global><patch1><patch2>...<patchK> |
K 越大:
- encoder latency ↑
- projector latency ↑
- LLM prefill latency ↑
这就是 Step3-VL 要严格控制 crop 数的原因。
一句话总结:
Step3-VL 的 local crop 本质是“固定 504×504 的自适应网格切分”,数量先由原图尺寸/长宽比决定,再由视觉 token budget 做上限裁剪。
即:
[
\text{num_crops}
\min
\left(
\lceil W/504\rceil \cdot \lceil H/504\rceil,
K_{max}
\right)
]
这是目前最符合论文、processor 和部署实现的解释。