现代多模态大模型的核心基础:Unified Self-Attention

现代多模态大模型的核心基础:Unified Self-Attention

作者:吴佳浩

撰稿时间:2026-05-26

测试环境:

  • GPU:RTX 5090(32GB VRAM)
  • Memory:96GB RAM
  • OS:Windows 11
  • Models:Qwen3-VL / Qwen3-VL-Reranker / Qwen3-VL-Embedding

引言

面向工程师与研究者的体系化分析。 不止讨论「是什么」,更推导「为什么只能这样」。 本文将从数学、架构、系统与工程实现多个层面,深入分析 Unified Self-Attention 在现代多模态大模型中的核心作用。

注:前半部分涉及较多 Transformer 与 Attention 底层原理,信息密度较高。


目录

  1. Self-Attention:从公式到因果
  2. [Cross-Attention 的结构性缺陷](#Cross-Attention 的结构性缺陷 "#%E4%BA%8Ccross-attention-%E7%9A%84%E7%BB%93%E6%9E%84%E6%80%A7%E7%BC%BA%E9%99%B7%E6%8E%A8%E5%AF%BC%E8%80%8C%E9%9D%9E%E7%BB%93%E8%AE%BA")
  3. [Unified Self-Attention:为什么必然走向这里](#Unified Self-Attention:为什么必然走向这里 "#%E4%B8%89unified-self-attention%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BF%85%E7%84%B6%E8%B5%B0%E5%90%91%E8%BF%99%E9%87%8C")
  4. 现代多模态的五层理解框架
  5. [SOTA 工程难点:真正在卷的是什么](#SOTA 工程难点:真正在卷的是什么 "#%E4%BA%94sota-%E5%B7%A5%E7%A8%8B%E9%9A%BE%E7%82%B9%E7%9C%9F%E6%AD%A3%E5%9C%A8%E5%8D%B7%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88")
  6. [为什么 Unified 适合 Agent:因果链推导](#为什么 Unified 适合 Agent:因果链推导 "#%E5%85%AD%E4%B8%BA%E4%BB%80%E4%B9%88-unified-%E9%80%82%E5%90%88-agent%E5%9B%A0%E6%9E%9C%E9%93%BE%E6%8E%A8%E5%AF%BC")
  7. [Python 代码:从公式到实现](#Python 代码:从公式到实现 "#%E4%B8%83python-%E4%BB%A3%E7%A0%81%E4%BB%8E%E5%85%AC%E5%BC%8F%E5%88%B0%E5%AE%9E%E7%8E%B0")
  8. 行业格局与工程取舍
  9. 总结

一、Self-Attention:从公式到因果

1.1 公式本身

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Attention ( Q , K , V ) = softmax  ⁣ ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^T}{\sqrt{d_k}}\right)V </math>Attention(Q,K,V)=softmax(dk QKT)V

这个公式经常被背诵,但很少被推导。理解它需要拆成三个问题:

为什么用点积衡量相似度?

两个向量的点积 <math xmlns="http://www.w3.org/1998/Math/MathML"> q ⋅ k = ∣ q ∣ ∣ k ∣ cos ⁡ θ q \cdot k = |q||k|\cos\theta </math>q⋅k=∣q∣∣k∣cosθ, <math xmlns="http://www.w3.org/1998/Math/MathML"> cos ⁡ θ \cos\theta </math>cosθ 正是方向相似度的自然度量。相比欧氏距离,点积天然适合高维空间,且可矩阵化并行计算。

为什么除以 <math xmlns="http://www.w3.org/1998/Math/MathML"> d k \sqrt{d_k} </math>dk ?

随机初始化时, <math xmlns="http://www.w3.org/1998/Math/MathML"> q q </math>q 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 每个维度服从 <math xmlns="http://www.w3.org/1998/Math/MathML"> N ( 0 , 1 ) \mathcal{N}(0,1) </math>N(0,1),则点积 <math xmlns="http://www.w3.org/1998/Math/MathML"> q ⋅ k = ∑ i = 1 d k q i k i q \cdot k = \sum_{i=1}^{d_k} q_i k_i </math>q⋅k=∑i=1dkqiki 的方差为 <math xmlns="http://www.w3.org/1998/Math/MathML"> d k d_k </math>dk,标准差为 <math xmlns="http://www.w3.org/1998/Math/MathML"> d k \sqrt{d_k} </math>dk 。不做缩放,高维向量的点积会进入 softmax 的梯度饱和区,梯度消失,训练崩溃。

为什么 softmax 之后乘 V?

softmax 把分数变成「在所有 Token 上的概率分布」,再乘 V 就是对所有 Token 的 Value 做加权平均。本质是:我关注谁,就从谁那里取多少信息

符号 含义 直觉
Q (Query) 当前 Token 的「提问」 我在找什么?
K (Key) 每个 Token 的「标签」 我能回答什么?
V (Value) 每个 Token 的「内容」 我实际携带什么信息?

1.2 复杂度的代价

Self-Attention 的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N 2 ⋅ d ) O(N^2 \cdot d) </math>O(N2⋅d),空间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N 2 ) O(N^2) </math>O(N2),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> N N </math>N 是序列长度。

这在纯文本时代尚可控( <math xmlns="http://www.w3.org/1998/Math/MathML"> N N </math>N 通常在数千级),但在多模态场景下将成为核心工程瓶颈------这个问题将在第五节展开。

1.3 全局连接是关键

graph LR I["I"] <--> love["love"] I <--> deep["deep"] I <--> learning["learning"] love <--> deep love <--> learning deep <--> learning

CNN 的感受野是局部的,RNN 的信息传递是顺序衰减的,而 Self-Attention 中每个 Token 与所有 Token 直接相连,没有距离衰减,没有信息瓶颈。这是 Transformer 超越 CNN/RNN 的根本原因,也是后来多模态统一的基础。


二、Cross-Attention 的结构性缺陷:推导而非结论

第一代多模态(Flamingo、BLIP-2)的做法是:用 Vision Encoder 提取图像特征,通过 Cross-Attention 注入 LLM。

graph TD A["🖼 Image"] --> B["Vision Encoder (ViT)"] B --> C["Visual Features\n(外部特征,非 Token)"] D["📝 Text Tokens"] --> E["LLM Layers"] C -->|"Cross-Attention\n文本 Query 图像 K/V"| E E --> F["Output"]

2.1 Cross-Attention 的工程价值

在讨论缺陷之前,需要承认它的工程合理性:

  • 冻结 LLM:只训练 ViT + Q-Former/Adapter,参数量小一到两个量级
  • 低算力门槛:7B LLM + 轻量 Projector,消费级 GPU 可运行
  • 模块化设计:视觉和语言模块独立迭代
  • 适合资源受限场景:边缘设备、实时推理

这些优势在特定业务场景下仍然成立,不应被简单否定。

2.2 OCR 弱的真实原因:一条因果链

「Cross-Attention OCR 弱」是结论。真正的问题是:为什么

OCR 本质是 字符级 spatial reasoning,需要:

  1. 二维空间关系:「这个字符在第 3 行第 5 列」
  2. 行列结构感知:上下行的对齐关系
  3. 局部区域 Token alignment:字符边界与图像区域的精确对应
  4. 跨层持续的视觉推理:浅层定位 → 深层语义,需要多层共同参与

Cross-Attention 的结构决定了:

bash 复制代码
图像特征只在特定 Cross-Attention 层注入
↓
深层 Self-Attention 层看不到原始视觉细节
↓
字符空间关系无法在深层持续参与推理
↓
长文本 OCR、表格 OCR、小字识别全部退化

而 Unified 架构中,图像 Token 从第 1 层到第 N 层全程与文本 Token 双向交互,空间关系信息不会在中途丢失。

2.3 GUI 理解弱的根本原因

GUI 理解需要回答:「这个按钮在坐标 (x, y),它的文字是『提交』,点击后会触发表单提交」。

这需要三种信息的同时融合

  • 空间位置(视觉)
  • 语义标签(文字)
  • 功能逻辑(推理)

Cross-Attention 中,视觉和文本走的是两条独立的信息流,只在 Cross-Attention 层短暂交汇。深层推理时,文本 Token 的 Self-Attention 已经看不到完整的视觉上下文。这就是 GUI Agent 用 Cross-Attention 模型容易在复杂界面上崩溃的原因。

2.4 模态不平等的数学表达

在 Cross-Attention 中:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> out = softmax  ⁣ ( Q text K img T d k ) V img \text{out} = \text{softmax}\!\left(\frac{Q_{\text{text}} K_{\text{img}}^T}{\sqrt{d_k}}\right) V_{\text{img}} </math>out=softmax(dk QtextKimgT)Vimg

信息流是单向的:文本查询图像。图像永远是被查询的对象,永远不能主动关注文本中的任何 Token。

而 Unified Self-Attention 中,注意力矩阵覆盖所有 Token 对:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> out = softmax  ⁣ ( Q [ i m g ⊕ t e x t ] K [ i m g ⊕ t e x t ] T d k ) V [ i m g ⊕ t e x t ] \text{out} = \text{softmax}\!\left(\frac{Q_{[img \oplus text]} K_{[img \oplus text]}^T}{\sqrt{d_k}}\right) V_{[img \oplus text]} </math>out=softmax(dk Q[img⊕text]K[img⊕text]T)V[img⊕text]

图像 Token 可以查询文本,文本 Token 可以查询图像,图像 Token 可以互相查询。这是真正的对称性


三、Unified Self-Attention:为什么必然走向这里

3.1 核心思路

把图像也变成 Token,让所有模态在同一个 Attention 空间中平等交互。

graph TD A["🖼 Image"] --> B["ViT Encoder"] B --> C["Patch Embeddings"] C --> D["Linear Projector\n维度对齐 → d_model"] D --> E["Image Tokens\n(B, T_img, d_model)"] F["📝 Text"] --> G["Text Tokenizer"] G --> H["Text Tokens\n(B, T_text, d_model)"] E --> I["Concat\n(B, T_img + T_text, d_model)"] H --> I I --> J["Unified Transformer Stack\n所有 Layer 全程双向交互"] J --> K["Output"]

3.2 Projector 的必要性

ViT 的输出维度(如 1024)通常与 LLM 的 d_model(如 4096)不同,需要一个线性变换层对齐。这个 Projector 看似简单,实际承担了重要职责:

  • 维度对齐:使图像 Token 能直接与文本 Token 拼接
  • 语义空间对齐:将视觉语义空间映射到语言语义空间
  • 参数高效:通常只是一个线性层或两层 MLP,但训练时它是信息融合的关键瓶颈

3.3 两代架构的本质区别

graph LR subgraph old["第一代:Cross-Attention(单向)"] T1["Text Token"] -->|"Query"| CA["Cross Attn"] I1["Image Feature"] -->|"Key / Value"| CA end subgraph new["第二代:Unified Self-Attention(全双向)"] TK["Text Token"] <-->|"双向对称" | IK["Image Token"] IK <--> IK2["Image Token"] TK <--> TK2["Text Token"] end
维度 Cross-Attention Unified Self-Attention
信息流方向 单向:Text → Image 全双向:All ↔ All
图像参与深度 仅 Cross-Attn 层 全部 Transformer 层
OCR / GUI 空间关系中途丢失 全程持续参与推理
训练成本 低(可冻结 LLM) 较高(全参数训练)
适用场景 低资源、轻量部署 高精度、复杂推理

四、现代多模态的五层理解框架

理解 Unified 多模态,需要五个层次递进,跳过任何一层都会形成误解。

第一层:直觉

LLM 只认识 Token。Transformer 的本质是序列建模器,它不知道「图像」是什么,只知道处理 Token 序列。

因此,让 LLM 理解图像,唯一的路径是把图像变成 Token。

第二层:工程

图像如何 Token 化?ViT(Vision Transformer)给出了答案:

  1. 将图像切成固定大小的 Patch(如 16×16 像素)
  2. 每个 Patch 线性嵌入为一个向量
  3. 加上位置编码,得到 Image Token 序列

一张 224×224 的图像,用 16×16 的 Patch,得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 224 / 16 ) 2 = 196 (224/16)^2 = 196 </math>(224/16)2=196 个 Token。更高分辨率会指数级增加 Token 数量,这直接导致第四层的问题。

