27届大模型面试准备(七十七):多模态大模型预训练工程——数据配比、多阶段训练与模态对齐

27届大模型面试准备(七十七):多模态大模型预训练工程------数据配比、多阶段训练与模态对齐

引言

本文是「27届大模型面试准备」系列的第 77 篇,承接(七十六)大模型推理服务高可用与容灾工程。前面我们讲了推理侧的部署、调度、容灾,这一篇把视角拉回训练侧最硬核的工程问题之一:多模态大模型(MLLM)怎么训出来。

单模态 LLM 的预训练范式已经相对成熟(数据清洗、tokenizer、next-token 预测、分布式并行),但一旦把图像、视频、音频塞进来,问题立刻复杂一个量级:不同模态的表示空间怎么对齐、训练数据怎么配比、视觉编码器冻不冻、多阶段课程怎么排、跨模态注意力怎么算才不爆显存。这些恰恰是校招/社招面试里区分"用过 API"和"真正训过模型"的分水岭。

本篇用一张总览图 + 多张对比表,逐节拆解多模态预训练的工程链路,并给出可落地的代码片段。结尾附面试速答与高频追问清单。

复制代码
多模态预训练工程总览
==================================================
                 训练数据层
   ┌──────────┬──────────┬──────────┬──────────┐
   │ 图文对   │ 交错文档 │ 视频片段 │ 纯文本   │
   │(image-   │(web      │(video    │(text     │
   │ caption) │ page)    │ clip)    │ corpus)  │
   └────┬─────┴────┬─────┴────┬─────┴────┬─────┘
        │ 清洗/去重 │ 配权重采样 │ 长度过滤 │
        └──────────┴────┬─────┴──────────┘
                         ▼
              ┌──────────────────────┐
              │  模态编码器 (Encoder) │
              │ ViT / Whisper / BPE  │
              └──────────┬───────────┘
                         ▼
              ┌──────────────────────┐
              │  对齐投影 (Projector) │
              │ MLP / Resampler /    │
              │ Q-Former / Cross-Attn│
              └──────────┬───────────┘
                         ▼
              ┌──────────────────────┐
              │  LLM 主干 (Decoder)  │
              │ LLaMA / Qwen / ...   │
              └──────────┬───────────┘
                         ▼
              ┌──────────────────────┐
              │  多阶段训练课程       │
              │ 1.对齐 2.预训练 3.SFT │
              └──────────────────────┘

一、数据工程:配比与清洗是天花板

多模态训练最容易被低估的就是数据。文本侧有 CommonCrawl、Wikipedia、代码语料;视觉侧最重要的是图文对(LAION、COYO、CC12M、音视频侧的WebVid、AudioSet)。但原始数据噪声极大,工程上要做四件事。

环节 目标 常用手段 踩坑点
图文对质量过滤 去掉水印/NSFW/低信息量 CLIP 相似度阈值、OCR 文本长度、图像分辨率 阈值过严会丢长尾概念
文本侧去重 防记忆、降冗余 MinHash + LSH、Suffix Array 跨语言去重常被忽略
配权重采样 平衡模态与来源 指数滑动、loss 加权、temperature sampling 纯文本比例过高会拖慢视觉收敛
长度与格式归一 统一 token 预算 图像分辨率分桶、视频抽帧 视频帧数过多直接爆显存

一个关键工程经验:图文对数据决定了模型"看得见什么",纯文本数据决定了模型"说得好不好"。实践里常见配比是图文对 : 纯文本 ≈ 1 : 1 到 1 : 4,视频/音频作为增量补充。配比不是拍脑袋,要 ablation。

复制代码
# 多源数据加权采样器(简化版)
import bisect, random

class MultiSourceSampler:
    def __init__(self, sources, weights):
        # sources: [("image_text", 1.0), ("text", 4.0), ("video", 0.5)]
        self.sources = [s for s, _ in sources]
        total = sum(weights)
        self.cum = []
        acc = 0.0
        for w in weights:
            acc += w / total
            self.cum.append(acc)

    def next_source(self):
        r = random.random()
        idx = bisect.bisect_left(self.cum, r)
        return self.sources[min(idx, len(self.sources) - 1)]

# 训练时按 source 切换对应 DataLoader,实现软配比
sampler = MultiSourceSampler(
    [("image_text", 1.0), ("text", 4.0), ("video", 0.5)]
)

二、模态编码器与对齐投影

视觉侧几乎都用 ViT(CLIP/ViT-L/InternViT)。核心问题是:ViT 输出的 patch token 数量巨大(一张 336×336 图约 576 个 token),直接塞进 LLM 既不经济又容易让文本信号被淹没。于是需要一个投影模块把视觉特征压缩/对齐到 LLM 的语义空间。

常见三种投影结构:

结构 代表 参数量 特点
线性/MLP 映射 LLaVA-1.5 极少 简单,但 token 数不变
Resampler(P-Sampling) Flamingo / IDFF 用固定 N 个 query 重采样,压缩 token 数
Q-Former BLIP-2 / InstructBLIP 学习离散 query 聚合视觉语义
Cross-Attention Flamingo Perceiver 视觉作为 KV 注入 LLM 层

工程取舍:想要省 token、跑得快,用 Resampler 固定到 64~256 个视觉 token;想要保留细粒度,用 MLP 但接受长序列。训练顺序上通常先冻结 ViT 和 LLM,只训投影(对齐阶段),让视觉特征先落入语言空间,再解冻逐步联合训练。

复制代码
import torch
import torch.nn as nn

class MLPProjector(nn.Module):
    """把 ViT 输出 [B, N_img, D_v] 映射到 LLM 维度 D_l"""
    def __init__(self, d_vis=1024, d_llm=4096, hidden=4096):
        super().__init__()
        self.fc1 = nn.Linear(d_vis, hidden)
        self.act = nn.GELU()
        self.fc2 = nn.Linear(hidden, d_llm)

    def forward(self, x):
        # x: [B, N_img, D_vis]
        return self.fc2(self.act(self.fc1(x)))  # [B, N_img, D_llm]

class Resampler(nn.Module):
    """用固定 N_q 个可学习 query 重采样视觉特征,压缩 token 数"""
    def __init__(self, d_vis=1024, d_llm=4096, n_query=256, heads=16):
        super().__init__()
        self.query = nn.Parameter(torch.randn(1, n_query, d_llm))
        self.cross = nn.MultiheadAttention(d_llm, heads, batch_first=True)
        self.norm = nn.LayerNorm(d_llm)

    def forward(self, vis):
        # vis: [B, N_img, D_llm](已投影到 llm 维度)
        B = vis.size(0)
        q = self.query.expand(B, -1, -1)
        out, _ = self.cross(q, vis, vis)
        return self.norm(out)  # [B, N_query, D_llm]

三、多阶段训练课程

工业界几乎都采用分阶段"解冻"策略,而不是一把梭从头训。典型三阶段:

复制代码
阶段 1  对齐预训练 (alignment / pretrain-projector)
   冻结 ViT + LLM,只训 Projector
   数据:大规模图文对
   目标:让视觉 token 落入 LLM 语义空间
   学习率:~1e-3,batch 大

阶段 2  多模态联合预训练 (multimodal pretrain)
   解冻 LLM(ViT 可冻可微)
   数据:图文 + 交错文档 + 视频 + 纯文本混合
   目标:next-token prediction,跨模态指令理解
   学习率:LLM 1e-5 ~ 2e-5,Projector 1e-4

阶段 3  指令微调 (SFT)
   全参数或半参数(LoRA)微调
   数据:多模态指令数据(VQA、 caption、推理链)
   目标:对齐人类意图、抑制幻觉
   学习率:LoRA 2e-4,全参 1e-6

分阶段的价值在于:先对齐再联合,避免"视觉噪声直接冲击语言模型导致语言能力下降"(catastrophic forgetting of language)。实测中如果直接解冻全训,纯文本 benchmark 会掉 3~5 个点。

四、损失与注意力掩码工程

多模态训练的损失仍是交叉熵,但有一个工程细节:图像/视频 token 不应参与 loss 计算(它们是输入不是预测目标)。需要构造模态掩码,只对文本 token 计算 CE。

复制代码
def multimodal_ce(logits, labels, loss_mask):
    """
    logits: [B, T, V]
    labels: [B, T]  图像位置填 -100
    loss_mask: [B, T]  float,文本=1.0 图像/填充=0.0
    """
    loss_fct = torch.nn.CrossEntropyLoss(reduction="none")
    loss = loss_fct(logits.view(-1, logits.size(-1)), labels.view(-1))
    loss = loss.view(labels.size(0), labels.size(1))
    loss = (loss * loss_mask).sum() / (loss_mask.sum() + 1e-8)
    return loss

注意力层面,纯文本位置用因果掩码;图像 token 之间可用双向(因为它们是同时输入的一张图),图像对文本用因果。实现上用 4D 注意力掩码(batch, head, q, k)统一处理最干净。

五、分布式训练要点

多模态模型参数量大、序列长,训练并行要组合使用:

并行策略 适用场景 备注
数据并行 DP/FSDP 通用 配合梯度累积
张量并行 TP LLM 主干横向切 跨节点通信重
流水线并行 PP 深网络 视觉编码器单独放一张卡
序列并行 SP 长视频长文本 切序列维度省显存
冻结参数优化 ViT/LLM 冻结段 不存其梯度/优化器状态

