任意分辨率:Patch-n'-Pack 与 NaFlex 本节摘要:真实图像不是 224×224 的方块。小票是 9:16,图表是 16:9,医学扫描可能是 4096×4096,手机截图是 9:19.5。2024 年前 VLM 的回答——把一切都缩放到固定方块——丢掉了让 OCR、文档理解、高分辨率场景解析得以工作的信号。NaViT(Google,2023)证明可以把可变分辨率的 patch 用块对角掩码打包进一个 Transformer 批。Qwen2-VL 的 M-RoPE(2024)彻底丢掉了绝对位置表。LLaVA-NeXT 的 AnyRes 把高分辨率图切成「基础图 + 子图」。
本节摘要:真实图像不是 224×224 的方块。小票是 9:16,图表是 16:9,医学扫描可能是 4096×4096,手机截图是 9:19.5。2024 年前 VLM 的回答——把一切都缩放到固定方块——丢掉了让 OCR、文档理解、高分辨率场景解析得以工作的信号。NaViT(Google,2023)证明可以把可变分辨率的 patch 用块对角掩码打包进一个 Transformer 批。Qwen2-VL 的 M-RoPE(2024)彻底丢掉了绝对位置表。LLaVA-NeXT 的 AnyRes 把高分辨率图切成「基础图 + 子图」。SigLIP 2 的 NaFlex 变体(2025)现在是想用一个 checkpoint 服务所有宽高比的开源 VLM 的默认编码器。本节端到端实现 patch-n'-pack。
阅读完本节,你应当能够:
Transformer 期望一条序列。一个 batch 是一堆等长序列的堆叠。如果你的图是 224×224,每次都是 196 个 patch token,无需 padding,完事。在 224 上训练,在 224 上推理,再也不用想分辨率。
可现实不配合。文档是竖向(8.5×11 英寸,约 2:3)。图表截图是横向(16:9)。小票又高又窄(1:3)。医学影像动辄 2048×2048 或更大。手机截图是 1170×2532(0.46:1)。
2024 年前的三种选择及各自的失败:
2024~2025 年的答案:让 Transformer 在图像的原生分辨率上吃 patch,再想办法把异构 batch 打包进一条序列而不浪费算力。
NaViT(Dehghani 等人,2023)是证明这事在大规模上可行的论文。思路很机械:
三张图——336×336(576 token)、224×224(256 token)、448×336(768 token)——变成一条 1600 token 的序列,配一张 1600×1600 的块对角掩码。零 padding,零浪费,Transformer 处理任意宽高比。
NaViT 还在训练时引入了分数 patch 丢弃——跨 batch 随机丢 50% 的 patch——既正则又加速训练。SigLIP 2 继承了这一点。
LLaVA-NeXT 的 AnyRes 是务实的替代方案。给定一张高分辨率图与一个固定编码器(CLIP 或 SigLIP @ 336),把图切块:
672×672 的图配 2×2 网格加缩略图:4×576 + 576 = 2880 个视觉 token。贵但有效——LLM 既看到局部细节也看到全局上下文。
当你的编码器是冻结的、且只支持单一分辨率时,AnyRes 是首选。但大图上 token 数爆炸(1344×1344 配 4×4 网格是 9216 + 576 ≈ 9800 token,塞满 8k LLM 上下文的大半)。
Qwen2-VL 引入多模态旋转位置嵌入。它不用 NaViT 的分数位置,也不用 AnyRes 的子图加缩略图,而是每个 patch 携带一个三维位置(时间、高、宽)。query/key 的旋转处理任意 H、W 与时间长度。
M-RoPE 原生支持动态分辨率,无需重训。推理时你喂任意 H×W 图,patch 分词器产出 H/14 × W/14 个 token,每个 token 拿到自己的 (t=0, r=行, c=列) 位置,RoPE 按正确频率旋转注意力,搞定。Qwen2.5-VL 与 Qwen3-VL 沿用此路;InternVL3 的 V2PE 是同样思路,按模态做可变编码。
与 AnyRes 不同,M-RoPE 在原生分辨率下是 O(H×W/P²) token,没有乘法级的子图开销。与 NaViT 不同,它仍假设每次前向一张图;跨分辨率 batch 仍要在其上叠加 patch-n'-pack。
NaFlex 是 SigLIP 2 checkpoint 的原生灵活模式。单一模型在推理时服务多种序列长度(256、729、1024 token)。内部训练时用 NaViT 式 patch-n'-pack 与每 patch 的绝对分数位置。卖点:一个 checkpoint,推理时按任务挑 token 预算。
语义任务(分类、检索)用 256 token;OCR 或图表理解用 1024 token。无需重训。
块对角掩码是大多数实现绊倒的地方。对一条覆盖图 i=0..B-1、长度分别为 n_i、总长 N_total 的打包序列,形状 (N_total, N_total) 的掩码 M 在两个下标落在同一图的块时为 1,否则 0。可由累计长度列表构造:
offsets = [0, n_0, n_0+n_1, ..., N_total] M[i, j] = 1 当且仅当 存在 b 使 offsets[b] <= i < offsets[b+1] 且 offsets[b] <= j < offsets[b+1]
PyTorch 里 torch.block_diag 或显式 gather 一行就能写。FlashAttention 的变长路径(cu_seqlens)干脆跳过掩码,直接用累计长度张量在序列内部做注意力——典型 batch 下比稠密掩码快约 10 倍。
按任务挑策略:
2026 年的生产法则:按任务定一个 max-pixels 上限,在原生宽高比下编码到该上限,打包 batch,跳过 padding。Qwen2.5-VL 正是为这个旋钮暴露了 min_pixels 与 max_pixels。
code/main.py 用整数像素坐标为一批异构图实现 patch-n'-pack。它:
sum(n_i) 的序列。# 输入: 一组图像尺寸 [(H0, W0), (H1, W1), ...], patch 大小 P seqs = [] for (H, W) in image_sizes: gh, gw = H // P, W // P seqs.append(list(range(gh * gw))) # 每张图自己的 patch 序列 packed = concat(seqs) # 总长 sum(n_i) offsets = cumsum([0] + [len(s) for s in seqs]) # 块边界
def block_diagonal_mask(offsets, N_total): M = zeros((N_total, N_total)) for b in range(len(offsets) - 1): lo, hi = offsets[b], offsets[b+1] M[lo:hi, lo:hi] = 1 # 只有同一图块内为 1 return M # 其余为 0, 图间互不可见
💡 生产实现:别真建稠密的
N_total × N_total掩码。用 FlashAttention 的cu_seqlens(累计长度张量),它在内核里直接按块做注意力,大 batch 下快约 10 倍,显存也省一个数量级。
| 图像 | 方块缩放 336 | AnyRes 2×2+缩略图 | M-RoPE 原生 patch14 |
|---|---|---|---|
| 672×672(1:1) | 576 | 4×576+576=2880 | (48×48)=2304 |
| 1344×672(2:1) | 576(扭曲) | 4×576+576=2880 | 96×48=4608 |
| 600×1500 小票 | 576(严重扭曲) | 6×576+576=4032 | ~43×107=4601 |
跑一遍代码,这些掉出来的数字,就是 2026 年每个开源 VLM 都用 patch-n'-pack 的原因。
Siglip2Model 暴露 NaFlex 开关。min_pixels/max_pixels 暴露给用户,patch 分词器按原生宽高比产 token,RoPE 处理任意网格,无需位置表插值。LlavaNextProcessor 内置切图逻辑,从预定义网格集合选最优,缩略图 + 子图拼接;适合冻结单一分辨率编码器。cu_seqlens 取代稠密块对角掩码,大 batch 下快约 10 倍。工程取舍:要单 checkpoint 多预算用 NaFlex;要纯原生动态分辨率用 M-RoPE;编码器只能固定分辨率用 AnyRes;追求极致吞吐把所有路径接到 FlashAttention varlen。
本节产出 outputs/skill-resolution-budget-planner.md。给定一个混合宽高比的工作负载(OCR、图表、照片、视频帧)与总 token 预算,它挑出对的策略(NaFlex、AnyRes、M-RoPE 或固定方块),并输出每条请求的配置。给产品选型 VLM 时用这个 skill——它能避免那种悄悄把 token 数吹爆 10 倍、拖垮延迟预算的意外。
小票 token:一张 600×1500(1:2.5)的小票,patch 14 下原生分辨率多少 token?缩放到 336 方块后多少?实践中哪种丢 OCR 准确率更多?
块对角掩码:为四张长度 256、576、729、1024 的图建块对角掩码,验证注意力矩阵是 2585×2585,且非零项恰好是 256² + 576² + 729² + 1024²。
三策略对比:对一张 1792×896、patch 14 的图,对比:(a)缩放到 336 方块再编码;(b)AnyRes 2×1 + 缩略图;(c)M-RoPE 原生。哪种 token 最少?哪种保细节最多?
分数丢弃:实现分数 patch 丢弃——给定一条打包序列,随机均匀丢 50% token,并相应更新块对角掩码。测量掩码稀疏度变化。
读论文:读 Qwen2-VL 论文(arXiv:2409.12191)3.2 节,用两句话说明 min_pixels 与 max_pixels 各控制什么,为什么两个边界都重要。
cu_seqlens 跳过稠密掩码,快约 10 倍。下一节,我们将进入开源 VLM 配方——把前六节的概念综合起来,看 2026 年真正「什么起作用」:数据混合、分辨率方案、LLM 选择、训练阶段如何共同决定一个开源 VLM 的质量。