Tuna-2: Pixel Embeddings Beat Vision Encoders for Multimodal Understanding and Generation

1. 出发点 (Motivation)

这篇论文是 SenseNova-U1 在 GenEval 表里反复对比的 Tuna 系列的正式续作。背景跟 SenseNova-U1 几乎一样的诉求 — 干掉 VAE 和 vision encoder 做端到端 — 但 Meta+港大+滑铁卢这队用更"实验科学"的路数: 给出三个连续简化的变体, 做受控对比

具体要解决的痛点:

  1. 表示空间错位: 传统 UMM 用 CLIP/SigLIP 做理解, 用 VQ-VAE 做生成, 两套编码器目标完全不同, 训出来的特征互不兼容。
  2. VAE 信息瓶颈: 8× / 16× 压缩本质上是有损的, 把图先过 VAE 再过 vision encoder 等于两次降维, 对细粒度感知 (小物体、稠密细节) 是致命的。
  3. 预训练编码器的归纳偏置: 固定输入分辨率、抽象掉低层视觉细节、对 OCR / counting 等任务不友好。

核心问题: 能不能彻底丢掉预训练 vision encoder, 让模型直接从原始像素端到端学习, 并且反过来得到更强的视觉表征?

论文的答案是"可以, 而且 understanding 上更好" — 这跟传统直觉是相反的 (大家以为 SigLIP/DINO 的语义 prior 是宝贵的)。证据是一个三阶段对照实验:

变体VAERepr Encoder用什么编图
Tuna (基线)✓ (SigLIP 2)VAE latent 后再过 SigLIP
Tuna-R (中间)✓ (SigLIP 2)原始像素 → SigLIP
Tuna-2 (终态)原始像素 → Conv2d patchify

2. 方法 (Method) — 高中生友好 + 数学严谨

核心思想 (类比)

把"看懂图"和"画图"这两件事放进同一个 transformer 里, 有三种做法:

"裸眼"具体是: 一个 Conv2d + RMSNorm 就完了。没有预训练 VE, 没有 VAE, patch 之后直接当 token 喂给 LLM。

三阶段架构演化
Fig. 1 — 三个变体的简化过程。左到右逐步剥离编码组件: Tuna 有 VAE + Repr Encoder, Tuna-R 砍掉 VAE, Tuna-2 把 Repr Encoder 也砍掉只剩 Patchify Layer。右侧雷达图显示 Tuna-2 在多数 benchmark 上反而最优。

2.1 三阶段架构演化

从 Tuna 砍到 Tuna-2

共同 backbone: Qwen2.5-7B-Instruct。Diffusion head 是一个 10 层的 ModulatedAttentionBlock 串接 (类 DiT)。三个版本的差异只在视觉编码:

repo/tuna/models/vision/patch_embed.py:L14-L64 — 整个 SimplePatchEmbedding 就这么短

class SimplePatchEmbedding(nn.Module):
    """Simple Conv2D-based patchifier with RMSNorm.
    Used by the "no encoder" Tuna variant (variant C). Takes raw RGB pixel
    values, splits them into non-overlapping patches via a strided Conv2d,
    and applies RMSNorm on the channel dimension."""
    def __init__(self, patch_size: int = 16, hidden_size: int = 1152, in_channels: int = 3):
        super().__init__()
        # Conv2d patchify: (B, C, H, W) -> (B, hidden_size, H/p, W/p)
        self.patch_embedding = nn.Conv2d(
            in_channels=in_channels, out_channels=hidden_size,
            kernel_size=patch_size, stride=patch_size, bias=True,
        )
        self.norm = nn.RMSNorm(hidden_size)

    def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
        embeddings = self.patch_embedding(pixel_values)          # (B, h, H/p, W/p)
        b, c, h, w = embeddings.shape
        embeddings = embeddings.reshape(b, c, h * w).transpose(1, 2)  # (B, N, h)
        return self.norm(embeddings)