第三层:数学

Attention 矩阵为什么天然支持模态融合?

将 Image Token 和 Text Token 拼接后,注意力矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> A ∈ R ( T i + T t ) × ( T i + T t ) A \in \mathbb{R}^{(T_i + T_t) \times (T_i + T_t)} </math>A∈R(Ti+Tt)×(Ti+Tt) 自然分解为四个子矩阵:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> A = [ A i i A i t A t i A t t ] A = \begin{bmatrix} A_{ii} & A_{it} \\ A_{ti} & A_{tt} \end{bmatrix} </math>A=[AiiAtiAitAtt]

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> A i i A_{ii} </math>Aii:Image-to-Image attention(图像内部空间关系)
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> A t t A_{tt} </math>Att:Text-to-Text attention(文本内部语义关系)
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> A i t A_{it} </math>Ait:Image-to-Text attention(图像查询文本)
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> A t i A_{ti} </math>Ati:Text-to-Image attention(文本查询图像)

这四个子矩阵同时被优化,不需要任何额外设计。模态融合是 Self-Attention 的自然属性,不是额外的工程 trick。

第四层:系统

Token 爆炸导致 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N 2 ) O(N^2) </math>O(N2) 复杂度危机:

输入类型 Token 数量(典型值)
纯文本(1K 字) ~500
单张图像(224px) 196
单张图像(448px) 784
单张图像(高分辨率) 1024~4096
1 秒视频(30fps,每帧 576 token) ~17,280
1 分钟视频 ~1,036,800

