任意分辨率:Patch-n'-Pack 与 NaFlex


文档摘要

任意分辨率: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 把高分辨率图切成「基础图 + 子图」。

任意分辨率: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 把高分辨率图切成「基础图 + 子图」。SigLIP 2 的 NaFlex 变体(2025)现在是想用一个 checkpoint 服务所有宽高比的开源 VLM 的默认编码器。本节端到端实现 patch-n'-pack。

学习目标

阅读完本节,你应当能够:

  1. 把一批可变分辨率图像的 patch 打包进一条序列,并构造块对角注意力掩码。
  2. 针对给定任务,在 AnyRes 切图(LLaVA-NeXT)、NaFlex(SigLIP 2)、M-RoPE(Qwen2-VL)之间做选择。
  3. 不做缩放地为 OCR、图表、摄影计算 token 预算。
  4. 说清方块缩放的三种失败模式:文字被压扁、内容被裁掉、padding 上浪费 token。

一、问题与直觉

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 年前的三种选择及各自的失败:

  1. 缩放到固定方块(224×224 或 336×336)。压扁会扭曲文字与人脸;缩小会毁掉图表标签与 OCR 内容。LLaVA-1.5 前的标准做法。
  2. 裁剪到固定宽高比。丢掉大半图像,而选裁剪位置本身就是个视觉问题。
  3. pad 到最长边。修了扭曲,但竖图上 50%+ 的 token 浪费在 padding 上,所有 padding token 还得付平方级的注意力成本。

2024~2025 年的答案:让 Transformer 在图像的原生分辨率上吃 patch,再想办法把异构 batch 打包进一条序列而不浪费算力。

NaViT(Dehghani 等人,2023)是证明这事在大规模上可行的论文。思路很机械:

  1. 对 batch 里每张图,按选定 patch 大小(比如 14)算它的原生 patch 网格。
  2. 把每张图的 patch 展平成它自己的可变长度序列。
  3. 把所有图的 patch 拼成 batch 的一条长序列。
  4. 构造块对角注意力掩码,让图 A 的 patch 只在图 A 内部互相关注。
  5. 携带每 patch 的位置信息(2D-RoPE 或分数位置嵌入)。

三张图——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 继承了这一点。

AnyRes(LLaVA-NeXT)

LLaVA-NeXT 的 AnyRes 是务实的替代方案。给定一张高分辨率图与一个固定编码器(CLIP 或 SigLIP @ 336),把图切块:

  1. 从预定义布局集合里——(1×1)、(1×2)、(2×1)、(1×3)、(3×1)、(2×2)等——选最贴合图宽高比的一个。
  2. 把整图切成该网格,每块成为一张 336×336 子图。
  3. 同时产出一张缩略图:整图缩到 336×336,作为全局上下文 token。
  4. 每张子图过冻结的 336 编码器。拼接子图 token + 缩略图 token。

672×672 的图配 2×2 网格加缩略图:4×576 + 576 = 2880 个视觉 token。贵但有效——LLM 既看到局部细节也看到全局上下文。

当你的编码器是冻结的、且只支持单一分辨率时,AnyRes 是首选。但大图上 token 数爆炸(1344×1344 配 4×4 网格是 9216 + 576 ≈ 9800 token,塞满 8k LLM 上下文的大半)。

M-RoPE(Qwen2-VL)

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)

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 倍。

token 预算

按任务挑策略:

  • OCR/文档:1024~4096 token。SigLIP 2 NaFlex 用 1024,或 AnyRes 3×3 + 缩略图。
  • 图表与 UI:384448 原生下 7291024 token。Qwen2.5-VL 动态分辨率配 max_pixels 上限。
  • 自然照片:256~576 token 够用。下游 LLM 看得够清楚。在内容密集处花钱买 token。
  • 视频:空间池化后每帧 64128 token,28 FPS。第 17 节讲。

2026 年的生产法则:按任务定一个 max-pixels 上限,在原生宽高比下编码到该上限,打包 batch,跳过 padding。Qwen2.5-VL 正是为这个旋钮暴露了 min_pixelsmax_pixels

二、从零实现

