omni / docs /multimodal /index.md
chenbhao's picture
Update docs: challenges.md (training bias + scene text approach), vam/multimodal/trainers docs sync
2460459
|
Raw
History Blame Contribute Delete
4.98 kB
# multimodal/ · 多模态
把「外部模态」(图像/音频)编码并投影到 LLM 隐藏空间,实现多模态理解与生成。
## 数据流
```
原始输入(图/音)
└─ encoder → 模态特征
└─ projector → 投影到 hidden_size
└─ 替换 input_ids 中的占位 token 位置的 embedding
└─ 进入共享 LM 主干
```
## 编码器(Encoders)
把外部模态的原始信号编码为模态特征,冻结预训练权重、不参与 LLM 主干训练。
| 文件 | 类 | 作用 |
| --- | --- | --- |
| `encoders/vision/siglip.py` | `SiglipVisionEncoder` | 图像 → patch 特征(SigLIP) |
| `encoders/audio/sensevoice.py` | `SenseVoiceAudioEncoder` | 语音 → 帧级别 fbank 特征(SenseVoice) |
### SigLIP 视觉编码器
- 基于 `transformers.SiglipVisionModel`(siglip2-base-p32-256)
- 图像被分割为 32×32 像素的 patch,输出 patch 特征序列
- `processor` 负责图像预处理(resize、normalize)
- 输出通过 `MMVisionProjector` 重采样到 `image_token_len=64` 个视觉 token
### SenseVoice 音频编码器
- 基于 FunASR 的 `SenseVoiceEncoderSmall`(阿里达摩院)
- 输入:16kHz 单声道音频 → fbank 特征
- 输出:**帧级别**声学特征(每帧对应约 10ms 音频)
- 编码器输出维度:`d_model=512``audio_hidden_size`
- 内部包含 frontend(fbank 提取)+ encoder(Conformer 架构)
- 使用 `funasr.AutoModel` 加载,`trust_remote_code=True`
> 注意:FunASR 的 C 库初始化与 PyArrow 存在冲突,需确保 pyarrow 在 FunASR 模型创建后导入(VAMDataset 使用 lazy import)。
### 音频特征长度
音频经 SenseVoice 编码后的帧数取决于输入长度。由于帧数与文本 token 数不一致,需用 `audio_proj` 重采样到固定长度匹配占位 token:
```
音频 3 秒 @ 16kHz = 48000 采样点
→ fbank (80 维) × ~300 帧
→ audio_proj 重采样到目标帧数(占位 token 数)
→ 替换 input_ids 中的 <|audio_pad|> 位置
```
## 投影器(Projectors)
把 encoder 输出的模态特征投影到 LLM 隐藏维度(`hidden_size`),随后替换 `input_ids` 中的占位 token 位置。
| 文件 | 类 | 作用 |
| --- | --- | --- |
| `projectors/vision.py` | `MMVisionProjector` | 视觉特征 → LLM `hidden_size` |
| `projectors/audio.py` | `MMAudioProjector` | 音频特征 → LLM `hidden_size` |
### MMAudioProjector
```
输入: (B, T_audio, audio_hidden_size=512)
└─ Linear(512 → hidden_size)
└─ 2D 位置编码(补偿音频帧与文本 token 的位置差异)
└─ 重采样到目标 token 数
输出: (B, target_tokens, hidden_size)
```
## VAM 双模态架构
VAM 同时处理视觉和音频:
```
VAM(LMForCausalLM)
├─ model: LM # 共享主干
├─ vision_encoder + proj # 图像理解
├─ audio_encoder + proj # 语音理解
└─ talker: TalkerModule # 语音生成
```
### 多模态特征注入
`bridge_layer`(默认第 3 层)之后注入多模态特征:
```python
# Forward 中的多模态注入
hidden_states = self.model.embed_tokens(input_ids)
# Thinker 层
h = self.thinker(hidden_states)
# 注入视觉特征
if pixel_values is not None:
vision_features = self.vision_proj(self.vision_encoder(pixel_values))
h = inject_at_positions(h, vision_features, vision_positions)
# 注入音频特征
if audio_inputs is not None:
audio_features = self.audio_proj(self.audio_encoder(audio_inputs))
h = inject_at_positions(h, audio_features, audio_positions)
# Talker 层
h = self.talker(h)
# 双 head 输出
logits = self.lm_head(h)
audio_logits = self.talker.decode(h) # 8 层 Mimi code
```
### 模态组合
VAM 支持灵活的模态输入组合:
| 输入模态 | 设置 | 用例 |
| --- | --- | --- |
| 文本 + 音频 | 提供 `audio_inputs``pixel_values=None` | 语音对话 |
| 文本 + 图像 | 提供 `pixel_values``audio_inputs=None` | 图像理解 |
| 文本 + 图像 + 音频 | 同时提供两者 | 全模态交互 |
| 纯文本 | 两者均为 None | 回退到 LM |
## 设计要点(面试)
- **encoder 冻结 + 只训 projector**:大模型主干易灾难性遗忘,冻结预训练视觉/音频 encoder、只训轻量投影层,是高效多模态对齐的常用做法。
- **`encoders/` 与 `projectors/` 分离**:encoder 负责「理解模态」,projector 负责「对齐到 LLM 空间」,二者职责清晰、可独立替换。
- **可扩展性**:加新模态 = 加一对 encoder+projector + 占位 token,主干不动(符合本仓库分层初衷)。
- 文本侧用特殊占位 token(`<|image_pad|>` / `<|audio_pad|>`)预留位置;
- `forward` 把投影特征写到这些位置,LLM 对文本/视觉/音频 token 一视同仁。
- **3 阶段 SFT**:T2A→audio_proj→full,逐步激活文本生成、音频理解、协同优化能力。