对比 SenseNova-U1 那个两层卷积 + 2D RoPE 的"近无损接口"更朴素 — Tuna-2 就一层 Conv2d (kernel=stride=16) + RMSNorm, 没有 2D 位置编码 (位置信息靠 LLM 的 1D RoPE 加 modality-aware 改造)。

2.2 像素空间 Flow Matching (x-prediction + v-loss)

生成端没有 VAE, 直接在像素空间做扩散去噪。沿用 JiT (Li & He, 2025) 的两个 trick: x-predictionv-loss reweighting

定义

Rectified flow 线性插值定义"含噪像素" (Eq. 1):

模型 \(\pi_\theta\) 直接预测干净图 \(x_\theta\) (Eq. 2):

其中 \(c\) 是条件 (T2I 时是文本, 编辑时是文本+源图)。

预测的 \(x_\theta\) 转换为速度 (Eq. 3):

训练时用 MSE 让 \(v_\theta\) 逼近真速度 \(v = x_1 - x_0\) (Eq. 4):

为什么是 "x-prediction + v-loss" 这个组合?

直接看 Eq. 3 可以推出: 在 v-loss 上做 MSE, 等价于在 x-prediction 上做\(1/(1-t)^2\) 权重的 MSE。代入:

这个权重的好处是: 当 \(t \to 1\) (接近干净图), 权重 \(\to \infty\), 强迫模型在低噪声端预测得更准 — 而这正是肉眼最敏感的细节区域。代码里这个权重显式写出来:

repo/tuna/models/jit_utils.py:L74-L90 — JiT-style x0 prediction loss with v-loss reweighting

def jit_x0_prediction_loss(x0_pred, x0_target, t=None, mask=None):
    """MSE on x0 with optional 1/(1-t)^2 weighting and a token mask."""
    loss = F.mse_loss(x0_pred, x0_target, reduction="none")
    if t is not None and t.shape[0] == loss.shape[0]:
        t_expand = t.view(-1, *([1] * (loss.ndim - 1)))
        weights = 1.0 / (1.0 - t_expand).clamp(min=5e-2) ** 2  # ← v-loss 重加权
        loss = loss * weights
    if mask is not None:
        loss = loss[mask.bool()].mean()
    else:
        loss = loss.mean()
    return loss

clamp(min=5e-2) 这里关键 — 防止 \(t \to 1\) 时权重爆炸, 把 \((1-t)\) 下限钉在 0.05, 等效最大权重 \(1/0.05^2 = 400\)。这是论文没提的数值稳定 trick

采样

推理时 Euler 求解 ODE: \(x_{t'} = x_t + (t' - t) v_\theta\)。代码里默认 50 步, 也支持 Heun。

2.3 Masking-based Visual Representation Learning (论文最关键的 trick)

这是 Tuna-2 区别于 NEO / SenseNova-U1 等同类工作的地方。问题: 像素空间维度太高, 模型容易学到"作弊捷径" (例如复制粘贴局部纹理而不理解语义)。

方案: 训练时随机 mask 一部分 patch, 用可学习的 mask token 替代:

Masking 训练方案
Fig. 3 — 同一套 masking 应用到两种任务: 生成时 模型在 noisy 输入上还要预测被 mask 区域的干净像素 (更难的去噪问题, 强迫 mask token 吸收上下文信息); 理解时 模型在部分 mask 的图上回答问题 (regularization, 防止依赖 superficial 视觉 shortcut)。

统一的 masking 操作, 两种角色:

  1. 生成任务: 让 mask token 必须凭借可见 patch 推断被遮的内容 → 类似 MaskGIT / DeTok 的"补全"训练。
  2. 理解任务: 让模型在不完整视觉输入下也能答对问题 → 类似 MAE / SigLIP 2 的"掩码语义学习"。

关键工程细节

repo/tuna/models/tuna_2_pixel.py:L397-L415 — masking 应用 (训练时, 在 patch embedding 之后)

