8.4 多模态推理(VLM):图文混合输入的推理链路
VLM 推理链路(编码器→投影→LLM)、多模态输入处理、内容 hash 缓存与 multi_modal_uuids、Encoder Cache、主流 VLM 支持
LLM 只能吃文本,VLM(视觉语言模型)能”看图说话”。8.4 讲 VLM 的推理链路:图像怎么变成 LLM 能理解的 token、这个转换怎么缓存、以及 vLLM 对主流 VLM 的支持。核心工程问题是性能——视觉编码器(ViT)很重,如果每张图都重算编码,多模态推理的 TTFT 会很难看。缓存是解药。
📑 目录
- 1. VLM 推理链路:三件套
- 2. 多模态输入的处理流程
- 3. 内容 Hash 缓存:相同图片只编码一次
- 4. multi_modal_uuids:显式 ID 与”免上传”优化
- 5. Encoder Cache:编码器输出的跨请求复用
- 6. 与前缀缓存(APC)的配合
- 7. vLLM 对主流 VLM 的支持与选型
- 📝 总结
- 🎯 自我检验清单
- 📚 参考资料
1. VLM 推理链路:三件套
VLM = 视觉编码器 + 投影层 + LLM:
图片 ──→ [视觉编码器 ViT] ──→ 视觉特征 ──→ [投影层] ──→ 视觉 token ──┐
├──→ [LLM] ──→ 文本
文本 ──→ [tokenizer] ────────────────────────────────────→ 文本 token ──┘
- 视觉编码器(如 CLIP ViT):图片 → 视觉特征(patch 序列)
- 投影层(MLP/交叉注意力):视觉特征 → LLM 词表空间的视觉 token
- LLM:视觉 token 与文本 token 混合输入,自回归生成
📌 关键点:一张 224×224 图片切成 14×14 patch = 256 个 patch,经投影后产生几百个视觉 token——一张图 ≈ 数百 token 的”虚拟输入”。这意味着:图片会让 prefill 显著变长(TTFT 上升),也让 KV Cache 占用上升。多模态推理的延迟账,本质上就是”视觉 token 的账”。
2. 多模态输入的处理流程
vLLM 对多模态输入的处理分三步(每次请求都会走):
第 1 步:预处理(processor)
图片解码 → resize → normalize → 转 tensor
(CPU,多张图串行,可能成为瓶颈)
第 2 步:编码(encoder)
视觉编码器前向:图片 → 视觉特征
(GPU 计算,与 LLM prefill 争算力)
第 3 步:投影与拼接
视觉特征 → 视觉 token → 与文本 token 拼接 → LLM prefill
三个步骤里,第 1 步在 CPU、第 2 步在 GPU 且计算量大——这就是缓存要下手的地方。
💡 提示:高并发多模态场景的常见瓶颈排序:预处理(CPU)> 编码(GPU)> LLM prefill。图片多、请求并发高时,先看 processor 是否打满 CPU——这是多模态服务最容易忽略的瓶颈(和第 6 章的 DP 思路一样:先看利用率再调)。
3. 内容 Hash 缓存:相同图片只编码一次
3.1 原理
vLLM 默认对每个媒体项按内容哈希建立缓存键:
图片字节 → hash → 缓存键
│
├── 命中:直接用缓存的编码结果(跳过编码器!)
└── 未命中:编码 → 存入缓存 → 返回
相同图片(相同字节)在不同请求中只编码一次——重复图片是缓存红利。
3.2 命中率取决于业务
| 业务形态 | 图片重复度 | 缓存收益 |
|---|---|---|
| 电商商品图 | 高(同 SKU 反复被问) | 巨大 |
| 文档 OCR | 中(同页面多轮问) | 明显 |
| 随机用户上传图 | 低 | 有限 |
📌 关键点:内容 hash 的缓存键是图片字节本身——要求图片完全一致(同一文件、未重编码)。客户端如果对同一张图做不同压缩/旋转再上传,缓存就失效了。生产上,让上游统一图片处理管线(固定格式与尺寸),缓存命中率才能稳住。
4. multi_modal_uuids:显式 ID 与”免上传”优化
内容 hash 需要先收到完整图片才能算(上传、解码都是成本)。vLLM 提供显式 ID 方案:
4.1 基本用法
outputs = llm.generate({
"prompt": "USER: <image><image>\n描述区别。\nASSISTANT:",
"multi_modal_data": {"image": [img_a, img_b]},
"multi_modal_uuids": {"image": ["sku-1234-a", None]}, # None = 回退内容 hash
})
- 提供稳定 ID(如 SKU、图片库 ID),避免每次重新 hash 原始内容
- 列表项一一对应;None 表示该项回退到内容 hash
4.2 “免上传”模式
更激进:如果 ID 已命中缓存,可以不传图片内容:
outputs = llm.generate({
"prompt": "USER: <image>\n这是什么?\nASSISTANT:",
"multi_modal_data": {"image": [None]}, # 跳过真实图片
"multi_modal_uuids": {"image": ["sku-1234-a"]},
})
- 命中:直接复用缓存,省掉整张图的传输与预处理
- 未命中:请求失败(明确报错)——调用方需要处理”缓存失效重传”
💡 提示:免上传模式是带宽和延迟双优化(图片可能是 MB 级,上传 + 预处理都是成本),但把”缓存失效”变成了业务错误码。生产建议:客户端先尝试免上传,失败(未命中)再带上图片重试一次——类似 HTTP 的乐观缓存。
5. Encoder Cache:编码器输出的跨请求复用
内容 hash / UUID 缓存的是**“图片 → 编码结果”的映射**(编码器输出复用)。这与 vLLM 的 Encoder Cache(多模态编码器缓存,v0.6.x 引入)是同一目标的工程实现:
┌─────────────┐ 未命中 ┌──────────────────┐
│ 请求 │ ────────────→ │ 视觉编码器(GPU) │ → 视觉特征
│ (图片) │ └──────────────────┘
└─────────────┘
│ 命中
▼
┌──────────────────────────┐
│ Encoder Cache(显存/CPU) │ ──→ 视觉特征(直接复用)
└──────────────────────────┘
- 缓存内容:编码器的输出(视觉特征),而非原始图片
- 收益:跳过整个编码器前向(GPU 计算),TTFT 显著下降
- 位置:与 KV Cache 类似,可配置在 GPU/CPU 显存层级
📌 关键点:Encoder Cache 与 LLM 的 KV Cache 是两个独立的缓存层——前者省”图片编码”,后者省”文本 prefill”。多模态的完整缓存栈是:图片 hash/UUID → 编码器输出缓存 → 视觉 token 的前缀缓存(下一节)。每一层命中都在砍 TTFT。
6. 与前缀缓存(APC)的配合
第 2 章讲过 Automatic Prefix Caching:文本前缀相同则 KV 复用。多模态场景的配合方式是图像 hash 前缀缓存:
- 相同图片 + 相同提问前缀 → 视觉 token 序列相同 → 前缀缓存直接命中
- 多轮对话(同一图片反复问)的收益最大:第一轮编码 + prefill,后续轮次全部命中缓存
第 1 轮:图片 A + "这是什么?" → 编码 + prefill(全算)
第 2 轮:图片 A + "还有什么?" → 前缀(图片 A 的视觉 token)命中缓存
第 3 轮:图片 A + "颜色呢?" → 再次命中
💡 提示:两个前提条件(v0.26 文档明确):如果同时禁用多模态 processor 缓存和前缀缓存,用户提供的
multi_modal_uuids会被忽略。也就是说——缓存是层层配合的,单开某一层可能不生效。排查”缓存没生效”时,先检查这三层各自的开关状态。
7. vLLM 对主流 VLM 的支持与选型
7.1 支持的模型家族(v0.26)
- Qwen2-VL / Qwen2.5-VL:图像 + 视频,多图输入
- LLaVA 系列(LLaVA-1.5/1.6/Next):经典开源 VLM
- InternVL 系列:图像 + 视频
- Pixtral、Phi-3-Vision、MiniCPM-V 等:各生态代表
- Llama-3.2-Vision:Meta 官方视觉版
7.2 选型维度
| 维度 | 关注点 |
|---|---|
| 能力 | 多图?视频?OCR 精度? |
| 视觉 token 数 | 决定 TTFT 与 KV 成本(第 1 节) |
| 缓存支持 | 是否支持 Encoder Cache / hash 前缀缓存 |
| 生态 | 与你的框架/部署链路的兼容性 |
📌 关键点:视觉 token 数是 VLM 部署成本的核心指标——同样的图片,模型 A 产生 256 个视觉 token、模型 B 产生 1024 个,后者的 prefill 计算、KV 占用、缓存粒度都是 4 倍。选型时把”视觉 token 数”和”能力”放在同一优先级。
7.3 部署注意
- 显存:视觉编码器(ViT)也要占显存(几 GB 到十几 GB)
--limit-mm-per-prompt:限制单请求的图片/视频数量(防滥用、控 TTFT)- 多图场景:预处理串行是瓶颈(第 2 节),考虑图片规格统一
📝 总结
- 链路:ViT 编码 → 投影 → 与文本拼接进 LLM;一张图 ≈ 数百视觉 token
- 三步处理:CPU 预处理 → GPU 编码 → LLM prefill;CPU 预处理是隐藏瓶颈
- 内容 hash 缓存:相同图片只编码一次;图片字节必须完全一致
- multi_modal_uuids:显式 ID 免 hash;免上传模式省带宽(失效需重试)
- Encoder Cache:缓存编码器输出(视觉特征),跳过 GPU 编码
- 三层缓存栈:图片 hash → 编码器输出 → 视觉 token 前缀缓存;互相配合、缺一层可能失效
- 选型:视觉 token 数是成本核心;主流 VLM 家族 vLLM 基本全覆盖
🎯 自我检验清单
- 画 VLM 三件套链路,指出”一张图≈多少 token”怎么来的?
- 多模态请求的三步处理?哪步最容易成为瓶颈?
- 内容 hash 缓存失效的常见原因?(图片字节不一致)
- multi_modal_uuids 的”免上传”模式怎么用?失效怎么办?
- Encoder Cache 缓存的是什么?收益在哪一步?
- 三层缓存栈分别省什么?为什么”缺一层可能失效”?
- 选 VLM 时为什么把视觉 token 数当成本指标?
📚 参考资料
- vLLM 官方文档:Multimodal Inputs(含 Cached Inputs / multi_modal_uuids)(https://docs.vllm.ai/en/latest/features/multimodal_inputs.html)
- vLLM 官方文档:Supported Models(VLM 家族列表)(https://docs.vllm.ai/en/latest/models/supported_models.html)
- Liu et al., Visual Instruction Tuning(LLaVA)(https://arxiv.org/abs/2304.08485)
- Qwen2-VL / Qwen2.5-VL 技术报告(https://arxiv.org/abs/2409.12191)