当 <math xmlns="http://www.w3.org/1998/Math/MathML"> N = 17280 N = 17280 </math>N=17280,Attention 矩阵大小为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1728 0 2 ≈ 3 × 1 0 8 17280^2 \approx 3 \times 10^8 </math>172802≈3×108,单次前向传播在 A100 上需要约 120GB 显存。这在工程上完全不可行。

这就是为什么 Token CompressionSparse Attention 成为现代多模态的核心工程问题。

第五层:行业

GPT-4o 为什么必须做 Token Compression?因为视频理解是刚需,但原始 Video Token 数量使得 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N 2 ) O(N^2) </math>O(N2) 的 Attention 根本无法部署。所以大厂真正卷的不是「要不要 Unified」,而是「如何在 Unified 框架内压缩到可以工程落地的 Token 数量,同时保留足够的视觉信息」。


五、SOTA 工程难点:真正在卷的是什么

Unified 方向已经是共识,真正的差距在工程细节。

5.1 Token Compression:核心工程挑战

问题:图像 Token 数量远超文本,视频更是指数级膨胀,直接导致推理成本不可接受。

主流方案对比

方案 思路 优点 代价
Average Pooling 相邻 Token 平均池化 简单,无参数 丢失细节,对 OCR 有损
Q-Former (BLIP-2) 用可学习 Query 提取固定数量 Token 压缩率高,可控 固定 Query 数量,灵活性差
Perceiver Resampler Cross-Attn 降采样到固定长度 端到端可学习 训练复杂
Token Merging (ToMe) 合并相似 Token 自适应,保留差异区域 需要在推理时动态计算
Dynamic Compression 根据内容重要性动态保留 最优信息保留 工程实现复杂
python 复制代码
# Token Merging 的核心思路(简化版)
import torch

