文章

多模态 LLM Serving 生产实战:用 Parallel Media Decode、Encoder Cache 与 Encoder Disaggregation 压住视觉请求尾延迟

多模态大模型上线后,视觉请求的尾延迟往往被图片下载、解码、视觉编码与重复处理拖高。本文结合 Dynamo、vLLM 与 EPD 实践,给出媒体解码前移、Encoder Cache、独立 Encoder Worker、输入预算和阶段级观测指标的生产治理方法。

多模态 LLM Serving 生产实战:用 Parallel Media Decode、Encoder Cache 与 Encoder Disaggregation 压住视觉请求尾延迟

多模态大模型上线后,很多团队会惊讶地发现:明明 GPU 利用率不高,视觉请求的 P95/P99 首 token 延迟却持续恶化。问题往往不在语言模型本身,而在图片或视频进入模型之前的那段”隐形前置链路”——远程拉取、解码、resize/normalize、视觉编码以及 embedding 传输。本文结合 NVIDIA Dynamo、vLLM 与 EPD 实践,梳理一条可落地的视觉关键路径治理方法。

为什么多模态请求不能继续按”普通 LLM 请求”治理

纯文本 LLM 的在线路径相对清晰:请求进入调度器,完成 prefill,然后进入逐 token decode。视觉语言模型(VLM)则多了一段容易被忽视的前置链路:

HTTP / Base64 media
  ↓ fetch / decode / decompress
  ↓ resize / normalize / multimodal processor
  ↓ vision encoder
  ↓ multimodal embeddings
  ↓ LLM prefill + decode

这意味着一个看起来只是”多传一张图片”的请求,实际可能同时消耗网络带宽、CPU 解码线程、主机内存、视觉 Encoder GPU 算力和语言模型 GPU 算力。图片数量、分辨率、视频帧数和媒体来源一旦失控,P95/P99 TTFT 会先恶化,而平均吞吐未必马上暴露问题。

NVIDIA Dynamo 当前的多模态 Serving 文档已经把这条链路拆成若干可独立优化的环节,包括 Parallel Media Decoding、Embedding Cache、Multimodal KV Routing 与 Encoder Disaggregation。本文聚焦与”视觉输入前处理 / 编码阶段”直接相关的部分。

核心原则:先把视觉关键路径拆开,再决定优化手段

多模态性能治理的第一步不是改架构,而是把关键路径拆成可独立度量的阶段。只有先看清瓶颈在哪一段,才能决定是优化 CPU、加缓存,还是拆 Encoder。

1. 把图片获取与解码从推理 Worker 的关键路径前移

远程图片请求通常包含 URL fetch、Base64 decode、JPEG/PNG/WebP 解压等 CPU 与 I/O 工作。如果这些操作全部发生在模型 Worker 内部,GPU 可能在等待 CPU 准备输入,同时模型进程还要承担不可预测的网络延迟。

Dynamo 的 Parallel Media Decoding 将图片拉取、Base64 解码和图像解压移动到前端 CPU worker pool,并发完成后再把 decoded pixel buffer 交给推理后端。这个设计的价值不是”让视觉 Encoder 更快”,而是把不应该占据 GPU Worker 请求线程的媒体工作移出去。

适合优先启用的场景通常包括:

  • 请求经常携带 HTTP/HTTPS 图片;
  • 单请求含多张图片;
  • 图片压缩率高、解码开销明显;
  • 后端 CPU 已成为请求路径瓶颈;
  • GPU 利用率不高,但 TTFT 仍然长且波动大。

工程上不要只测总 TTFT,至少要独立记录以下指标:

media_fetch_ms
media_decode_ms
mm_processor_ms
vision_encode_ms
embedding_transfer_ms
llm_prefill_ms
tpot_ms

如果 media_fetch_ms + media_decode_ms 已占到视觉请求首 token 延迟的大头,就应该先处理前端解码与媒体来源,而不是直接增加 LLM GPU。