# Apply masked image (DeTok-style) after patch embedding
if self.enable_mask_token and self.training:
    import random
    import numpy as np

    B, N, D = image_embeds.shape

    # Generate random mask for each sample in batch
    for batch_idx in range(B):
        mask_ratio = random.uniform(
            self.config.masked_image_ratio_min,    # 默认 0.0
            self.masked_image_ratio                 # 默认 0.75
        )
        num_masked = int(N * mask_ratio)

        if num_masked > 0:
            mask_indices = np.random.choice(N, num_masked, replace=False)
            image_embeds[batch_idx, mask_indices] = self.mask_token

注意: 实现是 per-sample Python for 循环 + np.random.choice — 不是向量化的。在大 batch + 高分辨率下应该是个 训练性能瓶颈, 但论文没提。

2.4 两阶段训练 Pipeline

整套训练只有两阶段, 跟 SenseNova-U1 的六阶段比简单很多:

550M image-text pairs 用于 Stage 1。Sequence length 固定 16K tokens/GPU。

Tuna-R 多一个 connector alignment stage (Stage 0): 只训 SigLIP-LLM 中间的 projection, 3K steps, lr=5e-4。Tuna-2 因为没 connector 跳过这一步 — 这是 architecture 简化带来的真实节省

2.5 与代码对照 (Cross-Read)

主模型 Tuna2Pixel 的核心 forward 流程: 文本 token + 图像 patch (可选 mask) → 拼成 sequence → Qwen2.5 LLM → 分两路: text logits 走 NTP loss, image hidden states 走 diffusion head 预测 x0:

repo/tuna/models/tuna_2_pixel.py:L316-L322 — JiT-style x0 prediction head

# Predict x0 (clean image) directly - JiT style
x0_pred = self.diffusion_head_b(last_hidden_states, time_embeds, modality_positions)
loss_disp = torch.tensor(0.0, device=device)
if image_latents is None:
    loss_ntp = next_token_prediction(logits, text_labels, self.config.llm_vocab_size)
    loss_flow = torch.tensor(0.0, device=device)
    return logits, loss_ntp, loss_flow, loss_disp

Loss 组合: NTP (cross-entropy) + JiT (mse on x0 with v-loss reweight):

repo/tuna/models/tuna_2_pixel.py:L325-L340 — 损失计算入口

from tuna.models.misc import jit_x0_prediction_loss
loss_ntp = next_token_prediction(logits, text_labels, self.config.llm_vocab_size)
# JiT-style loss: directly predict clean images (x0)
if t.shape[0] == x0_pred.shape[0]:
    t_for_loss = t
elif t.shape[0] == x0_pred.shape[0] * 2:
    t_for_loss = t[1::2]           # CFG: cond/uncond 交错, 取一半的 t
else:
    t_for_loss = None
loss_flow = jit_x0_prediction_loss(
    x0_pred, new_image_labels[: x0_pred.shape[0]], t_for_loss, image_masks
)
return logits, loss_ntp, loss_flow, loss_disp

注意中间那段处理 CFG 训练时的 t 交错: 当 batch 维度被翻倍 (cond + uncond), 只取一半的 t 用于 loss reweighting — 这是论文也没提的实现细节。

3. 结论 (Key Findings)

3.1 多模态理解 — Tuna-2 反超 Tuna-R

Understanding 评测
Tab. 1 — 在 9 个 VQA benchmark 上, Tuna-2 (7B) 全面超越同尺寸 native UMM (BAGEL/Janus-Pro/Show-o2 等), 同时在大多数任务上反超 Tuna-R (该模型有 SigLIP 2 编码器)。最右三列 V* / CountBench / VisuLogic 是 "pixel-centric" 细粒度任务, Tuna-2 的优势更明显。

关键数字 (Tuna-2 7B):

解释: 去掉 SigLIP 这种 "为分类训练" 的语义编码器, 模型反而能保留更多低层像素细节, 在数细节 / 找小物体的任务上反超。

3.2 图像生成 — Tuna-R 微胜, 但差距随数据规模缩小

Generation 评测
Tab. 2 — GenEval 上 Tuna 0.90, Tuna-R 0.88, Tuna-2 0.87。Tuna-R 在生成上稍强 (semantic prior 帮忙), 但 Tuna-2 也只差 0.01-0.02。DPG-Bench 上三者基本一致 (86.76 / 86.35 / 86.54)。

