vLLM 多模态输入:图像、视频与音频处理全解析
在生成式 AI 快速演进的今天,单一文本推理已无法满足复杂应用场景的需求。从智能客服中的图文问答,到教育平台上的音视频内容理解,再到工业质检中的视觉分析——多模态能力正成为大模型落地的关键一环。vLLM 作为高性能推理引擎,不仅在纯文本场景下表现出色,更通过深度优化支持图像、视频和音频等多种输入形式,在保证低延迟的同时实现高吞吐量服务。
这一切的核心,源于PagedAttention架构对传统注意力机制内存瓶颈的突破。不同于标准 Transformer 将所有 key-value 缓存连续存储的方式,vLLM 借鉴操作系统的虚拟内存分页管理思想,将 KV Cache 切分为固定大小的“页面”,按需分配与交换。这种设计使得显存利用率大幅提升,尤其在处理长序列或多模态输入时优势显著。结合连续批处理(continuous batching)和动态内存调度,vLLM 能够轻松应对图文混合、多帧视频甚至跨模态检索等复杂任务。
目前主流开源多模态模型如Qwen-VL、LLaVA系列、Phi-3.5-vision以及语音交互模型Ultravox均已在 vLLM 中得到良好支持。更重要的是,它内置了 OpenAI 兼容 API 接口,这意味着你无需重写现有应用逻辑,即可将强大的多模态推理能力无缝集成进生产系统。
如何高效传入多模态数据?
vLLM 的多模态输入遵循统一的数据结构规范,核心由两部分组成:文本提示prompt和多模态字典multi_modal_data。其中后者是一个类型为MultiModalDataDict的嵌套结构,用于传递图像、音频或预计算 embedding 等非文本信息。
一个常被忽视但极具实用价值的功能是UUID 缓存机制。当你需要对同一张图片发起多次查询(比如不同问题的视觉问答),重复传输原始像素数据显然不经济。vLLM 允许你为媒体指定稳定 ID(multi_modal_uuids),后续请求中只需提供 UUID 即可复用已缓存的特征表示:
from vllm import LLM from PIL import Image llm = LLM(model="Qwen/Qwen2.5-VL-3B-Instruct") img_a = Image.open("/path/to/product.jpg") img_b = Image.open("/path/to/updated_version.jpg") outputs = llm.generate({ "prompt": "USER: <image><image>\n比较这两款产品的外观差异。\nASSISTANT:", "multi_modal_data": {"image": [img_a, img_b]}, "multi_modal_uuids": {"image": ["sku_1001", None]} # 第一张图使用缓存标识 })下次再问关于sku_1001的问题时,即便只传第二张图也能触发缓存命中:
# 只更新变化的部分,节省带宽与编码开销 outputs = llm.generate({ "prompt": "USER: <image><image>\n新款有哪些改进?\nASSISTANT:", "multi_modal_data": {"image": [None, Image.open("new_model.jpg")]}, "multi_modal_uuids": {"image": ["sku_1001", "sku_1002"]} })当然,这个机制依赖于前缀缓存(prefix caching)开启;若显式关闭,则每次都会重新计算。
图像处理:从单图到多帧序列
最基础的应用莫过于单张图像的理解。使用 PIL 加载后直接注入multi_modal_data['image']字段即可:
import PIL.Image from vllm import LLM llm = LLM(model="llava-hf/llava-1.5-7b-hf") image = PIL.Image.open("example.jpg") outputs = llm.generate({ "prompt": "USER: <image>\n这张图里有什么?\nASSISTANT:", "multi_modal_data": {"image": image} }) print(outputs[0].outputs[0].text)对于批量处理多个独立图文对的任务,可以一次性提交列表形式的请求,vLLM 会自动进行批调度以提升 GPU 利用率:
requests = [ { "prompt": "USER: <image>\n这是什么动物?\nASSISTANT:", "multi_modal_data": {"image": PIL.Image.open("tiger.jpg")} }, { "prompt": "USER: <image>\n这个建筑风格属于哪个时期?\nASSISTANT:", "multi_modal_data": {"image": PIL.Image.open("cathedral.jpg")} } ] outputs = llm.generate(requests)当涉及多图理解——例如对比两张商品图、拼接多个监控画面或模拟视频帧流——则可通过列表传入多张图像。注意某些模型默认限制每轮对话最多处理 4 张图,需通过limit_mm_per_prompt参数调整:
llm = LLM( model="microsoft/Phi-3.5-vision-instruct", max_model_len=4096, limit_mm_per_prompt={"image": 4} ) images = [PIL.Image.open(f"frame_{i}.jpg") for i in range(2)] prompt = "<|user|>\n<|image_1|><|image_2|>\n请分别描述每张图的内容。<|end|>\n<|assistant|>" outputs = llm.generate({ "prompt": prompt, "multi_modal_data": {"image": images} })值得一提的是,vLLM 的chat()接口提供了更贴近真实对话的消息格式,允许你在一条消息中混合文本、图像 URL、PIL 对象乃至预计算 embedding:
from vllm.assets.image import ImageAsset import torch image_url = "https://picsum.photos/id/32/512/512" image_pil = ImageAsset('cherry_blossom').pil_image image_embeds = torch.load("precomputed_embeds.pt") conversation = [ {"role": "system", "content": "你是一个多模态助手"}, {"role": "user", "content": [ {"type": "image_url", "image_url": {"url": image_url}}, {"type": "image_pil", "image_pil": image_pil}, {"type": "image_embeds", "image_embeds": image_embeds}, {"type": "text", "text": "这些图像中有哪些共同主题?"} ]} ] outputs = llm.chat(conversation)对于透明 PNG 图像,默认情况下 vLLM 使用白色填充 alpha 通道区域。如果你的应用界面偏暗色系,可能会导致边缘失真。此时可通过media_io_kwargs自定义背景色:
llm = LLM( model="llava-hf/llava-1.5-7b-hf", media_io_kwargs={"image": {"rgba_background_color": (0, 0, 0)}} # 黑底 )值接受 RGB 元组或列表,范围 0–255,未设置时默认(255, 255, 255)。
至于视频理解,虽然并非所有模型都原生支持.mp4输入,但你可以将其视为一组关键帧来处理。例如 Qwen2-VL 支持通过采样若干帧并依次插入<image>标签实现粗粒度视频分析:
from vllm.utils import encode_image video_frames = load_video_frames("sample.mp4", num_frames=6) message = {"role": "user", "content": [{"type": "text", "text": "请描述这段视频的情节发展。"}]} for frame in video_frames: base64_img = encode_image(frame) message["content"].append({ "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_img}"} }) outputs = llm.chat([message])这种方式虽简单有效,但在时间连贯性建模上存在局限。真正的解决方案在于原生视频支持。
视频与音频:动态内容的端到端接入
随着 LLaVA-OneVision、Qwen2.5-VL 等模型的出现,vLLM 开始具备真正意义上的视频理解能力。这类模型内部集成了时空注意力模块,能够捕捉帧间运动模式。使用方式也极为直观:直接传入视频 URL 或本地路径即可。
以下是以 Qwen2.5-VL 为例的服务调用流程:
from transformers import AutoProcessor from vllm import LLM, SamplingParams from qwen_vl_utils import process_vision_info model_path = "Qwen/Qwen2.5-VL-3B-Instruct" video_url = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4" llm = LLM( model=model_path, gpu_memory_utilization=0.8, enforce_eager=True, limit_mm_per_prompt={"video": 1} ) messages = [ {"role": "user", "content": [ {"type": "text", "text": "请描述这个视频的内容"}, { "type": "video", "video": video_url, "total_pixels": 20480 * 28 * 28, "min_pixels": 16 * 28 * 28 } ]} ] processor = AutoProcessor.from_pretrained(model_path) prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) _, video_inputs = process_vision_info(messages) mm_data = {"video": video_inputs} if video_inputs else {} outputs = llm.generate([{ "prompt": prompt, "multi_modal_data": mm_data }], sampling_params=SamplingParams(max_tokens=1024)) print(outputs[0].outputs[0].text)这里的关键工具process_vision_info是 Qwen 官方提供的辅助函数,负责提取消息中的多媒体字段并转换为模型所需的张量格式。其他架构的模型可能需要自行实现类似逻辑。
音频方面,vLLM 支持将(numpy_array, sampling_rate)元组作为输入,适用于fixie-ai/ultravox-*系列模型:
import librosa llm = LLM(model="fixie-ai/ultravox-v0_5-llama-3_2-1b") audio_array, sr = librosa.load("speech.wav", sr=16000) outputs = llm.generate({ "prompt": "USER: <audio>\n请转录并总结这段语音内容。\nASSISTANT:", "multi_modal_data": {"audio": (audio_array, sr)} })客户端也可通过 base64 编码上传音频片段:
response = client.chat.completions.create( messages=[{ "role": "user", "content": [ {"type": "text", "text": "请转录并总结这段音频"}, { "type": "input_audio", "input_audio": { "data": base64.b64encode(audio_bytes).decode(), "format": "wav" } } ] }], model="fixie-ai/ultravox-v0_5-llama-3_2-1b" )或直接引用远程 URL:
{ "type": "audio_url", "audio_url": {"url": "https://example.com/audio.wav"} }各类媒体下载均有默认超时控制:
- 图像:5 秒(可通过VLLM_IMAGE_FETCH_TIMEOUT修改)
- 视频:30 秒(VLLM_VIDEO_FETCH_TIMEOUT)
- 音频:10 秒(VLLM_AUDIO_FETCH_TIMEOUT)
建议根据网络环境适当调整。
预计算 Embedding:跳过编码器的极致优化
在某些高并发场景下,重复运行视觉编码器(如 CLIP ViT)会造成不必要的算力浪费。如果前端系统已经完成了特征提取,完全可以通过注入 embedding 实现“零成本”图像输入。
假设你已有保存好的图像特征张量:
import torch image_embeds = torch.load("image_features.pt") # shape: (1, N, hidden_size) outputs = llm.generate({ "prompt": "USER: <image>\n基于此图像 embedding 回答问题。\nASSISTANT:", "multi_modal_data": {"image": image_embeds} })部分模型还需额外元信息。例如 Qwen2-VL 要求提供image_grid_thw描述空间布局:
mm_data = { "image": { "image_embeds": image_embeds, "image_grid_thw": torch.tensor([[1, 6, 6]]) # T x H x W } }MiniCPM-V 则需要原始尺寸列表:
mm_data = { "image": { "image_embeds": image_embeds, "image_sizes": [(512, 512), (256, 256)] } }在线服务中同样支持该模式。只需将 embedding 序列化为 base64 字符串上传:
import io embed_buf = io.BytesIO() torch.save(image_embeds, embed_buf) embed_b64 = base64.b64encode(embed_buf.getvalue()).decode() response = client.chat.completions.create( messages=[{ "role": "user", "content": [ {"type": "text", "text": "基于此图像特征回答问题"}, {"type": "image_embeds", "image_embeds": embed_b64, "uuid": "cached_img_001"} ] }], model="llava-hf/llava-1.5-7b-hf" )这一机制特别适合构建分级缓存体系:冷数据走原始图像解码,热数据直接加载 embedding,实现性能与灵活性的平衡。
在线部署:OpenAI 兼容 API 的无缝集成
vLLM 内置的 OpenAI 兼容接口极大简化了多模态服务上线流程。只需启动服务并指定模型路径,即可获得/v1/chat/completions标准端点:
vllm serve microsoft/Phi-3.5-vision-instruct \ --trust-remote-code \ --max-model-len 4096 \ --limit-mm-per-prompt '{"image": 2}'客户端使用标准openaiSDK 即可调用:
from openai import OpenAI client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1") response = client.chat.completions.create( model="microsoft/Phi-3.5-vision-instruct", messages=[{ "role": "user", "content": [ {"type": "text", "text": "图中有什么?"}, {"type": "image_url", "image_url": {"url": "https://example.com/nature.jpg"}} ] }] )值得注意的是,文本中无需手动添加<image>占位符,系统会自动根据 content 数组顺序拼接上下文。图像还可穿插在文本中间,实现细粒度控制:
"content": [ {"type": "text", "text": "左边是"}, {"type": "image_url", "image_url": {"url": "left.jpg"}}, {"type": "text", "text": ",右边是"}, {"type": "image_url", "image_url": {"url": "right.jpg"}} ]安全方面,强烈建议启用域名白名单防止 SSRF 攻击:
--allowed-media-domains upload.wikimedia.org github.com同时可在容器环境中禁用 HTTP 重定向:
export VLLM_MEDIA_URL_ALLOW_REDIRECTS=0对于本地文件访问,可通过--allowed-local-media-path限定可读目录,并使用file://协议调用:
vllm serve llava-hf/llava-1.5-7b-hf --allowed-local-media-path /data/images/{"type": "image_url", "image_url": {"url": "file:///data/images/test.jpg"}}整个架构的设计哲学很清晰:既保持与生态兼容,又不失底层可控性。无论是离线批量处理还是在线高并发服务,vLLM 都提供了足够灵活且高效的多模态支持方案。
这种高度集成的推理范式,正在推动智能应用向更自然、更丰富的交互形态演进。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考