多模态模型部署
多模态模型(Vision-Language, Audio-Language)的部署比纯文本 LLM 复杂:输入预处理多样、GPU 显存占用更大、推理延迟更高。
部署架构
graph TB
A[多模态请求] --> B[输入路由]
B --> C[图像预处理
Resize/Normalize] B --> D[音频预处理
Whisper/Encoder] B --> E[文本预处理
Tokenizer] C --> F[多模态推理引擎] D --> F E --> F F --> G[输出后处理] G --> H[文本生成] G --> I[图像生成] style A fill:#e3f2fd,stroke:#1976d2,stroke-width:3px style F fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
Resize/Normalize] B --> D[音频预处理
Whisper/Encoder] B --> E[文本预处理
Tokenizer] C --> F[多模态推理引擎] D --> F E --> F F --> G[输出后处理] G --> H[文本生成] G --> I[图像生成] style A fill:#e3f2fd,stroke:#1976d2,stroke-width:3px style F fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
多模态 vs 纯文本部署
| 维度 | 纯文本 LLM | 多模态 LLM |
|---|---|---|
| 输入处理 | Tokenizer only | 图/音/文分别处理 |
| 显存占用 | 中 | 高(Vision Encoder 额外开销) |
| batch 策略 | Token-level batching | 混合 batch(图+文对齐) |
| 推理延迟 | 100-500ms | 500ms-2s(含 Encoder) |
| 缓存 | KV Cache | KV Cache + Image Cache |
| 量化 | 成熟 | 部分支持(Encoder 精度敏感) |
多模态服务框架
"""
多模态模型推理服务
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Any
import base64
import io
class ModalityType(Enum):
TEXT = "text"
IMAGE = "image"
AUDIO = "audio"
VIDEO = "video"
@dataclass
class MultimodalInput:
"""多模态输入"""
modality: ModalityType
data: Any
metadata: dict = field(default_factory=dict)
@dataclass
class ProcessedInput:
"""预处理后的输入"""
embeddings: list[float]
tokens: int
modality: ModalityType
class ImagePreprocessor:
"""图像预处理"""
def __init__(self, target_size: tuple[int, int] = (336, 336)):
self.target_size = target_size
def process(self, image_data: bytes) -> ProcessedInput:
"""处理图像"""
# 解码 → 缩放 → 归一化 → 分 patch
patches = self._split_patches(image_data)
tokens = len(patches) * 256 # 每个 patch ≈ 256 tokens
return ProcessedInput(
embeddings=patches,
tokens=tokens,
modality=ModalityType.IMAGE,
)
def _split_patches(self, image_data: bytes) -> list:
"""将图像分割为 patches(简化示意)"""
# 实际实现使用 PIL/torchvision
return [0.0] * 576 # 简化:576 个 patch embeddings
class MultimodalServer:
"""多模态推理服务"""
# 不同模态的 Token 换算
TOKEN_RATIOS = {
ModalityType.TEXT: 1,
ModalityType.IMAGE: 256, # 1张图 ≈ 256-1024 tokens
ModalityType.AUDIO: 25, # 1秒音频 ≈ 25 tokens
}
def __init__(self, model_name: str, max_batch_size: int = 8):
self.model_name = model_name
self.max_batch_size = max_batch_size
self.image_processor = ImagePreprocessor()
self._request_count = 0
def infer(self, inputs: list[MultimodalInput]) -> str:
"""推理"""
processed = []
total_tokens = 0
for inp in inputs:
if inp.modality == ModalityType.IMAGE:
p = self.image_processor.process(inp.data)
elif inp.modality == ModalityType.TEXT:
p = ProcessedInput(
embeddings=[],
tokens=len(str(inp.data)) // 4,
modality=ModalityType.TEXT,
)
else:
continue
processed.append(p)
total_tokens += p.tokens
self._request_count += 1
# 实际推理(调用模型的 forward)
return self._forward(processed, total_tokens)
def _forward(self, inputs: list[ProcessedInput], total_tokens: int) -> str:
"""模型前向推理(简化示意)"""
return f"[多模态推理结果] 处理 {len(inputs)} 个输入,共 {total_tokens} tokens"
def estimate_cost(self, inputs: list[MultimodalInput]) -> dict:
"""估算成本"""
total_tokens = 0
for inp in inputs:
ratio = self.TOKEN_RATIOS.get(inp.modality, 1)
if inp.modality == ModalityType.IMAGE:
total_tokens += ratio * 4 # 高分辨率可达 4x
elif inp.modality == ModalityType.TEXT:
total_tokens += len(str(inp.data)) // 4
elif inp.modality == ModalityType.AUDIO:
duration = inp.metadata.get("duration_sec", 10)
total_tokens += ratio * duration
return {
"estimated_tokens": total_tokens,
"estimated_cost_usd": total_tokens * 0.00001,
}
GPU 显存规划
| 模型 | 参数量 | FP16 显存 | INT4 显存 | 推荐 GPU |
|---|---|---|---|---|
| LLaVA-1.5-7B | 7B | 14 GB | 5 GB | RTX 4090 |
| LLaVA-1.5-13B | 13B | 26 GB | 9 GB | A100 40G |
| Qwen-VL-Chat | 9.6B | 20 GB | 7 GB | A100 40G |
| InternVL2-26B | 26B | 52 GB | 16 GB | A100 80G |
| GPT-4V (API) | — | — | — | 云 API |
优化策略
graph LR
A[多模态优化] --> B[图像缓存
相同图不重复 Encode] A --> C[动态分辨率
按需降采样] A --> D[Encoder 量化
INT8 通常可行] A --> E[异步流水线
预处理与推理并行] style A fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
相同图不重复 Encode] A --> C[动态分辨率
按需降采样] A --> D[Encoder 量化
INT8 通常可行] A --> E[异步流水线
预处理与推理并行] style A fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
本章小结
| 主题 | 要点 |
|---|---|
| 输入复杂度 | 不同模态需不同预处理管线 |
| 显存规划 | Vision Encoder 额外占 20-30% 显存 |
| Token 换算 | 1 张图 ≈ 256-1024 tokens |
| 优化方向 | 图像缓存 + 动态分辨率 + 异步流水线 |
下一章:LLM 生产趋势