更有意思的是 LLM-as-judge 评测 (Tab. 3, GPT-5.4 / Claude Opus 4.7): Tuna-2 在 diversity 上显著最优 (48.4% / 41.9% 偏好率), quality 跟 Tuna-R 相当。生成 diversity 高可能是因为没有 SigLIP 这种"语义收敛器"压制了 mode collapse。

3.3 图像重建 — PSNR 32.80 / SSIM 0.93, 同分辨率超过 FLUX VAE

重建质量对比
Tab. 5 — 在 ImageNet val 上, 经过 lightweight 重建 finetune 后, Tuna-2 PSNR 32.80 / SSIM 0.93 @ 512²。同分辨率下接近 FLUX.1[dev] VAE 的 33.65 / 0.93, 远超其他 "unified tokenizer" (RAE 19.20 / 0.44, X-Omni 15.66 / 0.38)。说明像素空间表示能保留高质量重建信息, 不必非要走 VAE 通路。

3.4 Attention map 可视化分析 — Tuna-2 不被语言先验带跑偏

Attention map 可视化
Fig. 7 — 论文设计了几个"反直觉" prompt 测 Tuna-2 是否容易被语言先验骗。最有意思的是足球场案例: prompt 写"球员在踢东西", 图里有醒目的足球作为视觉干扰, 但实际被踢的是玻璃杯。LLaVA-OV / Penguin-VL / Qwen2.5-VL / Tuna 全部 attention 错聚焦到足球, 只有 Tuna-2 精确定位到杯子。证明 encoder-free 设计减少了对预训练语义先验的依赖。

3.5 Scaling 行为 — Tuna-R 早期领先, Tuna-2 晚期反超

Scaling curves
Fig. 6 — 在 OCRBench / MMVP / V* / GenEval 上随 training tokens 增长的曲线。Tuna-R 早期 (~10B tokens) 占优 — SigLIP 的语义 prior 提供初始 boost; 但 Tuna-2 在 ~30B tokens 之后反超。论文的判断: large-scale unified pretraining 更适合 monolithic 架构, encoder-based 的 inductive bias 反而成为天花板

生成端 (GenEval) 上 Tuna-R 一直略胜, 但 gap 随 token 数缓慢收敛, SFT 之后几乎并列。

4. 实现细节 (Implementation Notes)

4.1 核心架构超参

4.2 v-loss 重加权 + 数值稳定

代码里 jit_x0_prediction_loss 把 v-loss 等价转写成 x-loss × \(1/(1-t)^2\) 权重 (见 §2.2)。(1-t).clamp(min=5e-2) 把权重最大值钉在 400, 防止 \(t \to 1\) 时梯度爆炸 — 论文 §2 没提这个 clamp, 但它对训练稳定性非常关键

4.3 Masking 应用细节

4.4 训练 settings (按论文 §3.1)

项目Stage 1 PretrainingStage 2 SFT
Steps300K50K
LR1e-42e-5
OptimizerAdamWAdamW
节点数64
Seq length16K tokens/GPU16K tokens/GPU
数据550M I-T pairs (70% cap + 30% T2I) + 20% text-only (Nemotron)FineVision 13M + OmniEdit 2M + HQ T2I
Masking仅最后 40% 启用, p=50% 概率

4.5 论文 vs 代码的几处不一致

4.6 训练 loss 曲线观察 (Fig. 5)

不同 g:u ratio 的 loss 曲线
Fig. 5 + Tab. 6 — 测了 8g2u / 7g3u / 5g5u / 3g7u 等数据配比。7:3 是最优 trade-off (生成 loss 收敛快, 理解 loss 也没受影响)。Tab. 6 验证 masking 对两个变体都有提升, Tuna-2 收益更大 (+1.5-3 pt 在 MMVP/CountBench)。

5. 批判性总结 (Critical Assessment)

5.1 优点

5.2 不足 / 疑点

5.3 适用 vs 不适用

5.4 进一步阅读


讨论 / Comments

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