def token_merging(tokens: torch.Tensor, r: int) -> torch.Tensor:
    """
    将相似的 Token 合并,减少序列长度
    tokens: (B, N, D)
    r: 要合并掉的 Token 数量
    返回: (B, N-r, D)
    """
    B, N, D = tokens.shape

    # 计算相邻 Token 之间的余弦相似度
    # 取前半和后半分别作为 src 和 dst
    src, dst = tokens[:, ::2], tokens[:, 1::2]  # 简化:奇偶分组
    scores = torch.nn.functional.cosine_similarity(
        src, dst, dim=-1
    )  # (B, N//2)

    # 找出最相似的 r 对,将 src 合并到 dst
    _, merge_idx = scores.topk(r, dim=-1, largest=True)

    # 合并操作:用均值代替两个相似 Token
    # (实际实现需要处理 index scatter,此处简化)
    merged = (src + dst) / 2  # 简化示意

    return merged  # 实际返回合并后的完整序列

5.2 Dynamic Resolution:为什么分辨率不能固定

问题:固定分辨率(如 224×224)对 OCR 任务是灾难性的------一张 A4 文档缩小到 224px 后,10 号字体的汉字已经模糊到无法识别。

Qwen-VL 的 Dynamic Resolution 方案

python 复制代码
# Dynamic Resolution 的核心逻辑
def dynamic_resolution_encode(image, task_type: str):
    """
    根据任务类型动态决定分辨率
    """
    resolution_map = {
        "ocr":          (1344, 1344),  # 高分辨率,保留字符细节
        "chart":        (896, 896),    # 中等,保留结构
        "scene_qa":     (448, 448),    # 标准,足够语义理解
        "thumbnail":    (224, 224),    # 低分辨率,节省计算
    }
    target_res = resolution_map.get(task_type, (448, 448))

    # 关键:使用 NaViT 风格的变长序列处理
    # 不同分辨率的图像产生不同数量的 Token
    # Unified Transformer 天然支持变长输入
    patches = encode_patches(image, target_res)
    return patches

# Token 数量随分辨率二次方增长
# 224px → 196 tokens
# 448px → 784 tokens   (4x)
# 896px → 3136 tokens  (16x)
# 1344px → 7056 tokens (36x)

这是 Qwen-VL 系列在 OCR benchmark 上大幅领先的重要工程原因之一:它在需要细节的任务上用高分辨率,在不需要时用低分辨率,在计算成本和精度之间做了自适应权衡。

5.3 M-RoPE:多模态位置编码的真正难题

这是现代多模态最被低估的工程难点。

问题的根源

  • 文本是 1D 序列 :位置是自然数 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 , 2 , 3 , . . . , N 1, 2, 3, ..., N </math>1,2,3,...,N
  • 图像是 2D 空间 :位置是坐标 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x , y ) (x, y) </math>(x,y)
  • 视频是 3D 时空 :位置是 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x , y , t ) (x, y, t) </math>(x,y,t)

RoPE(Rotary Position Embedding)是当前 LLM 的主流位置编码,但它原生只支持 1D 序列。

标准 RoPE(1D)
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> RoPE ( q , m ) = q ⋅ e i m θ \text{RoPE}(q, m) = q \cdot e^{im\theta} </math>RoPE(q,m)=q⋅eimθ

其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m 是 Token 在序列中的位置, <math xmlns="http://www.w3.org/1998/Math/MathML"> θ \theta </math>θ 是频率参数。这对文本没问题,但对图像,Token 的「位置」是二维的。

**M-RoPE(多模态 RoPE)**的解决方案是将位置编码分解为多个独立分量:

python 复制代码
import torch
import math

def m_rope_encode(tokens, modality: str, positions):
    """
    M-RoPE:多模态位置编码
    
    对于文本: positions shape = (seq_len,)        → 1D 位置
    对于图像: positions shape = (seq_len, 2)      → (x, y) 坐标
    对于视频: positions shape = (seq_len, 3)      → (x, y, t) 时空坐标
    """
    d_model = tokens.shape[-1]
    d_per_dim = d_model // positions.shape[-1]  # 每个维度分配的维度数

    rope_embeds = []

    if modality == "text":
        # 标准 1D RoPE
        rope_embeds.append(
            rope_1d(tokens, positions, d_model)
        )

    elif modality == "image":
        # 2D RoPE:x 方向 + y 方向各用一半维度
        x_pos, y_pos = positions[:, 0], positions[:, 1]
        rope_embeds.append(rope_1d(tokens[:, :d_per_dim], x_pos, d_per_dim))
        rope_embeds.append(rope_1d(tokens[:, d_per_dim:], y_pos, d_per_dim))

    elif modality == "video":
        # 3D RoPE:x + y + t 三个方向各用 1/3 维度
        x_pos, y_pos, t_pos = positions[:, 0], positions[:, 1], positions[:, 2]
        d3 = d_model // 3
        rope_embeds.append(rope_1d(tokens[:, :d3],    x_pos, d3))
        rope_embeds.append(rope_1d(tokens[:, d3:2*d3], y_pos, d3))
        rope_embeds.append(rope_1d(tokens[:, 2*d3:],  t_pos, d3))

    return torch.cat(rope_embeds, dim=-1)

