跳转至

多模态 —— 架构视角

这一组文档回答的是模型层面的问题:像素和波形是怎么变成 token 的、视觉塔用什么目标训出来、各家 VLM 怎么把视觉接进 LLM、需要生成图像时走的是扩散还是自回归。它和 推理服务:从单请求推理到 SLO-aware 集群大规模训练的并行策略 —— 总览 分工不同,可以对照着看:

本章(多模态架构) serving / 并行策略
视角 架构 / 算法 infra / 调度与并行
核心对象 对比损失、patch token、connector、融合范式、去噪 ODE EPD 解耦、packing、cu_seqlens、encoder cache
变长 \(N = HW/P^2\) 从哪来、为什么逐样本波动 按 token 而不是按样本做负载均衡

前置知识

本章假设读者具备以下背景:

  • 知道 decoder-only Transformer 与 next-token prediction;相关的训练视角可对照 训练全景:从数据到权重更新
  • 熟悉 scaled dot-product attention 的 shape;不熟悉的可以先读 Attention 总览
  • 对比损失、ViT 的 patchify、diffusion 的前向/反向过程这几块知识,正文里会分别给出定义,不需要提前准备。

0. 整体图景

现代多模态模型大体上可以拆成三段:一个(或多个)模态 encoder,一个负责对齐的 connector,一个 LLM backbone,再加上一个可选的生成器。Encoder 把像素或波形压缩成一串 \(N\times D\) 的 embedding;connector 把这串向量的宽度对齐到 LLM 的 hidden size;至于这串向量最终以什么方式和文本交互——是直接拼进主序列,还是旁挂在 cross-attention 上——则由融合方式决定。这里的 \(N\) 由分辨率或时长决定,逐样本会剧烈波动,后面所有关于变长的讨论都从这里发端。

画成一条三段流水线大致是这样:

   ┌───────────────┐      ┌────────────────────────┐      ┌──────────────────┐
   │ modality       │     │ LLM backbone            │      │ modality          │
   │ encoder(s)     │ ──► │ (Transformer decoder)   │ ──► │ generator         │
   │ ViT / Whisper  │ emb │  prefill + decode       │ tok │ diffusion / AR    │
   └───────────────┘      └────────────────────────┘      └──────────────────┘
   像素/音频 → token          自回归理解 + 生成 text          可选:渲染回像素

三段各自的计算特征差别很大,这一点是后面 0608 三篇 infra 讨论的共同起点:

典型算子 算力画像 一次请求跑几遍 KV
encoder ViT / 双向 attention,窄而浅 compute-bound,一次性 1
backbone · prefill 大 GEMM、causal attention compute / 带宽混合 1
backbone · decode GEMV、逐 token memory-bound、串行 N 步 读+追加
generator DiT / U-Net,双向 compute-bound、迭代 20–1000

由于这四段的计算特征差别如此之大,没有任何单一的并行配置或 batch 大小能让它们同时被喂饱,训练和推理系统最终都会走向按 stage 解耦的方案。

全章还有两条相互独立的演化轴,会在多篇文档里反复出现:

轴 1 · 视觉怎么变成 LLM 能读的东西
  CLIP 双塔对比 ──► 冻结塔 + 可训 connector(Flamingo / BLIP-2 / LLaVA)
                 ──► 解冻 ViT + 动态分辨率(InternVL / Qwen2-VL)

轴 2 · 视觉信息在哪和文本交互
  decoder-only 拼接(LLaVA 家族,主流)
  cross-attention 旁挂(Flamingo / Llama 3.2 Vision)
  离散统一 token(Chameleon / Emu3);理解/生成解耦(Janus)

1. 这组文档怎么读

下面这张表给出了每篇文档大致覆盖的内容和对应的关键锚点,方便按需跳读:

文件 内容 锚点
README.md(本文) 整体图景、两条演化轴、与 serving/parallel 的分工、阅读顺序 ——
00 · 术语定义:encoder / projector / fusion / VLM 词汇辨析:VLM / encoder / projector / fusion / vision token / tile;三个都叫 projection 的东西 ——
01 · 对比预训练 视觉塔从哪来:CLIP 对称 InfoNCE、温度、batch 大小;SigLIP 的 pairwise sigmoid;为什么几乎所有开源 VLM 都从这里起步 CLIP Fig 1;SigLIP Eq. sigmoid
02 · Encoder:像素/音频到变长 token encoder 内部:ViT patchify 的数学、token 数 \(N=HW/P^2\)、变分辨率(NaViT / AnyRes / 动态 tile)、压缩(pixel-shuffle / Perceiver / Q-Former)、Whisper 与视频 clip_vit_model.py:L115
03 · 经典 VLM 按设计缺口串起 Flamingo、BLIP-2、LLaVA/1.5、InternVL、Qwen2-VL、DeepSeek-VL2、Janus,每节讲架构加训练配方 各论文 Fig 1
04 · 融合与 connector 融合设计空间:三种范式对 attention 形状与 KV 的影响;embedding 怎么被 scatter 进序列 llava_model.py:L482;sglang mm_utils.py
05 · 生成器:diffusion / DiT / 自回归图像 生成器的数学与算力画像:DDPM / CFG / DDIM / latent diffusion / DiT / flow matching / 自回归图像 DDPM Eq.14;DiT adaLN-Zero
06 · 异构与 stage 解耦 infra 问题之一:模型异构如何推导出 stage 解耦(DistTrain / EPD) add_encoder/add_decoder
07 · 变长输入与负载均衡 infra 问题之二:变长的 \(N\) 如何通过 packing、重排、token-balanced CP 摊平 greedy_knapsack
08 · 冗余、缓存与显存 infra 问题之三:encoder 是纯函数,因此可以按 mm_hash 缓存;image token 会撑大 KV EncoderCacheManager

比较自然的读法是:先读本文建立三段流水线和两条轴的印象,再读 00 把常用词的边界分清楚,接着读 01 弄清视觉塔的训练目标,然后读 02\(N\) 的公式过一遍,读 03 看各家沿着谱系怎么把视觉接进 LLM,读 04 把三种融合方式收进同一张设计表,最后读 05 看生成任务在哪里分叉。0608 三篇是把前面的架构事实推到 infra 上的自然结果,建议放在读完 0104 之后再看。

如果只关心算法、暂时不想碰 infra,读完 0005 就已经完整;0608 里每个设计动机其实都能追溯回 01 的对比损失、02 的 token 数量公式,以及 04 的融合范式,需要的时候再回来查也不迟。


2. 一组贯穿全文的数字

后面出现的各种 shape 和「贵不贵」的判断,基本都是从这张表里的几个具体模型量级引出来的:

CLIP ViT-L/14 LLaVA-1.5 Qwen2-VL InternVL 1.5(单 tile)
分辨率 \(224^2\) / \(336^2\) \(336^2\) 原生,像素预算内任意 \(448^2\) tile,最多 40
patch \(P\) 14 14 14 14
未压缩 \(N\) 256 / 576 576 \(HW/14^2\) 1024 / tile
压缩后 —— 576 \(\div 4\)(2×2 merge) 256(pixel-shuffle \(r{=}2\)
connector 对比空间的线性投影 两层 MLP MLP merge MLP
融合 双塔(不做生成) decoder-only 拼接 decoder-only 拼接 decoder-only 拼接

3. 代码映射表

想直接对照代码的话,可以从下面几个入口开始:

主题 文件 看什么
多模态模型装配 llava_model.py:L57 LLaVAModel:vision_model + projector + language_model;add_encoder/add_decoder
image embedding 注入 llava_model.py:L482 _preprocess_dataimage_token_index=-200 占位替换成 image embedding
projector multimodal_projector.py:L15 mlpaffine
vision encoder clip_vit_model.py:L26 conv1 patchify + class token + position embedding
scatter(推理) sglang mm_utils.py:L1022;vLLM utils.py:L479 masked scatter / _merge_multimodal_embeddings

4. 参考代码与论文

后面几篇会反复引用同一批参考代码,上游都固定在具体 commit(代码链接带 #Lx-Ly;Megatron pin 在 e03878b5f):

也会按阅读顺序陆续出现下面这些代表性论文:


接下来的一篇是 00 · 术语定义:encoder / projector / fusion / VLM,先把 encoder、projector、fusion、tile 这几个经常被混用的词厘清楚,后面的讨论才有共同的语言。