支持图像输入的推理服务
前言
随着 GPT-4V 的普及,多模态(Vision-Language Model, VLM)需求越来越多。我们团队最近接到一个需求:给电商平台做一个”以图搜文”功能——用户上传商品图片,AI 自动生成商品描述、提取卖点。
这篇文章分享我用 vLLM 部署多模态模型的实践经验。
💡 什么是 VLM?
Vision-Language Model(视觉语言模型)是同时理解图像和文本的多模态模型。它通过 Vision Encoder 将图像转换为特征向量,再与文本一起输入 LLM 进行理解和生成。典型应用包括:图像描述、视觉问答、OCR+理解等。
vLLM 支持的多模态模型
vLLM 从 0.4.0 版本开始支持多模态模型,目前支持的主流模型包括:
| 模型 | 参数量 | 图像分辨率 | 特点 |
|---|---|---|---|
| Qwen2-VL | 2B/7B/72B | 动态分辨率 | 中文能力强,支持视频 |
| Qwen2.5-VL | 3B/7B/72B | 动态分辨率 | 最新版本,效果更好 |
| LLaVA-1.5 | 7B/13B | 336×336 | 经典模型,社区支持好 |
| LLaVA-1.6 (NeXT) | 7B/13B/34B | 动态分辨率 | 支持更高分辨率 |
| Phi-3-Vision | 4.2B | 1344×1344 | 微软出品,小而精 |
| InternVL2 | 2B/8B/26B/76B | 动态分辨率 | 国产开源,效果出色 |
| Pixtral | 12B | 1024×1024 | Mistral 出品 |
🎯 模型选择建议:
- 中文场景优先:Qwen2.5-VL、InternVL2
- 英文/通用场景:LLaVA-NeXT、Phi-3-Vision
- 显存受限:Qwen2-VL-2B、Phi-3-Vision
快速部署 Qwen2.5-VL
以 Qwen2.5-VL-7B 为例,演示完整的部署流程。
启动服务
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-VL-7B-Instruct \
--trust-remote-code \
--limit-mm-per-prompt image=4 \
--max-model-len 8192 \
--gpu-memory-utilization 0.90 \
--host 0.0.0.0 \
--port 8000
关键参数说明:
| 参数 | 说明 |
|---|---|
--trust-remote-code |
Qwen 系列必须,允许执行模型自带的代码 |
--limit-mm-per-prompt |
限制每个请求的图像数量,防止显存溢出 |
--max-model-len |
最大上下文长度,包含图像 token |
使用 OpenAI Vision API 调用
vLLM 完全兼容 OpenAI 的 Vision API 格式:
from openai import OpenAI
import base64
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
# 方式 1:使用图片 URL
response = client.chat.completions.create(
model="Qwen/Qwen2.5-VL-7B-Instruct",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "请详细描述这张图片的内容"},
{
"type": "image_url",
"image_url": {"url": "https://example.com/image.jpg"}
}
]
}
],
max_tokens=512
)
print(response.choices[0].message.content)
# 方式 2:使用 Base64 编码(本地图片)
def encode_image(image_path: str) -> str:
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
image_data = encode_image("/path/to/local/image.jpg")
response = client.chat.completions.create(
model="Qwen/Qwen2.5-VL-7B-Instruct",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "这是什么产品?请生成一段商品描述。"},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
}
]
}
],
max_tokens=512
)
多图输入
Qwen2.5-VL 支持多图输入,非常适合对比分析场景:
response = client.chat.completions.create(
model="Qwen/Qwen2.5-VL-7B-Instruct",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "对比这两张图片,有什么不同?"},
{
"type": "image_url",
"image_url": {"url": "https://example.com/before.jpg"}
},
{
"type": "image_url",
"image_url": {"url": "https://example.com/after.jpg"}
}
]
}
],
max_tokens=512
)
⚠️ 注意:多图会显著增加显存占用,建议通过
--limit-mm-per-prompt限制数量。
显存与性能优化
显存占用分析
多模态模型的显存占用比纯文本模型更大,主要来自三部分:
总显存 = LLM 权重 + Vision Encoder + 图像特征 KV Cache
以 Qwen2.5-VL-7B 为例:
- LLM 权重:约 14GB(FP16)
- Vision Encoder:约 1.2GB
- 图像特征:每张图约 200-800MB(取决于分辨率)
实测数据(RTX 4090 24GB):
| 配置 | 显存占用 | 最大图片数/请求 |
|---|---|---|
| Qwen2.5-VL-7B | 16GB | 4 |
| Qwen2.5-VL-7B + 量化 | 10GB | 6 |
| Qwen2-VL-2B | 6GB | 8 |
图像预处理优化
高分辨率图像会显著增加处理时间和显存。建议在客户端预处理:
from PIL import Image
import io
import base64
def preprocess_image(image_path: str, max_size: int = 1024) -> str:
"""压缩图像到合适的分辨率"""
img = Image.open(image_path)
# 保持宽高比缩放
ratio = min(max_size / img.width, max_size / img.height)
if ratio < 1:
new_size = (int(img.width * ratio), int(img.height * ratio))
img = img.resize(new_size, Image.LANCZOS)
# 转换为 JPEG(压缩率更高)
buffer = io.BytesIO()
img.convert("RGB").save(buffer, format="JPEG", quality=85)
return base64.b64encode(buffer.getvalue()).decode("utf-8")
量化部署
显存紧张时,可以使用量化版本:
# AWQ 4-bit 量化
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-VL-7B-Instruct-AWQ \
--quantization awq \
--trust-remote-code \
--limit-mm-per-prompt image=6
💡 量化后显存降低约 40%,速度略有下降(约 10-15%)。
性能基准测试
测试环境:RTX 4090 24GB,Qwen2.5-VL-7B-Instruct
延迟测试
| 场景 | 首 token 延迟 | 生成速度 |
|---|---|---|
| 纯文本 | 85ms | 95 tok/s |
| 1 张图片(512×512) | 320ms | 88 tok/s |
| 1 张图片(1024×1024) | 580ms | 82 tok/s |
| 4 张图片(512×512) | 890ms | 75 tok/s |
📊 关键发现:
- 首 token 延迟主要来自图像编码,与图像分辨率和数量强相关
- 生成速度受影响较小,因为图像特征只在 prefill 阶段使用
吞吐量测试
并发 10 个图像请求(每请求 1 张 512×512 图片):
| 指标 | 数值 |
|---|---|
| 平均延迟 | 1.2s |
| P99 延迟 | 2.1s |
| 吞吐量 | 8.3 req/s |
| GPU 利用率 | 92% |
实战案例:电商图片描述生成
回到开头的需求,这是我最终的实现方案:
from openai import OpenAI
from PIL import Image
import io
import base64
class ProductDescriptionGenerator:
def __init__(self, base_url: str = "http://localhost:8000/v1"):
self.client = OpenAI(base_url=base_url, api_key="not-needed")
self.model = "Qwen/Qwen2.5-VL-7B-Instruct"
def preprocess_image(self, image_path: str) -> str:
"""预处理图片"""
img = Image.open(image_path)
# 限制最大尺寸
max_size = 800
ratio = min(max_size / img.width, max_size / img.height)
if ratio < 1:
new_size = (int(img.width * ratio), int(img.height * ratio))
img = img.resize(new_size, Image.LANCZOS)
buffer = io.BytesIO()
img.convert("RGB").save(buffer, format="JPEG", quality=85)
return base64.b64encode(buffer.getvalue()).decode("utf-8")
def generate_description(self, image_path: str, category: str = None) -> dict:
"""生成商品描述"""
image_data = self.preprocess_image(image_path)
prompt = """请分析这张商品图片,生成以下内容:
1. **商品名称**:简洁准确的产品名
2. **核心卖点**:3-5 个产品亮点,用于搜索标签
3. **商品描述**:100-150 字的营销文案,突出产品特色和使用场景
4. **目标人群**:适合的消费群体
请以 JSON 格式返回。"""
if category:
prompt += f"\n\n商品类目:{category}"
response = self.client.chat.completions.create(
model=self.model,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
}
]
}
],
max_tokens=800,
temperature=0.7
)
return response.choices[0].message.content
def batch_generate(self, image_paths: list, category: str = None) -> list:
"""批量生成(串行,避免显存溢出)"""
results = []
for path in image_paths:
try:
result = self.generate_description(path, category)
results.append({"path": path, "description": result, "error": None})
except Exception as e:
results.append({"path": path, "description": None, "error": str(e)})
return results
# 使用示例
generator = ProductDescriptionGenerator()
result = generator.generate_description("/path/to/product.jpg", category="女装/连衣裙")
print(result)
输出示例:
{
"商品名称": "法式复古碎花连衣裙",
"核心卖点": ["法式田园风", "显瘦V领设计", "透气雪纺面料", "百搭通勤款"],
"商品描述": "浪漫法式碎花设计,尽显优雅女人味。经典V领剪裁修饰脸型,收腰设计打造完美身材比例。轻盈雪纺面料,夏日穿着清爽透气。无论是约会出游还是日常通勤,都能轻松驾驭,让你成为人群中的焦点。",
"目标人群": "20-35岁都市女性,追求时尚优雅、注重穿搭品质的消费者"
}
常见问题
Q1: 首 token 延迟太高
图像编码是主要耗时。解决方案:
- 降低图像分辨率(预处理到 512-768px)
- 使用更小的模型(Qwen2-VL-2B)
- 启用 Flash Attention(默认已启用)
Q2: 图片 URL 请求失败
vLLM 服务需要能访问图片 URL。检查:
- 网络连通性(服务器能否访问外网)
- URL 是否返回真实图片(不是 HTML 页面)
- 建议使用 Base64 方式,更稳定
Q3: 多图输入显存溢出
CUDA out of memory
解决方案:
- 减少
--limit-mm-per-prompt的值 - 降低
--gpu-memory-utilization - 使用量化模型
Q4: 输出不稳定 / 幻觉
多模态模型的幻觉问题比纯文本更严重。建议:
- 使用更低的 temperature(0.3-0.5)
- Prompt 中明确要求”只描述图中可见的内容”
- 对关键信息做后处理校验
结语
vLLM 的多模态支持让图像理解服务的部署变得简单:
| 特性 | 说明 |
|---|---|
| 兼容 OpenAI API | 现有代码几乎不用改 |
| 灵活的输入方式 | 支持 URL 和 Base64 |
| 多图支持 | 适合对比、分析等复杂场景 |
适用场景:
- ✅ 商品图片描述生成
- ✅ 图像内容审核
- ✅ OCR + 理解(发票、证件识别)
- ✅ 图表/文档理解
局限性:
- ⚠️ 首 token 延迟较高(图像编码耗时)
- ⚠️ 高分辨率/多图场景显存压力大
- ⚠️ 视频理解支持有限(Qwen2-VL 部分支持)
多模态是 vLLM 正在快速迭代的领域。如果你的业务涉及图像理解,现在是入坑的好时机。
参考资料
- vLLM Vision Language Models 官方文档
- Qwen2-VL: Enhancing Vision-Language Model’s Perception of the World at Any Resolution
- OpenAI Vision API 官方文档
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:《 vLLM 高性能推理系列——多模态模型部署 》
本文链接:http://localhost:3015/ai/vLLM-multimodal.html
本文最后一次更新为 天前,文章中的某些内容可能已过时!