从零实现 Mini-LLaVA,掌握 Vision Encoder + Projector + LLM 架构,两阶段训练(特征对齐 + 指令微调)。
第3章的 CLIP 解决了视觉-语言对齐问题——它能判断图像与文本是否匹配。但它不能"对话":你不能问它"图像里发生了什么?接下来该怎么办?"
LLaVA (Liu et al., 2023) 解决了这个问题:将 CLIP 的视觉编码器与大语言模型连接起来,使 LLM 能够"看见"并"讨论"图像。
对于 VLA 自动驾驶,多模态 LLM 是核心能力:
- 场景理解:用自然语言描述和推理驾驶场景
- 指令理解:将"在前方路口右转"联系到实际视觉场景
- 决策解释:理解模型为什么会做出某个驾驶决策
- VLA 基础:DriveVLM 等驾驶 VLA 就是 LLaVA + 行动输出
第1-2章: Transformer + ViT(组件)
第3章: CLIP(视觉-语言对齐)
第4章: LLaVA(多模态对话)← 当前
第5章: 多模态推理(Grounding, CoT)
第14章: VLA(视觉→语言→行动)
┌─────────────────────────────────────────────────────┐
│ LLaVA 架构 │
│ │
│ 图像 (336×336) 文本 "What do you see?" │
│ │ │ │
│ CLIP ViT-L/14 Tokenizer │
│ (Vision Encoder) │ │
│ │ │ │
│ Patch Features Text Tokens │
│ (576 × 1024) (T,) │
│ │ │ │
│ MLP Projector │ │
│ (2-layer GELU) │ │
│ │ │ │
│ Visual Tokens Text Embeddings │
│ (576 × 4096) (T × 4096) │
│ │ │ │
│ └───────┬───────────────┘ │
│ │ │
│ [Visual | Text] Concatenation │
│ (576 + T) × 4096 │
│ │ │
│ Causal LLM (LLaMA/Vicuna) │
│ │ │
│ Generated Response │
│ │
└─────────────────────────────────────────────────────┘
三个核心组件:
1. Vision Encoder:CLIP ViT(预训练,冻结)
2. Projector:MLP/Q-Former,将视觉特征映射到 LLM 空间
3. LLM:Causal Language Model(预训练,Stage 2 解冻)
LLaVA 使用 CLIP 的视觉编码器作为"视觉 Frontend":
- 通常是 ViT-L/14(Large,patch=14)
- 输入 336×336 → 24×24 + CLS = 577 tokens
- 输出 577 × 1024 特征向量(保留空间结构)
关键设计选择:
- 使用 patch features(不用 [CLS] token):保留空间信息
- 使用倒数第二层特征(penultimate layer):比最后一层特征更具语义性
- 视觉编码器保持冻结:预训练的对齐能力已经足够
Projector 是视觉和语言之间的"翻译器"。三种主流方案:
| 方案 | 架构 | 输出 tokens | 参数 | 使用场景 |
|---|---|---|---|---|
| MLP (LLaVA v1.5) | 2层 Linear+GELU | 576 | ~0.1B | 标准方案 |
| Q-Former (BLIP-2) | N个可学习 query + Cross-Attn | 32-64 | ~0.2B | token 压缩 |
| C-Abstractor | 卷积下采样 + 抽象 | 144 | ~0.05B | 效率优化 |
MLP 为什么有效?
- 简单反而好:2 层 MLP 就能学好跨模态映射
- 不需要压缩 token:LLM 有能力处理 576 个额外的"视觉词"
- 保留完整的空间信息:每个视觉 token 对应图像的一个区域
LLaVA 使用预训练的 LLM 作为"语言后端":
- LLaVA v1.5 使用 Vicuna-7B/13B(LLaMA 微调版)
- 4096 维隐藏层
- 因果自注意力
关键 insight:LLM 将 visual tokens 视为一种"外语"——projector 学会了将视觉特征"翻译"为 LLM 能理解的表示。
USER: <image>\nWhat do you see in this image?
ASSISTANT: I see a busy city intersection with traffic lights
and several pedestrians crossing the street.</s>
USER: What should a driver be aware of?
ASSISTANT: The driver should pay attention to:
1. Traffic light status (currently green)
2. Pedestrians on the crosswalk
3. Vehicles in adjacent lanes
4. Turn signals from other cars</s>
关键设计:
- <image> token 是视觉占位符,被替换为 visual tokens
- USER: 和 ASSISTANT: 明确对话角色
- </s> 是 EOS token,表示回复结束
- 训练时只对 ASSISTANT 部分计算 loss
原始序列:
"<image>\nUSER: What do you see?"
Tokenize 后:
[<image>, USER, :, What, do, you, see, ?]
替换 <image>:
[visual_1, visual_2, ..., visual_576, USER, :, What, do, you, see, ?]
最终嵌入:
[V1, V2, ..., V576, T1, T2, T3, T4, T5, T6, T7] 共 576+7 个 tokens
只对 ASSISTANT 的回复部分计算 loss:
Position: 0...............50, 51, 52, 53, 54, 55
Content: [Visual Tokens | USER... | ASSISTANT: ...]
Labels: [-100, -100, ... -100, 51, 52, 53, 54]
↑ ↑
prompt不计算loss 只计算assistant部分
labels = input_ids.clone()
# 找到 assistant 开始位置
assistant_start = prompt.index("ASSISTANT:")
labels[:, :assistant_start] = -100 # 不计算 loss
LLaVA 的训练不是简单的端到端——需要分两个阶段:
直觉理解:
- Stage 1 让 projector 学会"翻译"视觉特征
- Stage 2 让 LLM 学会"利用"视觉信息进行对话
- 如果直接做 Stage 2,projector 的梯度会与 LLM 的梯度冲突
| 设置 | 值 |
|---|---|
| 目标 | 让 visual tokens 对 LLM 来说"可理解" |
| 可训练 | 仅 Projector |
| 冻结 | Vision Encoder + LLM |
| 数据 | 图像-描述对(简单) |
| Batch | 256 (大) |
| 学习率 | 2e-3 (大,因为只训一个小模块) |
| 步数 | ~5000 steps |
数据格式很简单:
USER: <image>\nDescribe this image in detail.
ASSISTANT: A highway scene with three lanes, clear weather...
这个阶段相当于让 projector 学会"对齐"——将视觉特征翻译为 LLM 能理解的"语言"。
| 设置 | 值 |
|---|---|
| 目标 | 让模型学会多模态对话 |
| 可训练 | Projector + LLM |
| 冻结 | Vision Encoder |
| 数据 | 多模态多轮对话 |
| Batch | 128 |
| 学习率 | 2e-5 (LLM) / 1e-4 (Projector) |
| 步数 | ~10000 steps |
数据格式更丰富(多轮对话):
USER: <image>\nWhat's the weather condition?
ASSISTANT: It's raining heavily, visibility is reduced.
USER: How should the driver adjust?
ASSISTANT: The driver should: 1) Reduce speed 2) Turn on headlights
3) Increase following distance 4) Be extra cautious of pedestrians
Stage 2 中,projector 和 LLM 需要不同的学习率:
optimizer = AdamW([
{"params": projector_params, "lr": 1e-4}, # 较大
{"params": llm_params, "lr": 2e-5}, # 较小(微调预训练模型)
])
原因:projector 是随机初始化的,需要大步长学习;LLM 已经预训练过,只需小步长微调。
在 Stage 2,可以用 LoRA 替代全量微调:
if use_lora:
# 冻结 LLM,注入 LoRA adapter
inject_lora(llm, r=64, alpha=128)
# 只训练 LoRA 参数 + projector
- 参数效率:只训练 ~1% 的 LLM 参数
- 避免灾难性遗忘
- 方便切换多个下游任务
自动驾驶常有多个相机输入。LLaVA-NeXT 支持多图像:
前相机 ─→ Vision Encoder ─→ 576 tokens
后相机 ─→ Vision Encoder ─→ 576 tokens
左相机 ─→ Vision Encoder ─→ 576 tokens
右相机 ─→ Vision Encoder ─→ 576 tokens
↓
[2304 visual tokens | text]
↓
LLM
策略:
1. 独立编码:每张图独立过 vision encoder
2. 直接拼接:所有 visual tokens 放入序列
3. 分隔标记:用特殊 token 分隔不同图像
标准 CLIP 输入是 336×336,对于驾驶场景来说太小了(需要看清远处的交通标志)。
LLaVA v1.6 的 AnyRes 策略:
720×960 的高分辨率驾驶图像
│
选择最佳分辨率: 672×672(2×2网格)
│
分割: 4个 336×336 子图 + 1个缩略图
│
编码: 5×577 visual tokens = 2885 tokens
│
拼接并送入 LLM
效果:
- 能辨认远处的小目标(交通标志、行人)
- 保留全局场景理解(通过缩略图)
- 代价是更多的 visual tokens
576 个 visual tokens 对于长对话或长视频来说太多了。压缩策略:
- Q-Former:用 32-64 个可学习 query 压缩 → 10x 压缩
- Token Merge (ToMe):合并相似的 adjacent tokens
- C-Abstractor:卷积下采样 → 4x 压缩
- Adaptive Pooling:简单的空间池化
USER: <image>\nDescribe this driving scene in detail.
ASSISTANT: I observe an urban intersection under clear weather:
- Road: 4-lane bidirectional with left-turn lane
- Traffic light: Red for our direction
- Vehicles: 3 cars stopped ahead of us
- Pedestrians: 1 person waiting at crosswalk on the right
- Road markings: Clear lane markings visible
- Signs: Speed limit 40 km/h sign on the right
USER: <image>\nThe light just turned green. What should I do?
ASSISTANT: Even though the light is green, check:
1. Are pedestrians still crossing? (look at crosswalk)
2. Is the car ahead starting to move?
3. Are there vehicles running the red light from cross street?
After confirming safety, proceed slowly through the intersection.
USER: <image>\nIs this a dangerous situation?
ASSISTANT: Yes, this appears hazardous:
1. The vehicle in the adjacent lane is drifting out of its lane
2. Wet road surface (recent rain) — reduced friction
3. The pedestrian on the sidewalk is looking at their phone
Recommendations:
- Increase distance from the drifting vehicle
- Reduce speed for wet conditions
- Be prepared to brake if pedestrian steps onto road
本章建立的核心能力:
1. ✅ LLaVA 三组件架构:Vision Encoder + Projector + LLM
2. ✅ 连接器对比:MLP vs Q-Former vs C-Abstractor
3. ✅ 两阶段训练:特征对齐 + 指令微调
4. ✅ 对话格式化:Loss Masking 只对 assistant 部分计算
5. ✅ 多图像处理:多相机拼接策略
6. ✅ AnyRes 高分辨率:分割→多 patches 编码
7. ✅ 驾驶场景应用:场景描述、决策推理、危险分析
下一章:多模态理解与推理 — 视觉定位、多模态思维链、多模态 Agent