Ferret-v2: An Improved Baseline for Referring and Grounding with Large Language Models

Ferret-v2 vs Ferret 在指代/定位和各基准上的对比
Fig. 1(左)定性:小区域里的文字"Great",Ferret 看成 "ABTO"(分辨率不够,认错),Ferret-v2 正确读出;"夏天用什么降温",Ferret 只找到一辆卡车,Ferret-v2 准确框出多台空调。(右)定量:Ferret-v2(深色)在 SA-Refer、RefCOCO/+/g、Ferret-Bench、VQAv2、TextVQA 上全面超过 Ferret(浅色),尤其在需要看清细节的小物体/OCR 任务上。(论文 Fig. 1,均为 7B)

上一篇 Ferret 解决了"圈哪问哪"的输入,但它的眼睛被卡在 336×336——小物体、屏幕文字、密集场景一律糊成一团。Ferret-v2 干的就一件事:在不丢全局推理能力的前提下,把分辨率拉上去。

1. 出发点 (Motivation)

Ferret 用混合区域表示统一了 referring 和 grounding,很强。但有个硬伤:它用的视觉编码器是预训练好的 CLIP-ViT-L/14,输入分辨率固定在 336×336(很多 MLLM 甚至 224)。这个分辨率一卡,精细视觉理解就废了——

但简单粗暴地提分辨率又有代价:已有的"任务专用高分辨率 MLLM"要么过于复杂,要么在传统 MLLM 基准上表现反而变差。于是论文盯住一个核心矛盾:

怎么让 MLLM 在精细视觉任务上变强,又不牺牲它的全局推理能力?

Ferret-v2 从三个方向回答:更高分辨率的缩放策略多粒度视觉编码训练配方。选 Ferret 当底座,因为它有两个别人没有的优点——指代/定位互益、且支持自由形状区域。

2. 方法 (Method)

Ferret-v2 架构:CLIP 编全局 + DINOv2 编局部块,channel-wise 融合后过视觉采样器
Fig. 2 — Ferret-v2 架构。全局低分辨率图走 CLIP 编码器 + Projector_1 得 $H_g$(抓语义);局部高分辨率子块走 DINOv2 编码器 + Projector_2 得 $H_{l1..lN}$(抓细节)。两路特征图按空间排布合并、上采样对齐后 channel-wise 相加(图中 ⊕)成高分辨率特征,既喂给 LLM 当视觉 token,又喂给空间感知视觉采样器抽区域特征。输出端照旧在名词后吐坐标。(论文 Fig. 3)

2.1 先做对照实验:any-resolution 完胜 direct upsampling

提分辨率有两条主流路线,论文先做了控制实验比个高下:

结论很干脆:any-resolution 在 ROC / REC / TextVQA / Ferret-Bench 四个任务上全面胜出。论文给的解释很关键:

direct upsampling 强迫 ViT 适应它从没见过的高分辨率(token 长度暴增),而微调数据(1.3M)远小于 CLIP 预训练数据(400M),会破坏预训练知识。any-resolution 把大图切成子块,每个子块的 token 长度和 CLIP 预训练时几乎一样,所以预训练知识保住了。

选最优网格的逻辑——在"有效分辨率最大"和"浪费分辨率最小"之间取舍——代码里就是一段直白的遍历:

repo/ferretui/ferretui/mm_utils.py:L13-L40 — 选择最贴合原图、浪费最少的网格分辨率(any-res 核心)

def select_best_resolution(original_size, possible_resolutions):
    original_width, original_height = original_size
    best_fit = None
    max_effective_resolution = 0
    min_wasted_resolution = float('inf')
    for width, height in possible_resolutions:
        scale = min(width / original_width, height / original_height)
        downscaled_width, downscaled_height = int(original_width * scale), int(original_height * scale)
        effective_resolution = min(downscaled_width * downscaled_height, original_width * original_height)
        wasted_resolution = (width * height) - effective_resolution
        if effective_resolution > max_effective_resolution or \
           (effective_resolution == max_effective_resolution and wasted_resolution < min_wasted_resolution):
            max_effective_resolution = effective_resolution
            min_wasted_resolution = wasted_resolution
            best_fit = (width, height)
    return best_fit

