7AA7 Blog

多模态模型入门:视觉编码器与文本对齐

Kael UID:68
2026-10-01 14:22
14 4 0

ViT 把图像变成序列

卷积网络处理图像时保留二维结构,每一层的特征图还是 H×W×C。Vision Transformer 换了个思路:先把图像拆成块,让它在形式上变得和句子一样。

输入:  H × W × 3
切块:  P × P 大小的 patch,步长 P,共 N = (H/P) × (W/P) 块
展平:  每块 P·P·3 维
投影:  线性层映射到 d 维  →  patch embedding
拼接:  [CLS] + patch_1 ... patch_N  + 位置编码
输出:  Transformer encoder → N+1 个 d 维 token

切块后加位置编码是必需的,原因和文本一样——自注意力对输入置换等变,不知道每块来自图像的哪个位置。这里的位置编码通常是二维可学习的,或者用 2D 正弦编码。

这一步的意义在于同构:图像变成了长度 N+1 的 token 序列,和文本 token 在数据结构上完全一致,所以可以直接接在语言模型前面。整个多模态路线的可行性都建立在这个转换上。

代价是 token 数随分辨率平方增长。patch 大小 14 时:

分辨率 patch 数 注意力开销(相对)
336 576 1×
672 2304 16×
1344 9216 256×

一张 1344×1344 的图就是九千多个 token,比大多数文本请求长得多。

投影层:跨空间的翻译

视觉编码器输出的 d_v 维向量和语言模型的 d_model 维输入空间,是两码事。维度不同只是一方面,更要命的是几何结构不同:视觉特征空间里”猫”和”老虎”很近,词嵌入空间里它们的邻近关系由语言共现决定,两者对不上。直接拼接等于让语言模型去读一门没学过的语言。

投影层就是这本词典。形态从轻到重:

线性层最省事,一个 d_v × d_model 的矩阵。表达能力有限,适合视觉特征已经比较”语义化”的情况。

MLP(两层 + GELU 激活)是更常见的选择,参数量不大但能表达非线性映射。

重采样器(Perceiver Resampler / Q-Former 一类)做的事更多:它用一组固定数量的可学习 query 向量,对全部视觉 token 做交叉注意力,把变长的 N 个 token 压成固定的 M 个(常取 64、128、256):

Q: M 个可学习 query
K,V: N 个视觉 token
输出: M 个压缩后的视觉 token

这一步同时解决了两个问题:维度对齐,以及 token 数不可控。压缩后语言模型看到的视觉 token 数固定,不管输入分辨率多大,上下文占用都可预期。

训练时通常冻结视觉编码器和语言模型,只训这一层,让投影层学会把视觉特征”翻译”到语言模型能理解的位置。

对比学习对齐与生成式对齐

两种路线的目标和产物完全不同。

对比学习(CLIP 式)用图文配对数据训练两个编码器。一个 batch 里有 N 对图文,图像编码得到 I_1..I_N,文本编码得到 T_1..T_N,投影到同一空间后算相似度矩阵,对角线上是正样本,其余 N²-N 个是负样本,用 InfoNCE 双向优化:

loss = -log( exp(sim(I_i, T_i)/τ) / Σ_j exp(sim(I_i, T_j)/τ) )

学出来的是一个共享的语义空间,图像和文本可以直接比较。能做图文检索、零样本分类、聚类。能力边界很清楚:它学的是”这段描述和这张图匹不匹配”,不是”这张图该怎么描述”。用它做生成任务,得外挂一个解码器,效果通常不如端到端方案。

生成式对齐则直接把视觉 token 经投影后拼在文本 token 前面,用自回归损失训练模型续写:

输入: [视觉 token][文本 token: "描述这张图"]
目标: 模型续写描述内容

这条路能对话、能推理、能遵循指令,代价是训练成本高得多,而且对数据质量要求苛刻——需要大量高质量的图文指令对,噪声数据会直接反映到输出质量上。

现在的常见做法是两段式:用对比学习预训练好的视觉编码器(冻结,因为它的语义特征已经不错),接一个投影层,再接语言模型,训练时主要更新投影层并对语言模型做轻量微调。这样既省算力,又保留了生成能力。

