现代多模态大模型的核心基础: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 底层原理,信息密度较高。
目录
- Self-Attention:从公式到因果
- [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")
- [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")
- 现代多模态的五层理解框架
- [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")
- [为什么 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")
- [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")
- 行业格局与工程取舍
- 总结
一、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 全局连接是关键
CNN 的感受野是局部的,RNN 的信息传递是顺序衰减的,而 Self-Attention 中每个 Token 与所有 Token 直接相连,没有距离衰减,没有信息瓶颈。这是 Transformer 超越 CNN/RNN 的根本原因,也是后来多模态统一的基础。
二、Cross-Attention 的结构性缺陷:推导而非结论
第一代多模态(Flamingo、BLIP-2)的做法是:用 Vision Encoder 提取图像特征,通过 Cross-Attention 注入 LLM。
2.1 Cross-Attention 的工程价值
在讨论缺陷之前,需要承认它的工程合理性:
- 冻结 LLM:只训练 ViT + Q-Former/Adapter,参数量小一到两个量级
- 低算力门槛:7B LLM + 轻量 Projector,消费级 GPU 可运行
- 模块化设计:视觉和语言模块独立迭代
- 适合资源受限场景:边缘设备、实时推理
这些优势在特定业务场景下仍然成立,不应被简单否定。
2.2 OCR 弱的真实原因:一条因果链
「Cross-Attention OCR 弱」是结论。真正的问题是:为什么?
OCR 本质是 字符级 spatial reasoning,需要:
- 二维空间关系:「这个字符在第 3 行第 5 列」
- 行列结构感知:上下行的对齐关系
- 局部区域 Token alignment:字符边界与图像区域的精确对应
- 跨层持续的视觉推理:浅层定位 → 深层语义,需要多层共同参与
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 空间中平等交互。
3.2 Projector 的必要性
ViT 的输出维度(如 1024)通常与 LLM 的 d_model(如 4096)不同,需要一个线性变换层对齐。这个 Projector 看似简单,实际承担了重要职责:
- 维度对齐:使图像 Token 能直接与文本 Token 拼接
- 语义空间对齐:将视觉语义空间映射到语言语义空间
- 参数高效:通常只是一个线性层或两层 MLP,但训练时它是信息融合的关键瓶颈
3.3 两代架构的本质区别
| 维度 | 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)给出了答案:
- 将图像切成固定大小的 Patch(如 16×16 像素)
- 每个 Patch 线性嵌入为一个向量
- 加上位置编码,得到 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 Compression 和 Sparse 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 显存
结论:完全不可行。
工程上的解决路径:
这就是为什么 Video LLM 领域的论文大量集中在 Sparse Attention、Token Pruning、Memory Routing 等方向------这些不是可选的优化,而是让系统能跑起来的必要条件。
六、为什么 Unified 适合 Agent:因果链推导
Agent 执行 GUI 任务时,需要完成以下推理:
markdown
「点击页面右上角蓝色的『提交』按钮」
↓
1. 空间定位:「右上角」→ 图像坐标系中的区域
2. 视觉识别:「蓝色按钮」→ 颜色 + 形状特征
3. 文字识别:「提交」→ OCR
4. 语义理解:「提交」按钮意味着表单将被提交
5. 行动规划:生成点击坐标 (x, y)
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 | 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 多模态发展时间线
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的内容还没写完 希望持续关注的小伙伴们多担待"!