背景:VLM 推理不是”多传一个 image_url”这么简单
越来越多业务开始把 Vision-Language Model(VLM) 接入生产系统:票据识别、截图问答、质检巡检、商品图理解、客服工单图片分析、视频片段总结、UI 自动化测试、医疗或工业图像辅助分析等。
从 API 形态看,多模态调用似乎只是把文本消息里的 content 从纯字符串换成「文本 + 图片 URL」或「文本 + 视频 URL」。但真正上线后,VLM Serving 的风险点会很快暴露:
- 同样是 1 次请求,1 张 512px 图片和 6 张高清截图的成本完全不同。
- 视频输入会带来抽帧、解码、帧数限制、上传大小、预处理排队等额外变量。
- 高分辨率图片可能显著放大视觉 token 数量,影响延迟、显存和费用。
- 文本 LLM 的 QPS/RPM 限流不足以描述多模态请求的真实资源消耗。
- 输入预处理失败、图片格式异常、外链下载慢、视频文件过大,都会让服务尾延迟变差。
因此,VLM Serving 的核心不是”能不能调用模型”,而是能不能建立一套 视觉 Token 预算 + 输入治理 + 多阶段队列 + 成本归因 的生产体系。
关键事实:视觉输入最终也会变成模型上下文成本
多数 VLM 的工程路径可以概括为:
- 接收文本、图片或视频输入。
- 下载或读取媒体文件。
- 对图片做解码、缩放、裁剪、归一化。
- 对视频做抽帧或帧序列构造。
- 通过视觉编码器或多模态处理器生成视觉表示。
- 将视觉表示与文本 prompt 拼接或融合后送入语言模型。
- 输出文本回答。
vLLM 文档 中的 multimodal inputs 页面说明,它支持向多模态模型传入图片、视频、音频、embedding 和 cached inputs,并在示例中使用 limit_mm_per_prompt 或 --limit-mm-per-prompt.image 控制每个 prompt 可携带的多模态输入数量。SGLang 的 Vision API 文档 也展示了 OpenAI-compatible 的视觉输入格式,并支持多图和图文交错输入。OpenAI 的图片与视觉文档 则明确给出了 detail 等级、图像缩放和图像 token 成本计算规则。
这说明生产系统需要把 VLM 输入当作一种可计量、可预算、可调度的资源,而不是把图片/视频简单视为普通请求参数。
核心设计:在 Gateway 层建立视觉 Token 预算
VLM Serving 的第一层治理应该放在入口层,而不是等请求进入 GPU worker 后再处理。建议在 LLM Gateway 或 API Gateway 中加入一个 multimodal admission controller。
它至少需要做以下检查:
| 检查项 | 目标 | 处理策略 |
|---|---|---|
| 文件类型 | 阻止非预期媒体输入 | 只允许 jpg/png/webp/mp4 等白名单格式 |
| 文件大小 | 防止上传或下载拖垮服务 | 超限直接拒绝或要求压缩 |
| 图片数量 | 防止多图请求无限堆叠 | 按模型和租户配置上限 |
| 图片分辨率 | 控制视觉 token 成本 | 超限缩放、降 detail 或拒绝 |
| 视频时长 | 控制抽帧与预处理成本 | 限制秒数、帧数或只取关键片段 |
| detail 等级 | 平衡质量、成本和延迟 | 默认 low/auto,高精度场景显式申请 |
| 租户预算 | 做成本归因和配额治理 | 按视觉 token 估算值扣减预算 |
一个实用的预算模型可以从粗粒度开始:
estimated_visual_cost = image_count × image_detail_factor × resolution_factor
+ video_frame_count × frame_detail_factor
+ preprocessing_cost_factor
这不是最终计费公式,而是用于准入控制和排队调度的工程估算。真正上线后,可以用模型返回的实际 usage、GPU 指标和预处理耗时不断校准这个估算值。
输入治理:先标准化,再进入推理队列
VLM 服务通常不应该让 GPU worker 直接面对原始图片或视频。更稳妥的做法是拆成三个阶段。
第一阶段:媒体接入与安全检查
入口层负责校验 URL、Content-Type、文件大小、媒体格式和租户权限。对于外部图片 URL,建议设置下载超时、重定向限制、私网地址拦截、最大响应体大小和 MIME sniffing,避免 SSRF、慢下载和异常文件拖垮服务。
第二阶段:预处理与视觉预算估算
预处理层负责图片解码、EXIF 处理、透明背景处理、缩放、裁剪、视频抽帧、异常帧过滤和视觉 token 估算。这个阶段可以使用 CPU worker 或独立的预处理池,不要和 GPU 推理 worker 混在一起。
第三阶段:多模态推理队列
只有经过治理后的结构化输入才进入推理队列。例如:
{
"tenant_id": "enterprise-a",
"model": "qwen2.5-vl-7b-instruct",
"text_tokens_estimated": 860,
"image_count": 2,
"video_frame_count": 0,
"detail": "low",
"visual_tokens_estimated": 1200,
"priority": "standard"
}
这样,调度器才能根据文本 token、视觉 token、租户优先级和 GPU 状态做更合理的排队,而不是只看请求数。
为什么图片、视频和文本请求要分队列
文本 LLM 服务中,一个请求通常主要由 prompt tokens、output tokens 和 KV cache 占用决定。VLM 服务还多了媒体下载、解码、图像处理、视频抽帧和视觉编码阶段。
如果所有请求进入同一个队列,可能出现以下问题:
- 大视频请求阻塞普通图片问答。
- 多图请求拉高排队延迟,影响轻量截图问答。
- 图片下载失败或慢下载占住推理入口。
- 视觉编码器成为瓶颈,但 GPU 语言模型仍有空闲。
- 成本归因只能看到”请求数”,看不到视觉输入导致的资源差异。
更好的策略是分成四类队列:
- download queue:处理外链下载和上传文件落地。
- preprocess queue:处理解码、缩放、抽帧和预算估算。
- vision encoder queue:处理视觉编码或图像 embedding。
- llm inference queue:处理最终文本生成。
这四类队列的指标要分开观测。尤其要关注:
| 指标 | 说明 |
|---|---|
| preprocess p95 | 预处理阶段 P95 延迟 |
| vision encoder queue length | 视觉编码器排队长度 |
| visual token estimate | 视觉 token 估算值与实际值的偏差 |
| media fetch failure rate | 媒体下载失败率 |
| video frame count distribution | 视频抽帧数分布 |
| detail level distribution | 各 detail 等级请求占比 |
detail 等级:不要默认把所有图片都按最高精度处理
OpenAI 图片与视觉文档中把 detail 分为 low、high、original、auto 等层级,并说明 low 适合不需要细粒度视觉细节的快速低成本理解;部分模型会根据 detail 等级和图像尺寸采用不同的缩放、patch 或 tile 计算方式。
这对自建 VLM Serving 有直接启发:
- 截图大意总结、图片分类、是否包含某类物体:默认 low 或自动降采样。
- OCR、表格、票据、合同截图:允许 high,但要限制分辨率和张数。
- UI 点击定位、密集图表理解、工业质检细节:允许更高 detail,但必须走更高预算和更严格限流。
- 视频总结:先做抽帧策略,不要把所有帧都送入模型。
生产环境里,detail 不应该只是客户端随便传的参数,而应该由服务端根据业务场景、租户等级、预算和模型能力共同决定。
视频输入:真正的成本通常在抽帧策略上
视频 VLM 服务比图片更容易失控。一个 30 秒视频,如果按每秒 1 帧抽取就是 30 帧;如果按每秒 5 帧抽取就是 150 帧。即使模型支持视频输入,工程上也要明确:
- 最大视频时长。
- 最大抽帧数量。
- 抽帧间隔或关键帧策略。
- 是否允许多视频输入。
- 视频分辨率上限。
- 是否异步处理长视频。
- 是否把长视频任务转成批处理任务。
vLLM 文档中给出了 video input 的用法示例,但”支持传入视频”不等于”生产环境可以无限制传入视频”。上线时要把视频请求当作高成本任务单独治理。
工程落地架构
推荐架构如下:
Client → LLM Gateway → Media Admission Controller → Media Fetcher / Uploader
→ Image & Video Preprocessor → Visual Token Estimator
→ Multimodal Scheduler → VLM Worker Pool → Usage Recorder / Cost Attribution
每一层的职责要清晰:
- Media Admission Controller:做格式、大小、数量、分辨率、detail 和预算检查。
- Media Fetcher:下载外部 URL,设置超时、大小限制和安全隔离。
- Preprocessor:统一图片方向、色彩、缩放、视频抽帧和异常处理。
- Visual Token Estimator:在进入推理前估算视觉输入成本。
- Multimodal Scheduler:按视觉 token、文本 token、租户优先级和队列状态调度。
- VLM Worker Pool:执行真正的视觉编码和文本生成。
- Usage Recorder:记录实际 token、耗时、失败原因和租户成本。
常见误区
误区一:只按 QPS 做限流
VLM 请求的成本差异远大于普通文本请求。1 QPS 的高清多图 OCR 可能比 20 QPS 的短文本问答更重。限流至少要加入视觉 token 估算、图片数量、视频帧数和 detail 权重。
误区二:把图片预处理放在 GPU worker 里
这会让 GPU worker 同时承担下载、解码、缩放、异常处理和推理逻辑,导致故障边界混乱。预处理应该前置并可水平扩展。
误区三:所有业务共用一个 detail 默认值
不同业务对视觉细节的需求差异很大。客服图片分类和票据 OCR 不应该使用同一套 detail 策略。
误区四:忽视失败输入的成本
坏图、慢 URL、超大图、损坏视频、错误 MIME 类型都会消耗系统资源。失败请求同样要记录成本和频率,否则排障时只会看到”模型慢”。
上线检查清单
上线前至少检查以下项目:
- 是否限制单请求图片数量、图片大小和图片分辨率。
- 是否限制视频大小、时长、帧数和抽帧策略。
- 是否有服务端 detail 策略,而不是完全信任客户端。
- 是否把下载、预处理、视觉编码和文本生成拆成独立指标。
- 是否记录
visual_tokens_estimated、actual_tokens、preprocess_latency、vision_encoder_latency。 - 是否能按租户、模型、业务场景统计多模态成本。
- 是否对外链图片做 SSRF 防护和下载超时。
- 是否对高成本请求做排队、降级或异步化。
- 是否有用于回归测试的图片/视频样本集。
- 是否能在模型版本、processor 版本或抽帧策略变更后对比输出差异。
结论
VLM Serving 的生产难点不只是”模型是否支持图片或视频”,而是如何让图片、视频和文本输入都变成可治理的资源。
一套稳定的多模态推理服务,应当在入口层完成媒体安全检查,在预处理层完成标准化和视觉 token 估算,在调度层区分轻重请求,在观测层记录真实成本与失败原因。只有这样,团队才能在多图、高清截图、视频抽帧和不同租户混跑的情况下,稳定控制延迟、显存、成本和服务质量。
主要参考资料
- vLLM Multimodal Inputs
- SGLang OpenAI APIs - Vision
- OpenAI Images and Vision Guide
- LLaVA-OneVision: arXiv:2408.03326
- LLaVA-Mini: arXiv:2501.03895
- Flamingo: a Visual Language Model for Few-Shot Learning: arXiv:2204.14198