分辨率的显存账

视觉 token 走的是和普通文本一样的注意力,显存和算力都按 O(N²) 走。N 随分辨率平方增长,于是总开销是分辨率的四次方关系——分辨率翻倍,视觉部分的注意力开销涨 16 倍。这还没算 KV Cache:一张图的 KV 缓存占用和几千个文本 token 相当。

几种常见应对:

动态分辨率(anyres)。预设一组候选网格,比如 (1×1) 到 (3×3) 个 336 的 tile,按原图长宽比选最贴合的那个,避免用正方形画布处理长条文档时浪费大量 padding token。padding 部分的 token 不携带信息却照常参与计算,这类浪费在文档扫描件上非常可观。

空间下采样。投影前把 2×2 的相邻 patch token 在通道维拼接,N 个变 N/4 个、通道数乘 4,再线性层压回 d 维。像素混洗(pixel shuffle)就是这种。信息损失换来 4 倍的 token 压缩。

分块 + 全局缩略图。大图切成多个 tile 分别编码,再附一张整体缩略图的编码。兼顾局部细节和全局结构,代价是 token 数仍然不小。

图文交错输入怎么拼

支持多图、图文混排的输入,实现上是在预处理阶段做序列拼接:

文本: "比较这两张图的差异:<image_1><image_2>"
展开: [文][视觉_1 的 M 个 token][文][视觉_2 的 M 个 token][文]

几个实现要点。位置编码要跨模态连续编号,图像内部的 token 编号要反映二维空间关系,否则模型分不清两张图的先后。注意力掩码的设计有分歧:文本部分保持因果(自回归生成的需要),图像内部的 token 之间通常用双向注意力,因为图像没有生成顺序的概念。

视频是同一套机制的直接扩展:抽帧后当成多图,加一个时间维度的位置编码。帧数 × 每帧 token 数增长很快,所以帧采样策略和 token 压缩在这里是必需品。

视觉 token 挤占上下文

一张高清图几千 token,多图对话一次就能吃掉几万的窗口。留给文本的预算被严重压缩,长文档理解任务尤其明显——图和文在抢同一个窗口。

几条缓解路径:压缩视觉 token 数(重采样到 64 或 128,多数问答任务下损失不大);按需编码(只在当前问题确实涉及图像时才插入视觉 token,历史轮次的图可以降级为文字描述);在 KV Cache 驱逐策略里优先淘汰视觉 token。

后一条有观测依据:深层网络里注意力权重会集中到少数视觉 token 上,大部分视觉 token 在后层几乎不被关注,说明存在可观冗余。按层做视觉 token 剪枝(浅层保留全部,深层只保留高权重的一小部分)能省下大量显存,而对输出影响有限。

实操中容易忽略的点

图像预处理必须和视觉编码器训练时完全一致:resize 的插值方法、归一化用的均值和标准差、通道顺序。差一点,特征就会整体偏移,表现为”模型看图说得牛头不对马脚”,而代码上看不出任何错误。

长宽比失真要小心。强行拉伸到正方形会让文字变形、物体比例失真,OCR 类任务准确率会明显下降。保持长宽比 + padding 通常更好,代价是多一些无效 token。

多图场景下顺序敏感。模型对先出现的图关注度通常更高,重要信息放后面可能被忽略。这不是保证成立的行为,但值得在多图任务里做 A/B 验证。

553Views
14Likes
4Comments
4Participants
9 minread time

Comments (4)

  • 硅屿 · 2026-10-01 16:52

    这个视角挺清奇,虽然我不完全同意。

    Reply
  • 沈叙白 · 2026-10-02 01:22

    照这个思路优化了一下,接口从 800ms 降到 120ms。

    Reply
  • 何遇 · 2026-10-02 10:25

    正好在写方案,直接引用了这里的结构,谢谢。

    Reply
  • 凛川 Wave · 2026-10-02 19:41

    这个方案我们评估过,最后没上,主要卡在运维成本。

    Reply