切块时,总是把一张缩小的全局图拼在子块前面([global] + patches),这样模型同时拿到"全局语义"和"局部细节":

repo/ferretui/ferretui/mm_utils.py:L138-L153 — 选最优分辨率 → 切块 → 全局图拼在最前

best_resolution = select_best_resolution(image.size, possible_resolutions)
image_padded = resize_and_pad_image(image, best_resolution, is_pad=False)
patches = divide_to_patches(image_padded, processor.crop_size['height'])
...
image_original_resize = image.resize((processor.size['shortest_edge'], processor.size['shortest_edge']))
image_patches = [image_original_resize] + patches      # 全局缩略图 + 高分辨率子块

⚠️ 这段 any-resolution 代码来自仓库里的 Ferret-UI 模块(同组、同期、共享 any-res 配方)。Ferret-v2 自己的双编码器(CLIP+DINOv2)+ 三阶段训练代码并未在 apple/ml-ferret 开源——详见 §4。

2.2 多粒度视觉编码:CLIP 管语义,DINOv2 管细节

切块后冒出一个新问题:全局图和局部块的"粒度"不一样——全局图看到整个场景但粗糙,每个局部块只看到一部分但精细。用同一个编码器硬编,效果打架。

Ferret-v2 的解法:两路不同的编码器各管一摊

\[F_g = \text{CLIP}(I_g); \qquad F_{li} = \text{DINO}(I_{li}), \quad I_{li} \in \{I_{l1}, ..., I_{lN}\}\]
\[H_g = \text{MLP}_g(F_g); \qquad H_{li} = \text{MLP}_l(F_{li})\]

—— 翻译:全局低分辨率图 I_g 交给 CLIP(F_g),局部高分辨率块 I_li 交给 DINOv2(F_li);两路各自再过一个独立的 MLP 投影层。为什么分工:CLIP 用图文对比学习,擅长抓"这张图讲了啥"的语义,但忽略像素细节;DINOv2 用自监督(图级+块级),擅长抓形状、纹理这种局部细节。一个看大局,一个看细节。

2.3 any-resolution referring:两路特征逐通道相加

区域指代要靠空间感知视觉采样器(沿用 Ferret)抽连续特征。但高分辨率下,光靠全局图的特征抽不准小物体。Ferret-v2 把全局语义和局部细节融合后再抽:先把局部块特征图按原空间排布拼成大图 \(H_l'\),把全局特征上采样到同尺寸 \(H_g'\),然后逐通道相加:

\[H_l' = \text{Concat}\{H_{l1}, ..., H_{lN}\}, \quad H_g' = \text{Upsample}(H_g), \quad H_a = H_l' + H_g'\]

—— 翻译:把"局部细节大图"和"上采样后的全局语义图"在每个像素位置、每个通道上直接加起来,得到一张既有强语义、又有局部细节的高分辨率特征图 H_a。再把 H_a 喂给视觉采样器抽区域特征。说白了:让每个位置同时记住"我是什么(语义)"和"我长什么样(细节)"。

合并的工程实现就是按网格形状 reshape、再按空间排布展平拼接(这段在 Ferret-UI 模块里是 CLIP 单路版本):

repo/ferretui/ferretui/model/ferret_arch.py:L618-L638 — 局部块特征按网格排布合并,再与全局特征拼接

if image_aspect_ratio == 'anyres':
    num_patch_width, num_patch_height = get_anyres_image_grid_shape(
        image_sizes[image_idx], self.config.image_grid_pinpoints, self.get_vision_tower().config.image_size)
    image_feature = image_feature.view(num_patch_height, num_patch_width, height, width, -1)
...
image_feature = image_feature.permute(0, 2, 1, 3, 4).contiguous()
image_feature = image_feature.flatten(0, 3)
image_feature = torch.cat((base_image_feature, image_feature), dim=0)   # 全局特征 + 合并后的局部特征

2.4 三阶段训练:coarse-to-fine

三阶段训练:图文对齐 → 高分辨率密集对齐 → 意图增强指令微调
Fig. 3 — 三阶段"由粗到精"训练(❄️=冻结,🔥=可训)。Stage I:低分辨率图文对齐,只训 Projector_1,冻结编码器和 LLM。Stage II(新增):高分辨率密集对齐,放开两个 projector + 视觉采样器,冻结编码器和 LLM。Stage III:意图增强指令微调,编码器、projector、采样器、LLM 全部放开。(论文 Fig. 4)