# 如果不解决位置编码问题:
# → 图像 Token 的空间关系无法被模型感知(左上角 vs 右下角对模型一样)
# → 视频 Token 的时序关系丢失(第 1 帧 vs 第 30 帧对模型一样)
# → OCR、目标检测、时序推理全部退化

M-RoPE 是 Qwen3-VL 等最新模型的核心设计之一,也是 Unified 架构从「能用」到「好用」的关键一步。

5.4 Video LLM 的失败案例:Token Explosion

这个失败案例值得详细分析,因为它清楚地展示了 Unified 架构在工程上面临的边界。

场景:用 Unified 架构处理 1 分钟的会议视频(30fps,每帧 576 Token)

bash 复制代码
Token 计算:
  总帧数 = 60s × 30fps = 1800 帧
  每帧 Token = 576
  总 Token 数 N = 1800 × 576 = 1,036,800 ≈ 100 万

Attention 矩阵大小:
  N² = (1,036,800)² ≈ 10¹² 个元素
  以 float16 存储 = 2TB 显存

结论:完全不可行。

工程上的解决路径

graph TD A["原始视频\n1M tokens"] --> B{Token 压缩策略} B --> C["帧采样\n1800帧 → 64帧"] B --> D["空间压缩\n576 tok/帧 → 64 tok/帧"] B --> E["Sparse Attention\n不做全量 N² 计算"] C --> F["压缩后\n~4K tokens"] D --> F E --> G["只计算局部+全局 Attention\nO(N√N) 或 O(N·log N)"] F --> H["可工程落地\n约 16M Attention 元素"] G --> H

这就是为什么 Video LLM 领域的论文大量集中在 Sparse Attention、Token Pruning、Memory Routing 等方向------这些不是可选的优化,而是让系统能跑起来的必要条件。


六、为什么 Unified 适合 Agent:因果链推导

Agent 执行 GUI 任务时,需要完成以下推理:

markdown 复制代码
「点击页面右上角蓝色的『提交』按钮」
  ↓
1. 空间定位:「右上角」→ 图像坐标系中的区域
2. 视觉识别:「蓝色按钮」→ 颜色 + 形状特征
3. 文字识别:「提交」→ OCR
4. 语义理解:「提交」按钮意味着表单将被提交
5. 行动规划:生成点击坐标 (x, y)
graph LR A["屏幕截图\nImage Tokens"] --> C["Unified Transformer\n(全层双向交互)"] B["指令文本\nText Tokens"] --> C C --> D["空间定位\n坐标推理"] C --> E["OCR\n文字识别"] C --> F["语义理解\n功能推断"] D --> G["行动输出\n(x, y) + 操作类型"] E --> G F --> G

Cross-Attention 为什么在这里失败?

上述步骤 1-4 需要视觉信息和文本信息在每一层 Transformer 中持续交互。但 Cross-Attention 中:

  • 步骤 1(空间定位):图像特征只在 Cross-Attn 层注入,深层推理时已经不可见
  • 步骤 3(OCR):字符级空间关系在注入时已经被 Adapter 压缩,精度受损
  • 步骤 4-5(语义+规划):需要视觉和语言联合推理,但此时图像特征已经不在序列中

Unified 为什么更适合?

图像 Token 全程在序列中,每一层 Transformer 都可以同时看到视觉细节和文字指令,从浅层的特征提取到深层的逻辑推理,视觉和语言信息始终耦合。


七、Python 代码:从公式到实现

7.1 Self-Attention 完整实现(NumPy,带逐步注释)

python 复制代码
import numpy as np

