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=512audio_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 层)之后注入多模态特征:

# 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_inputspixel_values=None 语音对话
文本 + 图像 提供 pixel_valuesaudio_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,逐步激活文本生成、音频理解、协同优化能力。