2. 把 Encoder Cache 当成独立缓存层,不要和 KV Cache 混为一谈

对于商品图、固定截图、共享流程图、同一图片的多轮问答等业务,请求内容可能不断变化,但媒体本身经常重复。每次都重新运行视觉 Encoder,会浪费大量重复计算。

Dynamo 的 Embedding Cache / Encoder Cache 使用 CPU 侧 LRU 缓存保存视觉 Encoder 输出,命中后可以跳过视觉编码。vLLM 也提供多模态 Processor Cache,并允许通过稳定的 multi_modal_uuids 避免每次重新对原始媒体内容做哈希。

需要特别区分三类缓存:

缓存缓存对象主要节省典型失效条件
MM Processor Cache预处理后的多模态输入resize、processor 等 CPU 工作Processor 参数、输入内容变化
Encoder / Embedding Cache视觉 Encoder 输出 embeddingVision Encoder GPU 计算Encoder、Processor、输入内容变化
KV CacheLLM attention key/valueLLM prefillPrompt/token 前缀变化

如果把三者合并成一个”缓存命中率”,线上排障几乎没有意义。建议分别观察命中率、容量、淘汰量和节省的阶段耗时。

缓存 key 也不能只使用图片 URL。CDN URL 可能不变而内容已更新,同一个 URL 在不同版本的图像 Processor 或视觉 Encoder 下也可能产生不同 embedding。生产环境更稳妥的 key 至少应包含:

media_content_hash + encoder_model_version + processor_version + processor_parameters

若业务使用稳定媒体 ID,应确保 ID 对应内容不可变,或者把内容版本号一起纳入 cache key。

3. 只有 Encoder 真正成为瓶颈时,再做 Encoder Disaggregation

多模态模型通常把视觉编码和语言模型推理放在同一个服务实例中。优点是部署简单,缺点是视觉请求突增时,Encoder 与 LLM 会在同一组 GPU 上争夺资源,而且两种阶段的资源需求不一定一致。

Encoder Disaggregation 的思路是把视觉 Encoder 单独放到 Encode Worker,得到 embedding 后再传给下游 LLM Worker。Dynamo 当前支持 E/PD、E/P/D 等拓扑,但从生产治理角度,最重要的是”Encoder 可以独立扩容和独立选硬件”,而不是为了拆分而拆分。

                ┌─ Encode Worker 1 ─┐
Media Decode ───┼─ Encode Worker 2 ─┼──> embeddings ──> LLM Worker Pool
                └─ Encode Worker N ─┘

适合拆分的信号包括:

  • vision_encode_ms 的 P95/P99 长期占据 TTFT 的显著比例;
  • Encoder 队列增长,而 LLM decode GPU 仍有余量;
  • 图片请求与文本请求的流量比例波动很大;
  • Vision Encoder 与语言模型适合不同规格 GPU;
  • 需要独立控制视觉阶段容量,避免图片流量拖慢纯文本流量。

反过来,如果业务只是低并发图片问答,Encoder 利用率很低,拆分后新增的 RPC、embedding transfer、部署拓扑和故障域可能比收益更大。聚合部署应当是默认基线,分离是瓶颈驱动的优化。

研究工作《Efficiently Serving Large Multimodal Models Using EPD Disaggregation》也验证了编码阶段独立配置资源的潜力。但论文中的 TTFT 和吞吐改善依赖模型、图片数量、硬件和流量形态,生产环境不应直接照搬论文收益率,应以自己的阶段级 profiling 为准。

输入预算比”后端硬扛”更重要

多模态接口天然存在请求放大问题:一张 512×512 图片和 20 张高分辨率图片完全不是同一种成本;一个 8 帧视频和一个数百帧视频也不是同一种负载。

vLLM 的多模态配置允许限制每个 prompt 的媒体数量,并为视频帧数、宽高等参数设置约束。生产环境应把这些限制提升为 API contract,而不是隐藏的引擎参数。