def scaled_dot_product_attention(Q, K, V, mask=None):
    """
    Scaled Dot-Product Self-Attention
    
    Args:
        Q: (seq_len, d_k) --- Query 矩阵
        K: (seq_len, d_k) --- Key 矩阵
        V: (seq_len, d_v) --- Value 矩阵
        mask: (seq_len, seq_len) --- 可选的注意力掩码(因果掩码等)
    
    Returns:
        output: (seq_len, d_v)
        weights: (seq_len, seq_len) --- 注意力权重矩阵,可用于可视化
    """
    d_k = Q.shape[-1]

    # Step 1: Q·Kᵀ --- 计算所有 Token 对的相似度
    # scores[i, j] = Token i 对 Token j 的原始注意力分数
    scores = np.matmul(Q, K.T)                          # (seq_len, seq_len)

    # Step 2: 缩放 --- 防止高维时梯度消失
    # 若不缩放:d_k=512 时 scores 标准差为 √512 ≈ 22,softmax 进入饱和区
    scores = scores / np.sqrt(d_k)

    # Step 3: 可选 mask(例如 causal mask 防止看到未来 Token)
    if mask is not None:
        scores = scores + (1 - mask) * (-1e9)           # 将被 mask 的位置置为 -∞

    # Step 4: Softmax --- 将分数转换为概率分布
    # 减去最大值(数值稳定技巧,不影响结果)
    scores_shifted = scores - scores.max(axis=-1, keepdims=True)
    exp_scores = np.exp(scores_shifted)
    weights = exp_scores / exp_scores.sum(axis=-1, keepdims=True)  # (seq_len, seq_len)

    # Step 5: 加权求和 Value
    output = np.matmul(weights, V)                      # (seq_len, d_v)

    return output, weights


# ── 验证:3 Token 序列 ──────────────────────────────────
np.random.seed(42)
seq_len, d_k = 3, 64   # d_k=64 是实际模型中的常见值

Q = np.random.randn(seq_len, d_k)
K = np.random.randn(seq_len, d_k)
V = np.random.randn(seq_len, d_k)

output, weights = scaled_dot_product_attention(Q, K, V)

print("注意力权重矩阵(每行是一个 Token 对所有 Token 的关注比例):")
print(np.round(weights, 3))
# 每行之和为 1(softmax 归一化)
assert np.allclose(weights.sum(axis=-1), 1.0), "权重之和应为 1"
print(f"\n输出 shape: {output.shape}")  # (3, 64)
print(f"权重矩阵行之和: {weights.sum(axis=-1)}")  # [1. 1. 1.]

7.2 Cross-Attention vs Unified Self-Attention(PyTorch 对比)

python 复制代码
import torch
import torch.nn as nn

# ─────────────────────────────────────────────────────
# 第一代:Cross-Attention 架构
# 图像信息单向注入,不参与文本内部交互
# ─────────────────────────────────────────────────────
class CrossAttentionBlock(nn.Module):
    def __init__(self, d_model: int, num_heads: int = 8):
        super().__init__()
        self.cross_attn = nn.MultiheadAttention(
            d_model, num_heads, batch_first=True
        )
        self.norm = nn.LayerNorm(d_model)

    def forward(self, text_tokens: torch.Tensor, image_features: torch.Tensor):
        """
        text_tokens:    (B, T_text, D) --- 来自 LLM 的文本 Token
        image_features: (B, T_img,  D) --- 来自 ViT 的视觉特征(不是 Token!)
        
        注意:image_features 只作为 Key 和 Value
        图像永远是被查询的对象,永远无法主动查询文本
        """
        out, attn_weights = self.cross_attn(
            query=text_tokens,     # Q: 文本 Token 提问
            key=image_features,    # K: 图像特征回答「我是什么」
            value=image_features   # V: 图像特征给出「我的内容」
        )
        # 图像信息单向注入文本,此后图像特征退出计算图
        return self.norm(text_tokens + out)


# ─────────────────────────────────────────────────────
# 第二代:Unified Self-Attention 架构
# 图像 Token 与文本 Token 全程平等参与
# ─────────────────────────────────────────────────────
class UnifiedTransformerBlock(nn.Module):
    def __init__(self, d_model: int, num_heads: int = 8):
        super().__init__()
        self.self_attn = nn.MultiheadAttention(
            d_model, num_heads, batch_first=True
        )
        # Projector:将 ViT 输出维度对齐到 LLM 的 d_model
        # 实际模型中可能是 2 层 MLP,此处简化为线性层
        self.visual_proj = nn.Linear(d_model, d_model)
        self.norm = nn.LayerNorm(d_model)

    def forward(self, image_patches: torch.Tensor, text_tokens: torch.Tensor):
        """
        image_patches: (B, T_img,  D_vit) --- ViT patch embeddings
        text_tokens:   (B, T_text, D_llm) --- 文本 Token embeddings
        """
        # Step 1:图像 Patch → Image Token(维度对齐)
        image_tokens = self.visual_proj(image_patches)   # (B, T_img, D)

        # Step 2:拼接为统一序列
        # 拼接后,图像 Token 和文本 Token 在序列中地位完全平等
        unified_seq = torch.cat([image_tokens, text_tokens], dim=1)
        # shape: (B, T_img + T_text, D)

        # Step 3:统一 Self-Attention
        # 注意力矩阵 A 的形状: (B, T_img+T_text, T_img+T_text)
        # 自然包含 A_ii, A_it, A_ti, A_tt 四个子矩阵,同时被优化
        out, attn_weights = self.self_attn(unified_seq, unified_seq, unified_seq)

        # Step 4:残差 + LayerNorm
        unified_out = self.norm(unified_seq + out)

        # 分离输出(实际推理时通常只取文本部分生成文字)
        img_out  = unified_out[:, :image_tokens.shape[1], :]
        text_out = unified_out[:, image_tokens.shape[1]:, :]

        return text_out, img_out, attn_weights