一个实用架构:ViT 放在单独的 GPU(甚至不进 FSDP 包装),Projector 和 LLM 走 FSDP。视频训练时序列可能到 8k~32k token,必须用激活重计算(gradient checkpointing)+ 序列并行。

复制代码
# FSDP 包装 LLM 主干(PyTorch 2.x 风格)
from torch.distributed.fsdp import FSDP, MixedPrecision
from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy

llm_fsdp = FSDP(
    llm,
    auto_wrap_policy=size_based_auto_wrap_policy,
    mixed_precision=MixedPrecision(
        param_dtype=torch.bfloat16,
        reduce_dtype=torch.bfloat16,
        buffer_dtype=torch.bfloat16,
    ),
    use_orig_params=True,
    sharding_strategy="FULL_SHARD",
)
# ViT 不进 FSDP,单独 .cuda() 并设 requires_grad=False 在阶段1

六、典型故障与排查

现象 可能原因 排查方向
语言能力下降 解冻太早/纯文本占比低 检查 stage2 文本比例、加语言侧辅助 loss
视觉回答泛化差 图文对质量低/过拟合 caption 查 CLIP 相似度分布、增广
loss 不降 投影未对齐就联合训 退回 stage1 先训投影
OOM 视频帧/分辨率过高 降帧、序列并行、激活重算
对齐慢 学习率不当 投影 1e-3,LLM 1e-5

面试速答

问:为什么多模态训练要分阶段而不是直接端到端训?

答:直接训会导致视觉噪声冲击语言模型、纯文本能力灾难性遗忘,且投影未对齐时梯度混乱、loss 难降。分阶段先对齐投影、再联合、最后 SFT,稳定且保语言力。

问:Projector 有哪几类,怎么选?

答:线性/MLP(简单但 token 多)、Resampler(固定 query 压缩 token 数)、Q-Former(离散 query 聚合语义)、Cross-Attention(视觉作 KV 注入)。省显存选 Resampler,保细节选 MLP。

问:图像 token 要不要算 loss?

答:不算。图像/视频 token 是输入不是预测目标,用 loss_mask 只对文本位置算 CE,否则会强迫模型"预测像素特征"导致训练崩。

问:ViT 一般冻还是训?

答:阶段1冻结只训投影;阶段2通常微调 ViT 末端或全微(视数据量与算力);阶段3多数保持 ViT 冻结,避免过拟合。

高频追问清单

  1. 图文对数据噪声怎么量化评估?CLIP 相似度阈值怎么定?
  2. 纯文本占比多少合适?过小/过大分别有什么副作用?
  3. 视频多模态怎么处理时序?抽帧策略与位置编码怎么设计?
  4. 交错文档(web page)和图文对训练目标有何不同?
  5. 多阶段学习率怎么退火?warmup 比例怎么设?
  6. 序列并行和激活重算怎么组合用才不互相拖累?
  7. 训练中出现模态坍缩(只输出文本不看图)怎么救?
  8. 如何评估多模态预训练质量?除 benchmark 外的过程指标有哪些?
  9. 视觉 token 数从 576 压到 64,信息损失怎么补偿?
  10. LoRA 用在多模态 SFT 上,应该只挂 LLM 还是也挂 Projector?
相关推荐
长谷深风1112 小时前
AI记忆会过期,会冲突,更需要治理
人工智能·ai·大模型·prompt·memory·aiagent
thesky1234562 小时前
27届大模型面试准备(七十八):大模型推理线上问题定位与根因分析工程——TTFT/TBT 抖动、OOM、吞吐跌零排查手册
大模型·面试准备
ReleaseU3 小时前
数据库 MCP:让 Agent 自己查数据、写报表
人工智能·大模型
夏文强5 小时前
DeepSeek Harness 可观测性:用 OpenTelemetry 把会话遥测出去
人工智能·开源·大模型·agent·deepseek
夏文强6 小时前
DeepSeek Harness SDK 集成:把 Agent 嵌进你的应用
人工智能·开源·大模型·agent·deepseek
thesky12345615 小时前
27届大模型面试准备(七十一):多租户 GPU 虚拟化与推理隔离工程——MIG、MPS 与噪声邻居治理
大模型·mig·多租户·mps·gpu虚拟化·显存隔离·噪声邻居
虎虎(_ _)。゜zzZ1 天前
Qdrant向量数据库工程实战
数据库·人工智能·大模型·向量数据库·rag·qdrant
deepseek231 天前
GPT-6 Astra 发布拆解:Computer Use 产品化时刻、CoT 监控失守与 AGI 契约的终结
大模型·openai·ai agent
程序猿编码1 天前
基于GGML的C++17轻量化语音推理引擎:说话人识别与语音分析技术全解析
开发语言·c++·pytorch·深度学习·神经网络·大模型