vLLM部署视觉语言模型:从原理到实践的多模态推理优化
1. 从文本到视觉:为什么我们需要 vLLM for Vision-Language?
最近在折腾大模型推理服务,特别是那些能“看图说话”的多模态模型时,我发现了一个挺有意思的现象。很多朋友在用 vLLM 部署纯文本模型时,感觉如鱼得水,速度快、吞吐高,管理也方便。但一旦把模型换成像 Qwen-VL、LLaVA 这类视觉语言模型,直接套用原来的 vLLM 配置,要么直接报错,要么推理结果莫名其妙。这背后其实反映了一个核心问题:vLLM 最初是为纯语言模型设计的,它的核心优化,比如 PagedAttention 和高效的 KV Cache 管理,都是围绕文本序列的“一维”特性构建的。而视觉语言模型引入了图像这个“二维”甚至更高维度的输入,整个计算图和数据流都发生了根本性的变化。
简单来说,纯文本模型处理的是[batch_size, sequence_length]这样的张量。vLLM 的 PagedAttention 机制可以像操作系统管理内存分页一样,高效地管理这些序列的键值对缓存,实现不同请求间显存的灵活复用,这是它高性能的基石。但视觉语言模型呢?它的输入变成了[batch_size, num_patches, hidden_dim]或者更复杂的结构。图像经过视觉编码器(如 Vision Transformer)处理后,会产生一个视觉特征序列。这个序列的长度(patch 数量)是固定的,但它的“语义”和文本 token 完全不同,其对应的 KV Cache 的生命周期、复用策略,以及如何与后续的文本生成过程协同,都是全新的挑战。
所以,当我们谈论“vLLM 学习 Vision Language”时,我们真正要探讨的是:如何让这个为文本而生的高效推理引擎,去理解并妥善处理来自视觉世界的信号。这不仅仅是安装一个支持多模态的 vLLM 版本那么简单,它涉及到对模型架构的深入理解、对 vLLM 内部机制的适配,以及对部署过程中各种“坑”的预判和规避。接下来,我就结合自己从零部署 Qwen-VL-Chat 和 LLaVA 的经历,拆解这里面的核心环节和实操细节。
2. 环境准备与 vLLM 的“多模态”版本选型
工欲善其事,必先利其器。部署视觉语言模型的第一步,就是搭建一个正确的环境。这里最大的误区就是直接pip install vllm。默认安装的 vLLM 是面向纯文本模型的,缺乏对视觉编码器的必要支持。
2.1 核心依赖:FlashAttention 与 Triton
视觉语言模型的计算负担极重,尤其是视觉编码部分。为了获得可接受的推理速度,FlashAttention(用于优化注意力计算)几乎是必选项。而 vLLM 的某些定制化算子依赖于 Triton 编译器。
# 一个相对稳妥的安装顺序(以 Ubuntu 22.04, CUDA 12.1 为例) # 1. 创建并激活虚拟环境 conda create -n vllm-vl python=3.10 -y conda activate vllm-vl # 2. 安装 PyTorch 及其对应 CUDA 版本 pip install torch==2.1.2 torchvision==0.16.2 torchaudio==2.1.2 --index-url https://download.pytorch.org/whl/cu121 # 3. 安装 FlashAttention-2 # 这一步是关键,确保你能从源码编译,以获得最佳性能和对新架构(如 Ada Lovelace)的支持 pip install packaging ninja pip install flash-attn==2.5.0 --no-build-isolation # 4. 安装支持多模态的 vLLM # 注意:截至我写这篇文章时,vLLM 的主分支(v0.3.0+)已更好地集成了多模态支持。 # 推荐从源码安装以获取最新特性。 git clone https://github.com/vllm-project/vllm.git cd vllm pip install -e . # 使用 `-e` 便于后续调试和更新注意:如果你遇到
“ERROR: Could not build wheels for flash-attn”,这通常是因为 CUDA 工具链版本不匹配。请务必检查你的nvcc --version和python -c “import torch; print(torch.version.cuda)”输出是否一致。不一致的话,需要调整 PyTorch 或系统 CUDA Toolkit 的版本。
2.2 模型下载与准备:Hugging Face 与模型协议
视觉语言模型通常由两部分组成:一个视觉编码器(如 CLIP-ViT)和一个语言模型。在 Hugging Face Hub 上,它们通常被集成在一个仓库里。
# 一个简单的模型下载和测试脚本,用于验证环境 from transformers import AutoProcessor, AutoModelForVision2Seq import torch model_id = “qwen/Qwen-VL-Chat” # 以 Qwen-VL-Chat 为例 # 或者 “liuhaotian/llava-v1.6-vicuna-7b” processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True) model = AutoModelForVision2Seq.from_pretrained( model_id, torch_dtype=torch.float16, # 使用半精度节省显存 device_map=“auto”, # 让 transformers 自动分配设备 trust_remote_code=True # 很多多模态模型需要此选项 ) # 测试一下处理器是否能正确处理图像和文本 # 这会验证视觉编码器和 tokenizer 是否加载正常 print(“Processor and model loaded successfully.”)这里有个关键点:trust_remote_code=True。因为许多前沿的多模态模型使用了自定义的建模代码(在modeling_xxx.py中),不信任远程代码就无法加载。你需要确认你了解并接受该模型的许可证协议。
2.3 vLLM 的多模态支持现状:从补丁到原生集成
早期的 vLLM 完全不具备多模态能力。社区和研究者们想出了各种“打补丁”的方法,比如:
- 预处理分离:先用单独的脚本运行视觉编码器,将图像编码成特征向量保存下来,然后将这些特征作为“伪 tokens”的嵌入输入给 vLLM。这种方法笨拙且破坏了端到端的流程。
- 定制化 Engine:修改 vLLM 的
LLMEngine类,在_run_workers方法中插入图像编码步骤。这对代码侵入性强,升级维护困难。
好消息是,vLLM 团队已经意识到了这个强烈的需求,并从架构上开始支持。在v0.3.0版本左右,vLLM 引入了MultiModalLLMEngine的概念和更灵活的Worker与Scheduler设计。核心思想是:将视觉编码器也视作一个特殊的“Worker”,它产生的视觉特征序列,会被调度器当作一种特殊类型的“输入块”进行管理,并与文本 tokens 的 KV Cache 进行协同调度。
因此,在选型时,我强烈建议你使用 vLLM 的最新源码或nightly 版本。较老的版本(如 v0.2.x)可能需要大量 hack 才能工作,而新版本提供了更清晰的支持路径。你可以通过查看 vLLM 源码中是否存在vllm.entrypoints.multi_modal或相关文档来确认。
3. 视觉输入的“表示”难题:从像素到模型可理解的 Token
这是整个流程中最核心的技术点之一。文本有 Tokenizer,那图像呢?vLLM 如何知道如何处理一张图片?
3.1 视觉编码器与 Patch 嵌入
现代视觉语言模型(如 LLaVA、Qwen-VL)普遍采用 Vision Transformer 作为视觉编码器。其处理流程是:
- 图像分块:将输入图像 resize 到固定尺寸(如 224x224),然后切割成一系列固定大小的图像块(例如 14x14)。
- 线性投影:每个图像块被展平成一个向量,并通过一个可训练的线性层(
Patch Embedding)映射到与语言模型隐藏层相同的维度。 - 添加位置编码:为这些图像块序列添加位置信息,因为 Transformer 本身是置换不变的。
- 通过 ViT 编码器:这些带有位置信息的 patch embeddings 经过多层 Transformer Encoder,输出最终的视觉特征序列。
假设输入图像被分成N个 patch,那么视觉编码器的输出就是一个形状为[N, D]的矩阵,其中D是隐藏层维度。这个N就是视觉序列的长度。
3.2 vLLM 中的多模态输入协议
vLLM 的 API 最初只接受List[str]作为输入。为了支持图像,它需要扩展输入协议。目前,一种被社区和 vLLM 自身逐渐采纳的方式是,使用一个结构化的字典列表来表示多模态输入。
# 一个示例,展示 vLLM 未来可能支持的多模态输入格式 from vllm import MultiModalRequest # 假设的请求格式 multi_modal_prompts = [ { “type”: “text”, “content”: “请描述这张图片。” }, { “type”: “image”, “content”: “/path/to/your/image.jpg”, # 或者是 base64 编码的字符串 “image_size”: (224, 224) # 可选的预处理尺寸 }, { “type”: “text”, “content”: “图片中最重要的物体是什么?” } ] # 在 engine 内部,调度器会识别 “image” 类型, # 将其路由到视觉编码器 worker 进行处理,生成视觉特征 tokens, # 然后将这些特征 tokens 和文本 tokens 按顺序拼接,形成完整的输入序列。在实际操作中,你可能需要根据你使用的 vLLM 版本和模型,编写一个适配层。这个适配层的核心工作就是:
- 接收原始的图像路径或像素数据。
- 调用正确的
Processor(它包含了视觉编码器和 tokenizer)对图像进行预处理。 - 将处理后的视觉特征(
vision_features)和文本 token ids按照模型要求的格式组织起来。 - 将这个整合后的输入,传递给一个经过修改、能理解此格式的 vLLM Engine。
3.3 KV Cache 对视觉序列的特殊处理
这是 vLLM 性能优化的关键,也是多模态适配的难点。对于文本 token,vLLM 的 PagedAttention 可以精细管理每个 token 的 KV Cache,支持高效的并行采样和请求间缓存共享。
但对于视觉特征序列,情况不同:
- 静态性:同一张图像的视觉特征序列在对话过程中是不变的。无论用户后续问多少轮关于这张图的问题,视觉编码只需要做一次。
- 长序列:视觉序列长度
N可能很长(例如 256 或 576),远超一个普通单词 token 的“长度”。 - 生命周期:视觉特征的 KV Cache 应该与整个对话会话(session)绑定,而不是与单个生成请求绑定。
因此,一个理想的多模态 vLLM 调度器,需要能够:
- 识别并缓存视觉特征:将图像输入的视觉特征序列计算出来,并将其 KV Cache 存储在一個特殊的、可复用的缓存池中。
- 建立会话关联:当同一个会话中后续的文本请求到来时,调度器能快速定位并关联到之前已计算的视觉 KV Cache,而无需重新编码图像。
- 跨请求拼接:将缓存的视觉 KV Cache 与当前请求的文本 prompt 的 KV Cache 在逻辑上正确拼接,输入给语言模型进行生成。
这要求对 vLLM 的Scheduler和BlockManager进行深度定制,也是目前许多“vLLM 多模态部署方案”的核心工作。
4. 实战部署:以 Qwen-VL-Chat 为例的端到端流程
理论说了这么多,我们来点实际的。下面我将演示一个相对稳定、基于当前(某个时间点)vLLM 源码的部署方案。请注意,具体细节可能随 vLLM 版本更新而变化,但核心思路是相通的。
4.1 方案选择:使用定制化的vllm.entrypoints
由于完全原生的多模态支持仍在演进中,我们采用一种折中但有效的方案:基于 vLLM 源码,编写一个自定义的入口脚本。这个脚本的核心是继承并扩展LLMEngine,手动处理图像编码与特征注入的流程。
步骤一:创建自定义引擎脚本multi_modal_engine.py
# multi_modal_engine.py import torch from PIL import Image from transformers import AutoProcessor from vllm import LLMEngine, SamplingParams from vllm.worker.worker import Worker from typing import List, Union, Dict, Any import base64 from io import BytesIO class MultiModalLLMEngine(LLMEngine): def __init__(self, model_id: str, **kwargs): # 首先,像普通 LLMEngine 一样初始化 super().__init__(model=model_id, **kwargs) # 然后,额外加载多模态处理器 self.processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True) self.vision_encoder = self.processor.image_processor # 获取视觉编码器组件 # 注意:有些模型处理器是集成的,可能需要调用 `feature_extractor` 或 `image_processor` # 这里需要根据具体模型调整 # 创建一个缓存字典,用于存储会话的视觉特征 # Key: session_id, Value: 视觉特征张量 self.vision_cache = {} def _encode_image(self, image_data: Union[str, Image.Image]) -> torch.Tensor: """将图像编码为视觉特征。""" if isinstance(image_data, str): # 假设是文件路径 image = Image.open(image_data).convert(“RGB”) elif isinstance(image_data, Image.Image): image = image_data else: raise ValueError(f“Unsupported image data type: {type(image_data)}”) # 使用处理器的视觉部分进行编码 # 这里需要根据具体模型调整预处理调用方式 # 例如,对于 Qwen-VL,可能是: # vision_inputs = self.vision_encoder(image, return_tensors=“pt”) # 这里仅为示意,实际调用需查阅模型文档 vision_inputs = self.processor(images=image, return_tensors=“pt”) # 将特征转移到模型所在的设备 vision_features = vision_inputs[“pixel_values”].to(self.device) return vision_features def generate_with_image( self, session_id: str, prompt_text: str, image_data: Union[str, Image.Image, None] = None, sampling_params: SamplingParams = None, ) -> str: """ 核心生成方法。 session_id: 用于关联同一对话中的图像和文本。 prompt_text: 用户输入的文本提示词。 image_data: 本次请求附带的图像(如果是对话中的第一轮)。 """ # 1. 处理图像(如果提供了新图像) if image_data is not None: vision_features = self._encode_image(image_data) # 将视觉特征存入缓存,关联到此 session_id self.vision_cache[session_id] = vision_features print(f“Encoded and cached vision features for session {session_id}”) # 2. 准备完整的模型输入 # 这里是最复杂的一步:需要将缓存的视觉特征和文本 prompt 结合起来, # 构造成模型 forward 方法所期望的输入格式。 # 这通常意味着要手动构造 `input_ids`, `attention_mask`, 和 `image_features` (或 `pixel_values`)。 # 首先,处理文本 text_inputs = self.processor( text=prompt_text, return_tensors=“pt”, padding=True, truncation=True ) input_ids = text_inputs[“input_ids”].to(self.device) # 3. 构建一个“伪”的请求数据,其中包含视觉特征信息 # 由于标准的 vLLM engine 不支持,我们需要“欺骗”一下。 # 一种方法是将视觉特征通过一个特殊的 `extra_inputs` 字典传递下去, # 这需要修改 worker 的 `execute_model` 方法以接收和使用这些额外输入。 # 这是一个深度定制点,需要修改 vllm 内部代码。 # 为简化示例,我们假设已经修改了 worker,使其能接受 `image_features` 参数。 # 在实际中,你可能需要重写 `Worker.execute_model` 和 `ModelRunner` 的相关部分。 data = { “prompt_token_ids”: input_ids.tolist(), # vLLM 期望的格式 “multi_modal_data”: { “session_id”: session_id, “image_features”: self.vision_cache.get(session_id) # 可能为 None } } # 4. 将请求加入引擎并执行(这里需要调用非公开的 _add_request 等方法, # 或者使用一个包装好的异步循环) # 示例流程(伪代码): request_id = self._get_new_request_id() # self._add_request(request_id, data, sampling_params) # self._run_engine_step() # output = self._get_outputs(request_id) # 由于涉及大量内部 API 调用,此处省略具体实现细节。 # 完整的实现需要深入研究 vLLM 的请求生命周期管理。 return “[Placeholder for generated text]” # 使用示例(概念性) if __name__ == “__main__”: engine = MultiModalLLMEngine( model_id=“qwen/Qwen-VL-Chat”, tensor_parallel_size=1, # 根据 GPU 数量调整 gpu_memory_utilization=0.9, trust_remote_code=True, ) sampling_params = SamplingParams(temperature=0.8, top_p=0.95, max_tokens=512) # 第一轮对话,带图片 session = “user_123_conversation_1” response1 = engine.generate_with_image( session_id=session, prompt_text=“描述这张图片。”, image_data=“cat.jpg”, sampling_params=sampling_params ) print(f“Round 1: {response1}”) # 第二轮对话,基于同一张图片提问 response2 = engine.generate_with_image( session_id=session, prompt_text=“图片中的猫是什么颜色的?”, image_data=None, # 不提供新图片,使用缓存 sampling_params=sampling_params ) print(f“Round 2: {response2}”)步骤二:修改 Worker 以支持多模态输入
上面的engine只是一个外壳,真正的计算发生在Worker中。你需要修改vllm/worker/worker.py中Worker类的execute_model方法,或者更具体地,修改其使用的ModelRunner。
你需要找到模型前向传播的调用处,并确保它能接收到我们传递的image_features。这通常意味着要修改vllm/model_executor/models/目录下对应模型架构(如llava,qwen_vl)的forward方法包装器。
这是一个非常底层的修改,需要对 vLLM 的模型加载和执行流水线有清晰的理解。通常的切入点是:
- 在
Worker的execute_model方法中,从seq_group_metadata_list里解析出我们自定义的multi_modal_data。 - 将这些数据传递给
model_runner。 - 在
model_runner中,在调用model.forward()之前,将视觉特征数据与 token ids 等整合,并注入到模型的forward参数中。
由于这部分代码与 vLLM 版本强相关且变动频繁,我无法给出万无一失的代码。最佳实践是:
- 在 vLLM 的 GitHub 仓库中搜索 “multimodal”、“vision” 或 “image” 相关的 Issue 和 Pull Request。
- 寻找社区贡献的适配代码,例如针对 LLaVA 的 vLLM 集成示例。
- 基于一个可工作的社区版本进行二次开发,以适应你的特定模型。
4.2 部署与服务化:构建异步 API
一旦你的自定义引擎能够正确运行,下一步就是将其服务化,提供一个类似于 OpenAI API 的接口。
# api_server.py (简化示例,基于 FastAPI) from fastapi import FastAPI, HTTPException from pydantic import BaseModel from .multi_modal_engine import MultiModalLLMEngine # 导入自定义引擎 import uuid app = FastAPI(title=“vLLM Multi-Modal API”) # 全局引擎实例 engine = None class VisionLanguageRequest(BaseModel): prompt: str image_url: str = None # 或 image_base64 session_id: str = None # 其他采样参数... temperature: float = 0.7 max_tokens: int = 1024 @app.on_event(“startup”) async def startup_event(): global engine # 初始化引擎,这是一个耗时操作 engine = MultiModalLLMEngine( model_id=“qwen/Qwen-VL-Chat”, tensor_parallel_size=2, gpu_memory_utilization=0.85, trust_remote_code=True, ) print(“Multi-Modal Engine loaded.”) @app.post(“/v1/chat/completions”) async def chat_completion(request: VisionLanguageRequest): if engine is None: raise HTTPException(status_code=503, detail=“Engine not ready”) # 生成或使用提供的 session_id session_id = request.session_id or str(uuid.uuid4()) # 处理图像数据(这里需要实现从 URL 或 base64 加载图像的逻辑) image_data = None if request.image_url: # 使用 requests 或 aiohttp 下载图片 # image_data = download_image(request.image_url) pass # 如果是 base64,解码 # elif request.image_base64: # image_data = decode_base64_image(request.image_base64) try: # 调用引擎生成 response_text = engine.generate_with_image( session_id=session_id, prompt_text=request.prompt, image_data=image_data, sampling_params=SamplingParams( temperature=request.temperature, max_tokens=request.max_tokens, ) ) return { “id”: f“chatcmpl-{uuid.uuid4().hex}”, “object”: “chat.completion”, “created”: int(time.time()), “model”: “qwen-vl-chat”, “choices”: [{ “index”: 0, “message”: { “role”: “assistant”, “content”: response_text }, “finish_reason”: “stop” }], “usage”: { “prompt_tokens”: 0, # 需要从引擎获取 “completion_tokens”: 0, # 需要从引擎获取 “total_tokens”: 0 } } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # 运行: uvicorn api_server:app --host 0.0.0.0 --port 8000这个 API 服务器提供了基本的会话管理能力。通过session_id,客户端可以在多轮对话中复用同一张图像的视觉特征,避免重复编码,显著降低延迟。
5. 性能调优与避坑指南
部署只是第一步,让服务稳定高效地运行才是挑战的开始。
5.1 显存管理:视觉特征缓存是双刃剑
视觉特征缓存可以极大提升多轮对话的响应速度,但它也占用显存。一个[batch_size, num_patches, hidden_dim]的特征张量,在float16精度下,大小是batch_size * num_patches * hidden_dim * 2字节。对于batch_size=1, num_patches=256, hidden_dim=4096,单张图的特征缓存就约2 MB。看起来不大,但如果你要支持成百上千个并发会话,显存压力就来了。
策略:
- 设置缓存淘汰策略:实现一个 LRU(最近最少使用)缓存。当缓存数量超过阈值时,淘汰最久未使用的视觉特征。
- 会话超时:为每个
session_id设置一个生存时间(TTL)。超过 TTL 未使用的会话,其视觉缓存被清除。 - 分级存储:对于非常用会话,考虑将视觉特征序列移动到主机内存甚至磁盘,需要时再加载回 GPU。但这会引入延迟,需要权衡。
5.2 批处理与吞吐量优化
vLLM 的强项之一就是高效的批处理。对于多模态请求,批处理变得更加复杂,因为请求间可能共享图像,也可能不共享。
- 同图多问:多个用户对同一张图片提出不同问题。这是最优情况,视觉编码只需一次,生成的视觉特征 KV Cache 可以被所有请求共享。你的调度器需要能识别这种模式。
- 异图异问:最常见的场景。每个请求都有自己独特的图像。这时,视觉编码成为瓶颈,因为它通常是顺序执行的。你需要确保视觉编码器部分也能进行批处理。即,将多个图像的像素张量堆叠成一个 batch,一次性通过视觉编码器。
- 在自定义引擎中实现视觉编码批处理:在你的
MultiModalLLMEngine中,不能简单地对每个请求单独调用_encode_image。你需要收集一个 step 中所有需要编码的新图像,批量处理,然后分别存入各自的缓存。
5.3 常见错误与排查
“Could not find the image processor in the model config”- 原因:你使用的
Processor类不正确,或者模型仓库的配置文件里没有正确指定image_processor。 - 解决:直接使用模型自带的
AutoProcessor,并确保trust_remote_code=True。手动检查processor对象的属性,看是否有image_processor或feature_extractor。
- 原因:你使用的
“The size of tensor a (X) must match the size of tensor b (Y)”- 原因:视觉特征的维度与语言模型期望的维度不匹配。这通常发生在你将视觉特征拼接到文本 token embeddings 时。
- 解决:仔细检查视觉编码器的输出维度 (
hidden_dim) 是否与语言模型的词嵌入维度一致。如果不一致,模型可能有一个额外的“连接层”来映射维度,你需要确保这个层被正确加载和调用。
推理结果胡言乱语或与图片无关
- 原因:视觉特征没有被正确注入到语言模型的注意力层中。可能是输入序列的构造方式错了,比如视觉特征对应的
input_ids占位符不对,或者注意力掩码没有正确覆盖视觉部分。 - 解决:这是最棘手的问题。你需要深入模型的前向传播代码。使用调试器,在
model.forward()处设置断点,检查传入的input_ids,attention_mask, 和pixel_values(或image_features) 的形状和值是否正确。确保模型内部的“视觉投影层”被调用。
- 原因:视觉特征没有被正确注入到语言模型的注意力层中。可能是输入序列的构造方式错了,比如视觉特征对应的
vLLM 报错
“Input tensor must be contiguous”或其他 CUDA 内核错误- 原因:你传递给 vLLM 内部 C++/CUDA 内核的张量不符合内存布局要求。
- 解决:在将视觉特征等张量注入 vLLM 内部之前,调用
.contiguous()方法确保内存连续。例如:vision_features = vision_features.to(device).contiguous()。
5.4 监控与日志
在生产环境中,你需要监控以下关键指标:
- 视觉编码延迟:从收到图像到完成特征提取的时间。
- 视觉 KV Cache 命中率:复用缓存视觉特征的请求比例。
- 每请求显存峰值:同时处理多个多模态请求时的显存使用情况。
- Token 生成速度:在视觉特征已就绪的情况下,文本部分的生成速度。
在自定义引擎中添加详细的日志,记录每个阶段(图像编码、缓存查找、请求调度、生成)的耗时,这对于定位性能瓶颈至关重要。
6. 未来展望与替代方案
虽然我们通过深度定制 vLLM 实现了视觉语言模型的部署,但这个过程无疑充满了挑战。幸运的是,整个生态正在快速演进。
vLLM 官方多模态路线图:vLLM 团队正在积极开发原生的多模态支持。关注他们的 GitHub 仓库和发布说明,未来可能只需要简单的配置就能支持主流的视觉语言模型。
其他高性能推理引擎:
- TGI:Hugging Face 的 Text Generation Inference 服务,对 Hugging Face 生态的多模态模型有较好的内置支持,部署可能更简单。
- TensorRT-LLM:NVIDIA 的推理优化库,通过编译和大量内核优化,能获得极致的推理性能。它对多模态的支持也在逐步完善,但上手难度更高。
- MLC-LLM:一个强调通用性和硬件覆盖的编译框架,也开始支持多模态模型的部署。
个人建议:如果你的项目对吞吐量和延迟要求极高,且团队有较强的工程能力,沿着定制化 vLLM 的路线深入是值得的。如果你的首要目标是快速验证和部署,不妨先尝试 TGI,它可能提供了更“开箱即用”的多模态体验。无论选择哪条路,理解本文所探讨的视觉特征表示、缓存与调度这些核心概念,都将帮助你更好地驾驭视觉语言模型的服务化部署。这个过程就像教一个原本只精通文字的语言学家去理解绘画,需要为它建立一套全新的“视觉词汇”处理流程,一旦打通,它将释放出巨大的应用潜力。