【大模型】跟着张量走一遍 VLM:图像如何变成 LLM 的 token?|多模态大模型专栏③
跟着张量走一遍 VLM:图像如何变成 LLM 的 token?|多模态大模型专栏③
一句话讲透本篇:借助一个零依赖的小 demo,跟随一个 batch 的张量从头走到尾,看清「图像 → patch → ViT → Projector → 拼进文本流 → LLM」每一步的 shape 变化。 走完会发现:VLM 中真正"新"的组件,仅有一个 Projector。
写在前面
前两篇 我们建立了两件事:① VLM 的三组件架构;② CLIP 如何训练出那双"眼睛"。
但很多读者反馈:架构图和损失函数都懂了,可一张图到底是怎么一步步变成 LLM 能处理的 token 序列的,脑子里还是糊的。
这很正常——因为中间隔着太多 shape 变换,光看公式记不住。本篇的解法很直接:写一个玩具 VLM,打印每一个中间张量的 shape,跟着数据走一遍。
- 零依赖:纯 Python,不用 torch/numpy,复制就能跑
- 玩具维度:8×8 的小图、4 个 patch、
d_v=8、d_h=12——以 clarity > realism 为原则,便于清晰追踪每一步 shape - NLP 视角:每个步骤都标注它在 NLP 里的等价物
本文代码对应
code/01_vision/04_walkthrough_demo.py,python 04_walkthrough_demo.py即可运行。本文展示的所有输出都是真实运行结果。
一、实验设置:一个极简的玩具 VLM
我们手搓一个最小可运行的 VLM 前向过程。超参数故意设得很小:
H = W = 8 # 8x8 的小图
P = 4 # patch 大小 4x4 -> 一共 4 个 patch
C = 3 # RGB 三通道
d_v = 8 # 视觉隐藏维数
d_h = 12 # LLM 隐藏维数
N_visual = (H // P) * (W // P) # = 4 个视觉 token
模拟的 prompt 是:<bos> <image> What is in this image ?(8 个文本 token,其中 <image> 是占位符)。
整个流程分 7 个 Stage,下面逐个走。
二、Stage A-B:图像 → patch(CV 的 “tokenization”)
Stage A:图像就是一个张量
image shape=(8, 8, 3)
= 8x8x3 grid of RGB pixel values in [0,1]
↩ NLP 对照:raw text string(还没分词,是连续的字符/像素)
图像在模型眼里就是 (H, W, C) 的数值网格。对应到 NLP,这就是"还没分词的原始文本"。
Stage B:切 patch = 分词(核心类比)
patches shape=(4, 48)
N = (H/P)*(W/P) = (8/4)*(8/4) = 4 patches; each patch flattened to P*P*C = 48 floats
↩ NLP 对照:BPE 分词 → token ids(把连续图像切成离散 patch = 切词)
这是整篇最关键的一步。把 8×8 的图按 4×4 切块,得到 (8/4)×(8/4) = 4 个 patch,每个 patch 摊平成 4×4×3 = 48 维向量。
记住这个类比,它会贯穿整个 VLM:
| CV(视觉) | NLP(文本) |
|---|---|
| 连续像素图像 | 原始文本字符串 |
| 切 patch | BPE 分词 |
| 每个 patch 摊平成向量 | 每个 token 的 id |
| patch 数 N = (H/P)(W/P) | 序列长度 L |
💡 真实 VLM 用 224×224 的图、P=14,会得到
(224/14)² = 256个 patch。高分辨率(如 448)就是 1024 个——这就是第一篇说的"一张图比一段文本还长"的来源。
三、Stage C-D:patch embedding + ViT(和 BERT 几乎一样)
Stage C:patch → embedding(查表)
patch_embeddings shape=(4, 8)
(N=4, d_v=8) — like token embeddings, but from images
↩ NLP 对照:nn.Embedding(ids) → token_emb (L, d),查表把离散 id 变成稠密向量
+ [CLS] token + pos shape=(5, 8)
(1 + N) = 5 tokens; ViT now sees a 'sentence' of patches
↩ NLP 对照:BERT 的 [CLS] + 位置编码,机制完全一样
每个 48 维的 patch,经一层 Linear 映射成 d_v=8 维的 embedding——和 nn.Embedding 把 token id 变成向量是同一回事,只是一个用查表、一个用线性投影。然后像 BERT 一样:前面拼一个 [CLS],加上位置编码,序列从 4 变成 5。
Stage D:ViT = Transformer(没有新东西)
vision_features shape=(5, 8)
shape unchanged: (1+N, d_v). All tokens have mixed information.
↩ NLP 对照:Transformer encoder —— 和 BERT/GPT 主体一模一样,没有新东西
Attention matrix (5x5, which token attends to which):
CLS -> [0.19 0.16 0.21 0.24 0.19]
p0 -> [0.20 0.19 0.22 0.19 0.19]
p1 -> [0.20 0.20 0.19 0.22 0.20]
p2 -> [0.32 0.16 0.17 0.20 0.15]
p3 -> [0.27 0.19 0.19 0.18 0.17]
ViT 跑了一个 Transformer block,shape 不变(还是 (5, 8)),但每个 token 已经"混合"了其他 token 的信息。看到这个 attention 矩阵,NLP 工程师应该很有亲切感——它和 BERT 的自注意力完全一样,只是这里的"词"是图像 patch。比如 p2 行对 CLS 的注意力最高(0.32),说明第 2 号 patch 的信息在往 CLS 汇聚。
结论:到这一步为止,整个过程没有任何超出 Transformer 范畴的新东西。
四、Stage E:Projector(VLM 唯一的"新组件")
patch_features (drop CLS) shape=(4, 8)
visual_embeddings shape=(4, 12)
(N, d_h) = (4, 12). Now LLM-shaped, ready to splice.
↩ NLP 对照:NLP 没有这一步 —— 这是 VLM 独有的'翻译层',把视觉空间对齐到 LLM 空间
先丢掉 [CLS](LLaVA 只保留 patch token),然后过一个 2 层 MLP Projector,把 d_v=8 维翻译成 d_h=12 维——也就是 LLM 的隐藏维数。
这是整个 VLM 流程里唯一 NLP 不存在的组件,也是第一篇说的"VLM 区别于纯 LLM 的灵魂"。它的作用就是当翻译官:SigLIP 说的"视觉语"是 8 维,LLM 听懂的"语言"是 12 维,Projector 负责对齐。
🔧 动手建议:将
d_h改为 4096(接近真实 LLM 的 hidden_size),可发现 Projector 的参数量主要由d_v × d_h决定——这正是 Projector 通常仅有数百万至数千万参数、远小于 LLM 的原因。
五、Stage F-G:拼进文本流 + loss mask
Stage F:splice(把视觉 token 缝进文本)
text_embeddings (pre-splice) shape=(8, 12)
(L_text=8, d_h=12). The <image> slot is a placeholder.
full_sequence (post-splice) shape=(11, 12)
(L_text - 1 + N) = 7 + 4 = 11 tokens
↩ NLP 对照:把 prompt 里的 <image> 占位符,原地替换成 N 个真正的视觉 token
这是 VLM 最巧妙的设计:prompt 里原本有个 <image> 占位符(1 个 token),现在把它原地替换成 4 个真正的视觉 token。序列从 8 变成 8 - 1 + 4 = 11。
替换后,视觉 token 和文本 token 就平权地住在同一条序列里了。
Stage G:LLM 看到的样子 + loss mask
Token layout (T = text token, V = visual token):
T V V V V T T T T T T
The LLM runs causal self-attention over this 11-token sequence.
Loss is computed only on assistant tokens (appended after this in real training).
Visual tokens NEVER contribute to loss — they are inputs, not targets.
↩ NLP 对照:label mask:只在 assistant 文本 token 上算 loss,视觉 token 永远 mask 掉
LLM 看到的就是这条 T V V V V T T T T T T 的序列(1 个 <bos> + 4 个视觉 token + 6 个文本 token)。它在上面跑 causal attention,和纯文本 LLM 没有任何区别。
但有一个训练时的关键细节:loss 只在 assistant 回复的文本 token 上算,视觉 token 永远被 mask 掉、不参与 loss——因为它们是"输入"不是"目标"。这个 label mask 是 VLM 训练最容易踩坑的地方(参见 code/03_sft/sft_mask_walkthrough.py)。
六、核心一图:NLP 路径 vs VLM 路径
把上面 7 步串起来,就是这张对照图(demo 末尾的真实输出):
【NLP 路径】
text --BPE--> ids (L,) --Embedding--> token_emb (L, d)
--Transformer--> hidden (L, d) --> LLM
【VLM 路径】
image (8,8,3)
--patch 切分--> patches (4, 48) ◀ 相当于 BPE 分词
--Linear--> patch_emb (4, 8) ◀ 相当于 Embedding
--+CLS +pos--> (5, 8)
--ViT--> vis_feat (5, 8) ◀ Transformer,无新东西
--Projector--> visual_emb (4, 12) ◀ VLM 独有!翻译到 LLM 维度
--splice--> full_seq (11, 12) ◀ 视觉 + 文本拼成一条序列
--> LLM(和 NLP 的 LLM 是同一个)
一句话:LLM 完全没变,只是 'token 从哪来' 换了——
NLP 用 BPE 切词,VLM 用 patch 切图 + Projector 把图翻译成 LLM 的语言。
七、从这次 walkthrough 里带走的 5 个洞察
- 切 patch 就是 CV 的分词——一旦想通这一点,VLM 的恐惧感就少了一半。
- ViT 没有任何新东西——它就是 BERT,把 patch 当 token 而已。懂 Transformer 就懂 ViT。
- Projector 是 VLM 唯一的新组件——一个把视觉维度翻译到 LLM 维度的 MLP,参数量不大,却是 Stage 1 训练的唯一主角。
- splice 后视觉 token 与文本 token 平权——
(11, 12)里没人知道哪 4 个是图、哪 7 个是字,LLM 一视同仁。 - 视觉 token 不参与 loss——它们是输入不是目标,label mask 必须正确处理,否则训练会崩。
八、动手验证(强烈建议)
这篇的价值在于"自己跑一遍"。打开 code/01_vision/04_walkthrough_demo.py,做三个小实验:
- 把
H = W = 16:patch 数从 4 变成 16,亲眼验证"高分辨率 → 视觉 token 变多"这条因果链。 - 把
P = 2:patch 更细,token 更多——理解 patch 大小如何权衡粒度与开销。 - 改
image_slot_idx:把视觉 token 拼到 prompt 的不同位置,理解 splice 的灵活性。
📎 本篇涉及代码
| 文件 | 说明 |
|---|---|
code/01_vision/04_walkthrough_demo.py |
零依赖端到端 walkthrough(本文增强版:每步含「NLP 对照」+ 末尾路径对照图) |
小结
| Stage | 做了什么 | NLP 等价 |
|---|---|---|
| A | 图像 = (H,W,C) 张量 | raw text |
| B | 切 patch | BPE 分词 |
| C | patch embedding + CLS + pos | Embedding + 位置编码 |
| D | ViT | Transformer(无新东西) |
| E | Projector 维度翻译 | NLP 无(VLM 独有) |
| F | splice 拼进文本流 | 替换 <image> 占位符 |
| G | LLM attention + loss mask | label mask |
下一篇我们进入对齐机制:从 BLIP-2 的 Q-Former 到 LLaVA 的 MLP Projector,再到 Qwen-VL 的 M-RoPE,搞懂视觉 token 如何获得 2D 位置感知、动态分辨率怎么工作。关注专栏,更新第一时间通知。
专栏:《从 NLP 到 VLM:多模态大模型研发实战》
上一篇:② 手写 CLIP | 本篇:③ 张量级 walkthrough | 下一篇:④ 对齐机制(上)· Projector
更多推荐



所有评论(0)