# ── 对比实验:复杂度与信息流 ──────────────────────────
B, T_img, T_text, D = 2, 196, 100, 512  # 224px 图像 + 100 文本 Token

image_patches = torch.randn(B, T_img, D)
text_tokens   = torch.randn(B, T_text, D)
image_features = torch.randn(B, T_img, D)  # Cross-Attn 使用

cross_block   = CrossAttentionBlock(D)
unified_block = UnifiedTransformerBlock(D)

# Cross-Attention:只输出文本 Token(图像信息已注入但不再参与)
cross_out = cross_block(text_tokens, image_features)
print(f"Cross-Attention 输出: {cross_out.shape}")       # (2, 100, 512)

# Unified Self-Attention:图像和文本都有输出
text_out, img_out, weights = unified_block(image_patches, text_tokens)
print(f"Unified 文本输出: {text_out.shape}")            # (2, 100, 512)
print(f"Unified 图像输出: {img_out.shape}")             # (2, 196, 512)
print(f"注意力矩阵 shape: {weights.shape}")             # (2, 296, 296)
# 296 = 196 (图像) + 100 (文本),包含全部 4 个子矩阵

7.3 M-RoPE:多维位置编码实现

python 复制代码
import torch
import math

def rope_1d(x: torch.Tensor, positions: torch.Tensor, d: int) -> torch.Tensor:
    """标准 1D RoPE 编码"""
    theta = 1.0 / (10000 ** (torch.arange(0, d, 2, dtype=torch.float) / d))
    angles = positions.unsqueeze(-1) * theta.unsqueeze(0)   # (N, d//2)
    cos, sin = angles.cos(), angles.sin()

    x_even, x_odd = x[..., ::2], x[..., 1::2]
    x_rotated_even = x_even * cos - x_odd * sin
    x_rotated_odd  = x_even * sin + x_odd * cos

    return torch.stack([x_rotated_even, x_rotated_odd], dim=-1).flatten(-2)


def multimodal_rope(tokens: torch.Tensor, modality: str, positions: torch.Tensor):
    """
    M-RoPE:为不同模态提供合适维度的位置编码
    
    text  → positions: (N,)       1D 位置索引
    image → positions: (N, 2)     (row, col) 二维坐标
    video → positions: (N, 3)     (row, col, time) 三维时空坐标
    
    关键设计:将 d_model 均分给各维度
    → 各维度的位置信息独立编码,互不干扰
    → 模型可以分别学习「空间在哪」和「时间在哪」
    """
    N, d = tokens.shape

    if modality == "text":
        # 文本:标准 1D RoPE,位置编码覆盖全部维度
        return rope_1d(tokens, positions.float(), d)

    elif modality == "image":
        # 图像:2D RoPE,行列方向各用一半维度
        d_half = d // 2
        row_pos, col_pos = positions[:, 0].float(), positions[:, 1].float()

        x_row = rope_1d(tokens[:, :d_half], row_pos, d_half)   # 行方向
        x_col = rope_1d(tokens[:, d_half:], col_pos, d_half)   # 列方向
        return torch.cat([x_row, x_col], dim=-1)

    elif modality == "video":
        # 视频:3D RoPE,行、列、时间各用 1/3 维度
        d_third = d // 3
        row_pos  = positions[:, 0].float()
        col_pos  = positions[:, 1].float()
        time_pos = positions[:, 2].float()

        x_row  = rope_1d(tokens[:, :d_third],         row_pos,  d_third)
        x_col  = rope_1d(tokens[:, d_third:2*d_third], col_pos, d_third)
        x_time = rope_1d(tokens[:, 2*d_third:],       time_pos, d_third)
        return torch.cat([x_row, x_col, x_time], dim=-1)

    else:
        raise ValueError(f"未知模态: {modality}")


# ── 示例:为 14×14 的图像 Token 网格生成 2D 位置编码 ──
grid_size = 14  # ViT-L/14 的图像被切成 14×14 Patch
N_img = grid_size ** 2   # 196 个 Image Token

# 生成二维坐标
rows = torch.arange(grid_size).repeat_interleave(grid_size)  # [0,0,...,13,13]
cols = torch.arange(grid_size).repeat(grid_size)             # [0,1,...,0,1,...,13]
img_positions = torch.stack([rows, cols], dim=-1)            # (196, 2)

# 模拟 Image Token
img_tokens = torch.randn(N_img, 512)

# 施加 2D RoPE
img_tokens_with_pos = multimodal_rope(img_tokens, "image", img_positions)
print(f"图像位置编码后 shape: {img_tokens_with_pos.shape}")  # (196, 512)

# 对比:若用 1D RoPE,第 0 行第 13 列(位置 13)和第 1 行第 0 列(位置 14)
# 位置相差仅 1,但实际在图像中相距很远(跨行)
# 2D RoPE 中它们的编码会正确反映 (0,13) vs (1,0) 的二维距离差异

八、行业格局与工程取舍

8.1 主流模型技术对比