相比 Ferret 的两阶段(图文对齐 + 指令微调),v2 在中间插了一个全新的 Stage II:

3. 结论 (Key findings)

① 小物体指代暴涨。 指代物体分类 (ROC),Ferret-v2-7B vs Ferret-7B:

数据集 类型 Ferret-7B Ferret-v2-7B
LVIS Box 79.42 86.59
LVIS Free-form 69.77 76.13
SA-refer(野外小物体) Box 62.99 68.83
SA-refer Free-form 57.74 62.07

SA-refer 是专门用 SA-1B 高分辨率图 + 小物体标注做的"in-the-wild"测试集——v2 在这上面领先最多,正是高分辨率的功劳。

② 定位逼近专用检测器。 REC (Acc@0.5),Ferret-v2-7B 在 RefCOCO val 92.79(Ferret-7B 87.49)、RefCOCOg val 89.42(83.93),超过 Qwen-VL-7B、SPHINX-2k、MiniGPT-v2,已接近专用检测器 Grounding-DINO-L 的水平。

③ Ferret-Bench 大幅提升。 平均 75.6 vs Ferret-7B 的 64.5,指代描述、指代推理、对话中定位三项全面提高。

④ any-resolution > direct upsampling 被实证。 控制实验(Fig. 2)显示 any-res 在四个任务上一致更优;且 any-res 下放开编码器训练总是更好,而 direct upsampling 下冻结编码器有时反而更好——印证"别破坏 CLIP 预训练知识"的判断。

4. 实现细节 (Implementation notes)

① v2 的核心代码(双编码器 + 三阶段)未开源。 apple/ml-ferret 仓库只有 Ferret v1Ferret-UI 两个模块,没有 Ferret-v2 的 CLIP+DINOv2 双编码器、§2.3 的逐通道融合 \(H_a = H_l' + H_g'\)、以及三阶段训练脚本。本文的 any-resolution 代码引用自同组同期的 Ferret-UI 模块(共享 any-res 配方,但它是 CLIP 单编码器,没有 DINOv2 那一路)。所以 §2 的双编码器部分只能对照论文公式,代码层面无法逐行验证——按诚实原则在此标注。

② any-resolution 的 token 上限是 1280。 论文脚注:token 数随输入分辨率动态变化,但最大 1280;选 448 作为目标分辨率是权衡计算开销。网格配置 {1x1, 1x2, 1x3, 1x4, 1x5, 1x6, 2x2, 2x3} 及其转置,最多 6 格。

③ 空间感知视觉采样器直接复用 v1。 v2 没动采样器(FPS+KNN+融合+池化那套),只是改了喂给它的输入——从"全局特征"变成"融合后的高分辨率特征 \(H_a\)"。采样器实现见 Ferret 精读的 ferret_arch.py:GeoRegionSampler

④ DINOv2 projector 从 CLIP projector 初始化。 这是个稳定性 trick(§3.4 Stage II):新加的 DINOv2 路 projector 不是随机初始化,而是拷贝 CLIP projector 的权重起步,避免训练早期两路特征尺度差太多。

⑤ "数据统一"靠外部模型伪标注。 Stage III 用 GLIPv2(开放词表检测器)和一个 OCR 模型 把 VQA/OCR 数据里的名词/文字自动框出来,变成带坐标的训练数据。好处是把纯文本任务和区域任务对齐;隐患是伪标注的误差会传进训练(见 §5)。

⑥ 投影层从 linear 升级为 2 层 MLP。 §3.2 提到,相比 Ferret 的单层 linear projector,v2(及其对照实验)统一改用两层 MLP——这是 LLaVA-1.5 带起来的小改动,但对高分辨率下的对齐有帮助。

5. 批判性总结 (Critical assessment)

Strengths

Limitations / open questions

When to use / not use

谱系衔接:Ferret → Ferret-v2 → Ferret-UI

把三篇放进一条线看最清楚。

Further reading

讨论 / Comments

评论托管在本仓库的 GitHub Discussions, 需 GitHub 账号。