例如可以建立如下分层预算:

multimodal_budget:
  default:
    max_images: 4
    max_image_width: 2048
    max_image_height: 2048
    max_video_frames: 32
  trusted_batch_job:
    max_images: 16
    max_video_frames: 96

真正需要控制的不只是”文件大小”,还包括:

  • 图片数量;
  • 解码后像素总量;
  • 视频帧数;
  • 视频时长和采样策略;
  • 媒体下载超时;
  • 单请求累计媒体字节数;
  • Processor 产生的视觉 token 数量。

这些字段应同时进入请求日志和成本归因,否则多模态服务很难解释”为什么同样一次 API 调用,耗时和 GPU 成本差异数倍”。

媒体 URL 本身也是生产边界

支持 image_url、video_url 后,服务端通常会主动访问外部地址。这个能力如果没有 URL policy,会带来 SSRF 和内部网络探测风险。

Dynamo 当前文档明确给出了默认 URL 校验策略:默认允许 HTTPS 和 data URL,并阻止私网 / loopback 等内部地址;只有在明确的内网部署场景才建议放宽。无论采用哪套 Serving 框架,都应该建立类似规则:

  • 默认拒绝内网和 metadata endpoint;
  • 对重定向后的每一跳继续校验;
  • 设置 DNS、连接、读取和总下载超时;
  • 限制响应体大小和 MIME type;
  • 不允许任意本地文件路径;
  • 对可信内部媒体域名使用 allowlist。

这类校验应在媒体 fetch 层完成,不要等到模型 Worker 收到异常输入后再处理。

一套可落地的演进顺序

第一步:先建立阶段级基线

先不要改架构。给现有聚合部署增加阶段耗时、输入规模和资源指标,至少连续覆盖一轮真实高峰。重点指标包括:

  • media_fetch_ms / media_decode_ms / vision_encode_ms 的 P50/P95/P99;
  • Encoder queue depth;
  • Encoder GPU utilization / memory;
  • 图片数量、像素总量、视频帧数;
  • MM Processor Cache hit ratio;
  • Encoder Cache hit ratio;
  • 纯文本与多模态 TTFT 分布。

必须把纯文本和多模态请求拆开看。混合后的总体 P95 很容易掩盖视觉请求已经恶化的事实。

第二步:先解决 CPU / I/O,再动 GPU 拓扑

如果瓶颈集中在图片 fetch 和 decode,优先做并行媒体解码、连接池、超时、媒体代理或边缘缓存。此时扩 Encoder GPU 通常解决不了问题。

第三步:只有存在重复媒体时才上 Encoder Cache

先测重复媒体比例,再决定缓存容量。若 99% 请求都是唯一图片,10 GB 的 embedding cache 也不会产生理想收益,反而增加主机内存占用和失效治理成本。

Dynamo 文档说明其多模态 embedding cache 使用 CPU 侧 LRU;当前 vLLM 集成要求 vLLM 0.17.0 或更高版本。实际部署时应以当前版本兼容矩阵为准。

第四步:Encoder 饱和后再独立拆分

如果 profiling 显示 Encoder 是稳定瓶颈,再拆出 Encode Worker。拆分后重新验证:

TTFT = media + processor + queue(E) + encode + transfer + queue(LLM) + prefill

不要只看 encode 是否下降,还要确认新增的 queue(E) 和 transfer 没有吃掉收益。

适用场景

这套方法尤其适合:

  • 电商商品图问答,同一商品图片被大量重复访问;
  • OCR / 图像理解平台,单请求包含多张图片;
  • 企业文档截图、设计稿或流程图的多轮问答;
  • 视频理解服务,需要严格控制采样帧数;
  • 同一集群同时承载纯文本与视觉请求,希望避免视觉突发拖慢文本流量;
  • Vision Encoder 和语言模型对 GPU 型号、显存、算力需求差异明显的场景。

