一、核心结论
可以先记住最重要的一句话:
普通 ViT 和 VLM 中使用的 ViT,本质上的视觉编码过程非常相似。
主要区别在于:
普通 ViT 做分类时,通常只取 CLS Token 作为整张图片的全局特征。
VLM 通常保留大量 Patch Token,将这些视觉 Token 经过 Projector 后送入 LLM。
可以简单表示为:
普通 ViT 分类:
Image
-> ViT
-> B, N+1, D_vision
-> 取 CLS
-> B, D_vision
-> 分类器
VLM:
Image
-> ViT / CLIP-ViT
-> B, N+1, D_vision
-> 去掉 CLS,保留 Patch Token
-> B, N, D_vision
-> Projector
-> B, N, D_llm
-> LLM
二、ViT 为什么会产生很多 Token
ViT 不直接把整张图片作为一个整体处理,而是先将图片切成 Patch。
假设:
图片大小 = 224 × 224
Patch Size = 16 × 16
那么:
每边 Patch 数量 = 224 / 16 = 14
总 Patch 数量:
N = 14 × 14 = 196
因此一张图片会被转换成:
196 个 Patch
每个 Patch 进一步转换成一个视觉 Token。
所以:
Image
-> 196 个 Patch
-> 196 个 Patch Token
三、CLS Token 是什么
传统 ViT 通常还会额外加入一个可学习的 CLS Token。
因此:
196 个 Patch Token
再加:
1 个 CLS Token
最终进入 Transformer 的 Token 数量:
196 + 1 = 197
所以 ViT 内部常见的特征 Shape 是:
B, 197, D
其中:
B = Batch Size
197 = 1 个 CLS Token + 196 个 Patch Token
D = ViT Hidden Dimension
例如 ViT-B/16:
B, 197, 768
四、B, 197, 768 每个维度是什么意思
假设:
x.shape = B, 197, 768
第一个维度 B:
表示一个 Batch 中有多少张图片。
例如:
B = 8
表示一次输入 8 张图片。
第二个维度 197:
表示视觉 Token 数量。
其中:
Token 0 = CLS Token
Token 1 ~ Token 196 = Patch Token
第三个维度 768:
表示每一个视觉 Token 的特征维度。
因此可以理解为:
一张图片被表示成 197 个 Token。
每个 Token 是一个 768 维向量。
五、普通 ViT 为什么只取 CLS Token
假设 ViT 输出:
B, 197, 768
分类任务通常只需要回答:
这张图片属于什么类别?
例如:
猫
狗
汽车
飞机
因此没有必要把 196 个 Patch Token 全部送入分类头。
通常直接取:
x:, 0, :
也就是第 0 个 Token:
CLS Token
Shape 从:
B, 197, 768
变成:
B, 768
然后:
B, 768
-> Linear Classifier
-> B, num_classes
例如:
B, 768
-> Linear
-> B, 1000
就可以完成 ImageNet 1000 类分类。
六、CLS Token 为什么可以代表整张图片
CLS Token 刚开始只是一个可学习参数。
一开始它本身并不包含图片内容。
但是进入 Transformer 后:
CLS Token
会和:
Patch 1
Patch 2
Patch 3
...
Patch 196
通过 Self-Attention 不断进行信息交换。
经过多层 Transformer 后:
CLS Token 会融合整张图片的信息。
因此可以把最终 CLS Token 理解为:
整张图片的全局摘要向量。
例如:
CLS Feature = 0.12, -0.28, 0.76, ..., 0.15
这个向量可能包含:
图片大概是什么场景
主要物体是什么
整体语义是什么
因此很适合用于:
图像分类
全局图像表示
图文相似度
七、为什么 VLM 不只使用 CLS Token
VLM 面临的任务比普通分类复杂得多。
例如用户可能问:
图片左下角的人手里拿着什么?
图片中的第三辆车是什么颜色?
右上角有什么物体?
图片上的文字写了什么?
这些问题需要大量局部细节。
如果只把整张图片压缩成一个 CLS Token:
Image
-> ViT
-> CLS
-> LLM
相当于:
整张图片的大量信息
被压缩成一个向量。
这样很容易损失:
局部位置
小目标
细粒度纹理
文字
空间关系
多个物体之间的关系
因此 VLM 通常会保留大量 Patch Token。
八、VLM 中的 ViT 输出
假设视觉编码器输出:
B, 197, 1024
其中:
第 0 个 Token:
CLS Token
Shape:
B, 1, 1024
剩下 196 个:
Patch Token
Shape:
B, 196, 1024
很多 VLM 会执行类似:
x = x:, 1:, :
也就是删除 CLS。
最终得到:
B, 196, 1024
这就是常说的:
视觉 Token。
九、为什么是 B, 196, 1024
这里的三个维度分别表示:
B:
Batch Size
196:
视觉 Patch Token 数量
1024:
Vision Encoder 的 Hidden Dimension
例如:
B, 196, 1024
意味着:
每张图片由 196 个视觉 Token 表示。
每个视觉 Token 是一个 1024 维向量。
可以直观理解为:
Image
-> 196 个区域
-> 每个区域得到一个 1024 维视觉表示
十、Patch Token 是否只包含局部信息
不是。
这一点很重要。
刚进入 ViT 时:
Patch 1 主要对应图片的某个局部区域。
Patch 2 对应另一个局部区域。
但是经过多层 Self-Attention 后:
Patch 1 可以关注 Patch 2、Patch 3、...、Patch N。
所以最终的 Patch Token 不只是:
"这个局部区域有什么"
还包含:
这个区域和其他区域是什么关系
周围的上下文是什么
整张图的部分全局信息
因此更准确地说:
Patch Token 是以局部空间位置为锚点,同时融合全局上下文的视觉表示。
十一、为什么 VLM 要保留 Patch Token
假设图片中:
左上角 = 天空
右上角 = 篮球框
中央 = 球员
右下角 = 篮球
ViT 经过编码后,某些 Token 可能主要对应这些区域。
虽然实际一个 Patch 不一定严格对应一个完整物体,但可以帮助理解为:
Visual Token 1 -> 左上区域信息
Visual Token 30 -> 篮球框相关信息
Visual Token 100 -> 球员相关信息
Visual Token 180 -> 篮球相关信息
如果用户问:
"右下角是什么?"
LLM 可以通过 Attention 更关注右下角对应的一些 Visual Tokens。
如果只有一个 CLS:
CLS = 整张图片全局摘要
就更难保留这种细粒度空间信息。
十二、普通 ViT 与 VLM 最核心区别
普通 ViT 分类:
ViT 输出:
B, 197, 768
然后:
只取:
B, 768
也就是 CLS Token。
最终:
CLS
-> Classifier
-> 分类结果
VLM:
ViT 输出:
B, 197, 1024
然后:
去掉 CLS
得到:
B, 196, 1024
保留全部或大部分 Patch Token。
再把它们送给语言模型。
十三、为什么还需要 Projector
Vision Encoder 与 LLM 的 Hidden Dimension 往往不同。
例如:
ViT Hidden Dimension:
1024
LLM Hidden Dimension:
4096
那么:
B, 196, 1024
不能直接当成 LLM 的 Token Embedding。
因为 LLM 希望每个 Token 都是:
4096 维。
因此需要一个 Projector:
B, 196, 1024
-> Projector
-> B, 196, 4096
Projector 的作用就是:
将视觉特征空间映射到 LLM 的特征空间。
十四、Projector 可以是什么结构
最简单可以是一层 Linear:
1024 -> 4096
也可以使用 MLP:
1024
-> Linear
-> 中间维度
-> 激活函数
-> Linear
-> 4096
例如:
Visual Feature
1024 维
-> Linear
4096 维
或者:
1024
-> 4096
-> GELU
-> 4096
最终得到与 LLM Hidden Dimension 相同的视觉 Token。
十五、Projector 前后 Shape
假设:
Vision Encoder 输出:
B, 196, 1024
Projector:
1024 -> 4096
那么:
Projector 前:
B, 196, 1024
Projector 后:
B, 196, 4096
需要注意:
通常 Projector 只改变最后一个 Hidden Dimension。
不会自动改变 Token 数量。
因此:
196 一般保持不变。
十六、视觉 Token 如何进入 LLM
假设视觉部分:
B, 196, 4096
用户输入:
"图片里面有什么?"
Tokenizer 之后假设有 T 个文本 Token。
经过 LLM Embedding:
B, T, 4096
因为现在两边最后一维都是:
4096
所以可以组合成同一个 Token 序列。
例如:
Visual Tokens:
B, 196, 4096
Text Tokens:
B, T, 4096
组合后:
B, 196 + T, 4096
然后整体送入 LLM Transformer。
十七、一个简单完整例子
假设:
输入图片大小:
224 × 224
Patch Size:
16
Vision Encoder Hidden Dimension:
1024
LLM Hidden Dimension:
4096
第一步:Patchify
224 / 16 = 14
14 × 14 = 196
得到:
196 个 Patch
第二步:加 CLS
Token 数:
196 + 1 = 197
第三步:ViT 编码
ViT 输出:
B, 197, 1024
第四步:删除 CLS
得到:
B, 196, 1024
第五步:Projector
1024 -> 4096
得到:
B, 196, 4096
第六步:文本 Token
假设问题经过 Tokenizer 后有 10 个 Token。
文本 Embedding:
B, 10, 4096
第七步:组合
视觉:
B, 196, 4096
文本:
B, 10, 4096
最终:
B, 206, 4096
送入 LLM。
十八、196 并不是固定的
不能把 VLM 固定理解成:
B, 196, 1024
更通用的表示应该是:
B, N, D_vision
其中:
N = 视觉 Token 数量
D_vision = Vision Encoder Hidden Dimension
视觉 Token 数量 N 和以下因素有关:
输入图片分辨率
Patch Size
是否使用 CLS
是否使用特殊 Token
是否进行 Token Pooling
是否进行 Token Merge
是否使用 Resampler
十九、视觉 Token 数量如何计算
如果图片尺寸是:
H × W
Patch Size:
P × P
假设 H 和 W 都能被 P 整除。
那么 Patch 数量:
N = (H / P) × (W / P)
例如:
224 × 224 图片
Patch = 16
那么:
N = 14 × 14 = 196
如果图片:
336 × 336
Patch = 14
那么:
336 / 14 = 24
N = 24 × 24 = 576
所以视觉 Token 可能变成:
B, 576, D_vision
而不是:
B, 196, D_vision
二十、为什么视觉 Token 太多会导致 VLM 很慢
Visual Tokens 最终会作为 LLM 上下文的一部分。
例如:
文本只有:
100 Tokens
图片却产生:
576 Visual Tokens
那么 LLM 实际输入序列可能变成:
676 Tokens
如果两张图片:
2 × 576 = 1152 Visual Tokens
再加 100 个文本 Token:
1252 Tokens
因此视觉 Token 数量会直接影响:
LLM Prefill 时间
Attention 计算量
显存占用
KV Cache
端侧推理速度
二十一、为什么很多 VLM 要压缩视觉 Token
假设原始 ViT 输出:
B, 576, 1024
如果全部送入 LLM,成本比较高。
于是可以使用:
Token Merge
Pooling
Resampler
Q-Former
Token Compression
将:
576 个 Visual Tokens
压缩为:
256
128
64
甚至更少的 Token。
例如:
B, 576, 1024
-> Resampler
-> B, 64, 1024
-> Projector
-> B, 64, 4096
-> LLM
目标是:
尽量保留视觉信息
同时减少 LLM 输入长度。
二十二、CLS Token 与 Patch Token 的区别
CLS Token:
主要用于汇总整张图片的全局信息。
典型 Shape:
B, 1, D
适合:
图像分类
全局语义表示
图文整体匹配
Patch Token:
保留更多空间和局部信息。
典型 Shape:
B, N, D
适合:
VLM
视觉问答
细粒度图片理解
OCR
目标关系理解
空间推理
二十三、普通 ViT 和 VLM 的 Shape 对比
以一个假设模型为例。
普通 ViT:
输入:
B, 3, 224, 224
Patchify:
196 Patch
加入 CLS:
197 Tokens
ViT 输出:
B, 197, 768
分类:
取 CLS:
B, 768
分类头:
B, num_classes
VLM:
输入:
B, 3, 224, 224
Patchify:
196 Patch
加入 CLS:
197 Tokens
Vision Encoder 输出:
B, 197, 1024
删除 CLS:
B, 196, 1024
Projector:
B, 196, 4096
与文字 Token 拼接:
B, 196 + T, 4096
送入 LLM。
二十四、最容易出现的误区
误区 1
"普通 ViT 输出就是 B, 768。"
不准确。
ViT Transformer 内部通常仍然会产生:
B, N+1, D
只是在分类时只拿 CLS:
B, D
误区 2
"VLM 的 ViT 天生输出 B, 196, 1024。"
不准确。
更可能是:
ViT 原始输出:
B, 197, 1024
然后:
去掉 CLS
得到:
B, 196, 1024
误区 3
"视觉 Token 就等于图片分类结果。"
不是。
视觉 Token 是视觉特征。
例如:
B, 196, 1024
仍然只是连续特征向量。
它不是:
狗
猫
汽车
这样的类别编号。
误区 4
"Projector 负责理解图片。"
不完全正确。
真正主要负责提取视觉信息的是:
Vision Encoder / ViT。
Projector 更主要负责:
特征空间转换和视觉---语言接口连接。
误区 5
"一个 Patch Token 只包含一个小区域。"
不完全正确。
Patch Token 最开始来自一个局部区域。
但经过多层 Self-Attention 后,会融合其他 Patch 的上下文信息。
所以最终 Patch Token 同时具有:
局部空间信息
一定程度的全局上下文。
二十五、最通用的 VLM Shape 写法
不要死记:
B, 196, 1024
更推荐记住:
Vision Encoder 输出:
B, N, D_vision
Projector 后:
B, N, D_llm
文本 Token:
B, T, D_llm
组合后:
B, N + T, D_llm
其中:
B = Batch Size
N = Visual Token Number
T = Text Token Number
D_vision = Vision Encoder Hidden Dimension
D_llm = LLM Hidden Dimension
二十六、最核心的理解
普通 ViT:
把图片转换成很多视觉 Token。
但分类任务通常只使用 CLS Token:
B, N+1, D
->
B, D
VLM:
为了保留更多局部视觉信息,通常使用大量 Patch Token:
B, N, D_vision
然后通过 Projector:
B, N, D_llm
再作为 LLM 的输入 Token。
因此一句话总结:
ViT 负责把图片变成视觉 Token;
普通分类只取其中的全局 CLS;
VLM 则通常保留大量 Patch Token,让 LLM 能够看到更加完整和细粒度的视觉信息。