code/main.py 用整数像素坐标为一批异构图实现 patch-n'-pack。它:

  • 接收一组 (H, W) 图像尺寸。
  • 在 patch 大小 14 下算每张图的 patch 序列长度。
  • 把它们打包成总长 sum(n_i) 的序列。
  • 构造块对角注意力掩码(稠密,便于看懂)。
  • 对比打包成本与方块缩放、AnyRes 切图。
  • 打印一个混合 batch(小票、图表、截图、照片)的 token 预算表。

patch 打包的伪代码

# 输入: 一组图像尺寸 [(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 倍,显存也省一个数量级。

三策略的 token 对比

图像 方块缩放 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 的原因。

三、框架对比

  • SigLIP 2 NaFlex:单一 checkpoint,推理时按任务选 256/729/1024 token,内部即 NaViT 式打包 + 分数位置;HuggingFace Siglip2Model 暴露 NaFlex 开关。
  • Qwen2-VL / Qwen2.5-VL(M-RoPE):min_pixels/max_pixels 暴露给用户,patch 分词器按原生宽高比产 token,RoPE 处理任意网格,无需位置表插值。
  • LLaVA-NeXT(AnyRes):LlavaNextProcessor 内置切图逻辑,从预定义网格集合选最优,缩略图 + 子图拼接;适合冻结单一分辨率编码器。
  • InternVL3(V2PE):M-RoPE 思路的变体,按模态做可变位置编码,支持像素与视频帧混合。
  • FlashAttention varlen:底层加速,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 倍、拖垮延迟预算的意外。

五、练习

  1. 小票 token:一张 600×1500(1:2.5)的小票,patch 14 下原生分辨率多少 token?缩放到 336 方块后多少?实践中哪种丢 OCR 准确率更多?

  2. 块对角掩码:为四张长度 256、576、729、1024 的图建块对角掩码,验证注意力矩阵是 2585×2585,且非零项恰好是 256² + 576² + 729² + 1024²

  3. 三策略对比:对一张 1792×896、patch 14 的图,对比:(a)缩放到 336 方块再编码;(b)AnyRes 2×1 + 缩略图;(c)M-RoPE 原生。哪种 token 最少?哪种保细节最多?

  4. 分数丢弃:实现分数 patch 丢弃——给定一条打包序列,随机均匀丢 50% token,并相应更新块对角掩码。测量掩码稀疏度变化。

  5. 读论文:读 Qwen2-VL 论文(arXiv:2409.12191)3.2 节,用两句话说明 min_pixelsmax_pixels 各控制什么,为什么两个边界都重要。

本节要点回顾

  1. 现实非方块:小票、图表、扫描、截图宽高比各异,方块缩放会扭曲文字、裁掉内容、padding 浪费。
  2. NaViT patch-n'-pack:把可变长度 patch 序列拼成一条,块对角掩码让图间互不可见,零 padding。
  3. 块对角掩码:按累计长度构造,生产用 FlashAttention 的 cu_seqlens 跳过稠密掩码,快约 10 倍。
  4. AnyRes:高分辨率图切固定大小子图 + 全局缩略图,适合冻结单一分辨率编码器,但大图 token 爆炸。
  5. M-RoPE:每 patch 携带(时间,行,列)三维旋转位置,处理任意 H/W/T,无需位置表,Qwen2/2.5-VL 沿用。
  6. NaFlex:SigLIP 2 单 checkpoint 服务 256/729/1024 token,推理时按任务挑预算。
  7. 分数 patch 丢弃:训练时随机丢 50%,既正则又加速。
  8. token 预算:OCR/文档 10244096,图表/UI 7291024,自然照片 256576,视频每帧 64128。
  9. 生产法则:按任务定 max-pixels,原生宽高比编码到上限,打包 batch,跳过 padding。
  10. 2026 标配:每个开源 VLM 都用某种 patch-n'-pack 变体,方块缩放已成历史。

下一节,我们将进入开源 VLM 配方——把前六节的概念综合起来,看 2026 年真正「什么起作用」:数据混合、分辨率方案、LLM 选择、训练阶段如何共同决定一个开源 VLM 的质量。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U