支持图像输入的推理服务

前言

随着 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 正在快速迭代的领域。如果你的业务涉及图像理解,现在是入坑的好时机。


参考资料

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 vLLM 高性能推理系列——多模态模型部署 》

本文链接:http://localhost:3015/ai/vLLM-multimodal.html

本文最后一次更新为 天前,文章中的某些内容可能已过时!