跳到主要内容
推理优化

8.4 多模态推理(VLM):图文混合输入的推理链路

VLM 推理链路(编码器→投影→LLM)、多模态输入处理、内容 hash 缓存与 multi_modal_uuids、Encoder Cache、主流 VLM 支持

多模态VLMEncoder Cache图片哈希多模态缓存Qwen-VL

LLM 只能吃文本,VLM(视觉语言模型)能”看图说话”。8.4 讲 VLM 的推理链路:图像怎么变成 LLM 能理解的 token、这个转换怎么缓存、以及 vLLM 对主流 VLM 的支持。核心工程问题是性能——视觉编码器(ViT)很重,如果每张图都重算编码,多模态推理的 TTFT 会很难看。缓存是解药。

📑 目录


1. VLM 推理链路:三件套

VLM = 视觉编码器 + 投影层 + LLM:

图片 ──→ [视觉编码器 ViT] ──→ 视觉特征 ──→ [投影层] ──→ 视觉 token ──┐
                                                                     ├──→ [LLM] ──→ 文本
文本 ──→ [tokenizer] ────────────────────────────────────→ 文本 token ──┘
  1. 视觉编码器(如 CLIP ViT):图片 → 视觉特征(patch 序列)
  2. 投影层(MLP/交叉注意力):视觉特征 → LLM 词表空间的视觉 token
  3. 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 数当成本指标?

📚 参考资料