不太适合一开始就做复杂拆分的场景是低并发内部工具、媒体输入极少、没有明显视觉阶段瓶颈,或者业务仍处于快速验证期。此时保持单实例聚合部署通常更可控。

常见误区

误区一:TTFT 高就直接扩 LLM GPU。 多模态 TTFT 高可能是图片下载、解码或视觉 Encoder 阻塞。没有阶段指标时扩 GPU 只是昂贵的猜测。

误区二:只要有图片就开大缓存。 缓存收益取决于媒体重复率。唯一图片流量不会因为 LRU 更大而自动变快。

误区三:把 Encoder Cache 当成 Prefix / KV Cache。 它们缓存的对象、生命周期和正确性边界完全不同。Encoder 或 Processor 升级时,旧 embedding 通常不能继续无条件复用。

误区四:为了架构先进直接做 E/P/D 全拆分。 多一个阶段就多一个队列、传输链路、故障域和容量参数。先证明 Encoder 是瓶颈,再拆。

误区五:只限制上传文件大小。 压缩后的 JPEG 很小,解码后可能是大尺寸像素矩阵;视频文件大小也无法直接代表实际采样帧与 Encoder 成本。预算必须面向解码后的计算规模。

误区六:把媒体 URL 当成普通字符串。 一旦服务端主动 fetch URL,它就是网络访问能力。必须做协议、域名/IP、重定向、大小和超时控制。

上线检查清单

  • 已区分纯文本与多模态请求的 SLO;
  • 已拆分 media fetch、decode、processor、vision encode、transfer、prefill、decode 指标;
  • 已设置图片数量、分辨率、视频帧数等输入预算;
  • 已限制媒体下载超时、大小、协议与访问网段;
  • 已验证 MM Processor Cache 与 Encoder Cache 的命中率和失效策略;
  • Cache key 包含模型 / Processor 版本;
  • 已确认是否真的需要 Encoder Disaggregation;
  • 拆分后已测 embedding transfer 开销;
  • 已对纯文本流量做隔离回归,确认视觉峰值不会明显拖慢文本请求;
  • 升级视觉 Encoder 或 Processor 时有明确的缓存失效与灰度策略。

参考资料

  1. NVIDIA Dynamo — Multimodal Model Serving:https://docs.nvidia.com/dynamo/v1.4.1/multimodal/overview
  2. NVIDIA Dynamo — Parallel Media Decoding:https://docs.nvidia.com/dynamo/multimodal/parallel-media-decoding
  3. NVIDIA Dynamo — Embedding Cache:https://docs.nvidia.com/dynamo/user-guides/multimodal/embedding-cache
  4. NVIDIA Dynamo — Encoder Disaggregation:https://docs.nvidia.com/dynamo/dev/multimodal/encoder-disaggregation
  5. vLLM — Multimodal Inputs:https://docs.vllm.ai/en/latest/features/multimodal_inputs/
  6. Singh et al. — Efficiently Serving Large Multimodal Models Using EPD Disaggregation:https://arxiv.org/abs/2501.05460

常见问题

Encoder Cache 和 KV Cache 是一回事吗?
不是。Encoder Cache 缓存视觉或其他媒体编码器输出的 embedding,目标是跳过重复媒体编码;KV Cache 缓存语言模型注意力层的 key/value 状态,目标是减少重复 prefill。两者的 key、容量、失效和观测方式都应独立设计。
多模态服务一定要把 Encoder 独立拆成 Worker 吗?
不一定。低并发、图片较少或视觉编码不是瓶颈时,聚合部署更简单。只有编码队列、Encoder GPU 利用率或视觉阶段 P95/P99 明显成为瓶颈时,独立 Encoder Worker 才通常值得引入。
为什么不能只看整体 TTFT 判断多模态性能?
因为 TTFT 会把媒体下载、解码、Processor、视觉编码、embedding 传输和语言模型 prefill 混在一起。若不拆阶段指标,很容易把 CPU 解码瓶颈误判成 GPU 推理问题。