模型 机构 Resolution Token 压缩 位置编码 特色
GPT-4o OpenAI Dynamic 未公开 未公开 原生多模态端到端训练
Gemini 2.5 Google Dynamic 未公开 未公开 超长上下文视频理解
Qwen3-VL Alibaba Dynamic (高分辨率) M-RoPE OCR/文档理解 SOTA
InternVL3 Shanghai AI Lab Dynamic 2D-RoPE 开源 SOTA,工程成熟
LLaVA-OneVision LMMs-Lab Any Resolution 未公开 多任务泛化能力强

8.2 多模态发展时间线

timeline title 多模态大模型发展历程 2021 : CLIP(对比学习双塔,奠定视觉语言对齐基础) 2022 : Flamingo(Cross-Attention 注入,Adapter 范式) : BLIP-2(Q-Former,极致轻量接入) 2023 : LLaVA(MLP Projector + Instruction Tuning) : InternVL(早期 Unified 探索) 2024 : GPT-4o(完全端到端 Unified) : Gemini 1.5(Unified + 百万 Token 上下文) : Qwen-VL(Dynamic Resolution + M-RoPE) 2025 : Qwen3-VL / InternVL3 / LLaVA-OV(开源 Unified SOTA) : Video LLM 成为新核心战场

8.3 Cross-Attention 是否已经过时?

不是。正确的理解是:两种架构服务于不同的工程约束。

场景 推荐架构 原因
消费级 GPU 部署 Cross-Attention 可冻结 LLM,显存占用低
边缘设备 / 实时推理 Cross-Attention 计算量更可控
OCR / 文档理解 Unified 字符级空间推理需要深层视觉融合
GUI Agent / Computer Use Unified 需要持续的视觉-语言联合推理
视频理解(长视频) Unified + Token Compression 需要时序推理,同时需要工程优化
快速多模态原型验证 Cross-Attention 训练周期短,迭代快

九、总结

核心推导链

markdown 复制代码
1. LLM 只认识 Token
   ↓
2. 让图像进 LLM 的唯一路径是 Token 化
   ↓
3. Cross-Attention 只做到「图像特征注入」,不是真正的 Token
   ↓
4. 图像特征无法全程参与深层推理
   ↓ 导致
5. OCR / GUI / Grounding 能力受限(字符空间关系在深层丢失)
   ↓
6. Unified Self-Attention:图像变成真正的 Token
   ↓
7. 四个 Attention 子矩阵同时优化,模态融合是自然属性
   ↓ 但带来
8. Token 爆炸:视频 N=百万,N² Attention 不可行
   ↓ 因此需要
9. Token Compression + Sparse Attention + M-RoPE
   ↓ 这才是
10. 大厂真正在卷的工程难点

两代架构的本质

维度 第一代(Cross-Attention) 第二代(Unified)
代表模型 Flamingo、BLIP-2 GPT-4o、Gemini、Qwen3-VL
图像处理方式 外挂特征,单向注入 原生 Token,平等参与
信息流 Text 查询 Image All Attends All
深层推理 图像信息提前退出 图像 Token 全程参与
OCR / GUI 空间关系中途丢失 字符级空间推理可靠
Token 复杂度 不受图像 Token 数量影响 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N 2 ) O(N^2) </math>O(N2),需要压缩
工程成本 低(可冻结 LLM) 较高(全参数训练)

总结

最核心的一句话:

旧方案中,LLM 被动地「读取」图像特征;现代 Unified 架构中,图像 Token 与文本 Token 在每一层 Transformer 中双向交互,LLM 真正「理解」了图像。

但这带来了 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N 2 ) O(N^2) </math>O(N2) 的系统代价,Token Compression 和 M-RoPE 才是让 Unified 真正落地的工程关键。


包主:"最近还欠了一些账,pytorch的内容和transformers的内容还没写完 希望持续关注的小伙伴们多担待"!

相关推荐
lizhihai_9911 小时前
股市学习心得-PCB材料、制造、设备与耗材
大数据·人工智能·制造
zuozewei11 小时前
AI-7D-SATS 开发笔记 04:为什么要做一个面向性能分析的 Agent?
人工智能·笔记
码农阿强11 小时前
GPT-Image、Gemini-Image、Grok-Imagine 技术对比与API接入实战分享
人工智能·gpt·ai·aigc
索木木11 小时前
Deepseek MLA CP通信AlltoAll
人工智能·深度学习·训练·模型并行·cp并行·alltoall
南屹川11 小时前
【Linux】Linux性能调优实战:从CPU到内存
人工智能
Allen正心正念202511 小时前
DolphinScheduler快速了解(二)
人工智能
HS_Tiger11 小时前
混沌处理器 - 由韬定律探讨 自研的未来架构设计(设计中的10000条通路85000节点仅作为一个理论验证过程的参考)
人工智能·原创·可复用架构·未来架构
cd_9492172111 小时前
工业溶剂行业合规发展新范式:以渥克化学为例,解析正规渠道与全域服务布局
大数据·人工智能
小小编程路12 小时前
C++ 常用逻辑运算符
开发语言·c++·算法