引言
传统视觉模型只能输出预定义类别的标签,而视觉语言模型(Vision-Language Model,VLM)能用自然语言回答问题、描述场景、按指令定位目标。这带来的工程变化是:视觉系统的「输出接口」从固定类别表变成了自由文本,需求变更不再需要重新标注与训练。
但落地 VLM 会遇到三类新问题。第一是成本:一张高分辨率图会被切成上千个视觉 token,显存与延迟远高于纯文本推理。第二是幻觉:模型会自信地描述图中不存在的东西,在医疗、工业等场景是硬伤。第三是评估:自由文本输出没有单一的准确率指标,需要构造任务化基准。
本文聚焦工程落地,讲清 VLM 的结构、训练、检索增强、幻觉治理与成本估算。CLIP 的对比学习原理、ViT 的注意力机制在 视觉 Transformer 与多模态模型 中已有铺垫,本文更侧重把模型变成可用系统。
目录
- VLM 的三段式结构与演进
- 视觉编码器的选择
- 投影层与视觉 token 压缩
- 对比学习与图文对齐
- 指令微调与数据构造
- 代码:CLIP 图文检索
- 视觉问答与对话式理解
- 多模态 RAG
- 幻觉成因与缓解
- 评估基准与指标
- 部署成本与显存估算
- 代码:显存与吞吐估算
1. VLM 的三段式结构与演进
几乎所有 VLM 都是「视觉编码器 + 连接模块 + 语言模型」三段式。演进主线是连接方式与训练策略的变化。
| 阶段 | 代表 | 连接方式 | 特点 |
|---|---|---|---|
| 双塔 | CLIP、ALIGN | 各自编码后算相似度 | 只做匹配,不能生成 |
| 查询压缩 | BLIP-2、Flamingo | Q-Former 或交叉注意力 | 冻结 LLM,只训连接层 |
| 投影对齐 | LLaVA、MiniGPT-4 | 线性或 MLP 投影 | 结构最简,需指令微调 |
| 原生多模态 | GPT-4V、Gemini、Qwen-VL | 早期融合,统一 token | 能力强,闭源或大参数 |
工程含义:查询压缩方案(BLIP-2)视觉 token 少、推理便宜,适合作为「图片描述器」;投影对齐方案(LLaVA)结构简单、易微调,是自研 VLM 的常见起点;原生多模态能力强但可控性差、成本高。
2. 视觉编码器的选择
视觉编码器决定模型「看得清什么」,选型主要看分辨率与训练数据。
- CLIP ViT-L/14:336 或 448 输入,通用性强,是 LLaVA 系列的默认选择。
- SigLIP:用 sigmoid 损失替代 softmax,对批次大小不敏感,训练更稳,近两年被大量 VLM 采用。
- DINOv2:自监督训练,稠密特征质量高,适合需要精细定位的任务。
- SAM 编码器:分割能力强,适合需要像素级理解的多模态任务。
- EVA-CLIP:更大规模训练,特征更丰富。
分辨率是最关键的工程参数:224 输入只能看清大物体,OCR 与小目标需要 448 以上,或用「切片 + 拼接」(如 LLaVA-1.6 的 AnyRes)把大图切成若干子图分别编码。
# 视觉 token 数量随分辨率平方增长,这是显存的主要来源
def vision_tokens(image_size, patch=14, extra=1):
return (image_size // patch) ** 2 + extra
for size in [224, 336, 448, 672]:
print(size, "->", vision_tokens(size), "tokens")
# 224 -> 257, 336 -> 577, 448 -> 1025, 672 -> 2305
从上表可见,448 分辨率的一张图就产生 1025 个 token,相当于一篇千字文章。多图或多帧场景要乘以倍数。
3. 投影层与视觉 token 压缩
连接模块把视觉特征映射到语言模型的词嵌入空间,主流做法有三种:
- 线性投影:一个 Linear 层,最简单,LLaVA 的做法。参数量极小,但需要指令微调来对齐。
- MLP 投影:两层 MLP,表达能力更强,LLaVA-1.5 起采用。
- Q-Former / Perceiver Resampler:用一组可学习的查询向量把 N 个视觉 token 压缩成固定的 M 个(如 32 个)。BLIP-2 用 32 个查询 token 压缩整张图,显存与延迟大幅下降。
- 池化压缩:对视觉 token 做平均池化或注意力池化,简单粗暴但有效。
压缩是降低推理成本最直接的手段。从 576 个视觉 token 压到 64 个,预填充阶段的算力降近一个数量级,代价是细粒度信息(小字、细微纹理)丢失。工程上按任务定:图像描述可激进压缩,OCR 与图表理解必须保留高分辨率。
4. 对比学习与图文对齐
CLIP 用 4 亿图文对训练双塔:图像编码器与文本编码器输出归一化向量,用对称的 InfoNCE 损失让匹配对的余弦相似度高于不匹配对。批次内所有其他样本充当负样本。
工程上的几个关键事实:
- 温度系数 τ 是可学习参数,控制分布的尖锐程度,影响训练稳定性。
- 批次越大负样本越多,效果越好,但显存吃紧,因此常用梯度缓存扩大等效批次。
- 零样本分类的精度对 prompt 措辞敏感,多模板 ensemble 通常提升 1 到 2 个点。
- 对比学习学到的嵌入空间天然适合检索,这是多模态 RAG 的基础。
对齐质量的直观检验是「文本搜图」:用一句描述检索图库,看 Top-5 是否相关。若检索质量差,说明嵌入空间没对齐好,后续 VLM 的视觉理解也会受限。
5. 指令微调与数据构造
投影对齐后模型还不会「按指令回答」,需要指令微调(Instruction Tuning)。数据构造是关键,分两类:
- 从标注数据模板化:把检测、分割、VQA 数据集转成对话格式。例如 COCO 的框标注转成「图中左上角的物体是什么」。成本低、可批量生成。
- 模型生成 + 人工筛选:用强模型(GPT-4V)生成对话数据,人工抽检。质量高但成本高、有蒸馏风险。
训练策略上,常见三阶段:
- 冻结视觉编码器与 LLM,只训投影层,让视觉特征先对齐语言空间。
- 解冻 LLM(或加 LoRA),用混合指令数据微调。
- 任务特化微调,针对目标场景补数据。
# LLaVA 风格的两阶段微调配置示例
stage1 = dict(train_projector=True, train_llm=False, lr=1e-3, epochs=1)
stage2 = dict(train_projector=True, train_llm=True, lr=2e-5, epochs=1)
# 阶段一学习率大、只训投影层;阶段二学习率小、解冻 LLM
for name, cfg in [("align", stage1), ("instruct", stage2)]:
print(name, cfg)
显存有限时,LLM 部分用 LoRA 或 QLoRA,把可训练参数压到 1% 以内,单卡也能微调 7B 级 VLM。
6. 代码:CLIP 图文检索
下面用 CLIP 建一个图文检索索引,这是多模态 RAG 的第一步。
import numpy as np
import torch
import open_clip
from PIL import Image
model, _, preprocess = open_clip.create_model_and_transforms(
"ViT-B-32", pretrained="laion2b_s34b_b79k")
model.eval()
tokenizer = open_clip.get_tokenizer("ViT-B-32")
def encode_images(paths):
with torch.inference_mode():
batch = torch.stack([preprocess(Image.open(p).convert("RGB")) for p in paths])
feat = model.encode_image(batch)
return feat / feat.norm(dim=-1, keepdim=True)
def encode_text(texts):
with torch.inference_mode():
feat = model.encode_text(tokenizer(texts))
return feat / feat.norm(dim=-1, keepdim=True)
gallery = encode_images(["a.jpg", "b.jpg", "c.jpg"])
query = encode_text(["a photo of a red car"])[0]
sims = (gallery @ query).tolist()
print("ranking:", sorted(enumerate(sims), key=lambda x: -x[1]))
检索时务必 L2 归一化,否则内积会被向量模长主导。图库规模上万后改用 FAISS 建索引,思路与 向量数据库选型 中一致。
7. 视觉问答与对话式理解
VQA(Visual Question Answering)是 VLM 最典型的任务:给一张图与一个问题,输出自然语言答案。工程上分两种形态:
- 单轮问答:一问一答,无状态,易于批处理与缓存。
- 多轮对话:保留历史,支持「它左边那个呢」这类指代。需要把历史消息与图像 token 一起构造输入。
多轮对话的坑在于图像 token 重复:如果每轮都把图像重新塞进上下文,token 数会随轮次线性增长,成本飙升。优化手段是缓存视觉 token 的 KV,或用「图像引用 + 摘要」替代重复图像。
# 多轮输入构造:图像 token 只放一次,后续轮次引用
def build_messages(question, history, image_token="<image>"):
msgs = [{"role": "user", "content": f"{image_token}\n这张图里有什么?"}]
msgs.append({"role": "assistant", "content": "图中是一张街道照片。"})
for q, a in history:
msgs += [{"role": "user", "content": q}, {"role": "assistant", "content": a}]
msgs.append({"role": "user", "content": question})
return msgs
print(build_messages("那辆车是什么颜色?", []))
结构化输出是另一类需求:不要自然语言,而要 JSON。做法是给 few-shot 示例加约束解码,或用模型的 JSON mode。工业场景几乎总要把输出转成结构化字段再进下游系统。
8. 多模态 RAG
多模态 RAG 把「检索」从纯文本扩展到图像与图文混合,用于「问一个关于图库或文档的问题」。
三种典型架构:
- 文搜图 + VLM 回答:用 CLIP 检索相关图,再把图送 VLM 回答。适合图片素材库。
- 图文混合索引:把文档页渲染成图,用 VLM 生成页面摘要,摘要进文本索引,命中后回取原图。适合图文混排的 PDF。
- 多向量索引:ColPali 类方法把文档页切成 patch,每个 patch 一个向量,检索时对 patch 做后期交互(late interaction)。对复杂版式效果最好,代价是索引体积大。
import numpy as np
def late_interaction_score(query_patches, doc_patches):
# ColBERT 风格:每个查询 patch 取与文档 patch 的最大相似度后求和
sim = query_patches @ doc_patches.T # (Q, D)
return float(sim.max(axis=1).sum())
q = np.random.rand(32, 128).astype(np.float32)
d = np.random.rand(256, 128).astype(np.float32)
q /= np.linalg.norm(q, axis=1, keepdims=True)
d /= np.linalg.norm(d, axis=1, keepdims=True)
print("score:", round(late_interaction_score(q, d), 3))
多模态 RAG 的评估要分开看检索与生成:检索看 Recall@K,生成看答案准确率与引用正确性。只报生成指标会掩盖检索失败。
9. 幻觉成因与缓解
VLM 幻觉指模型描述图中不存在的内容。成因有三:
- 语言先验过强:LLM 部分按「常见搭配」生成,如看到餐桌就写「有刀叉」,即使图里没有。
- 视觉信息不足:低分辨率或 token 压缩过度,模型只能靠猜。
- 训练数据偏差:指令数据中「是/否」比例失衡,模型倾向肯定回答。
缓解手段:
| 手段 | 做法 | 效果 |
|---|---|---|
| 提高分辨率 | 用切片编码保留细节 | 显著减少细节幻觉 |
| 减少压缩 | 保留更多视觉 token | 有效但成本上升 |
| 约束解码 | 要求引用图中区域 | 提升可核查性 |
| 自一致性 | 多次采样取一致答案 | 有效但成本翻倍 |
| 外部工具 | 接检测或 OCR 交叉验证 | 工程最可靠 |
| 拒答训练 | 数据中加入「图中没有」样本 | 减少盲目肯定 |
工业场景最实用的组合是「VLM 出结果 + 小模型交叉验证」:让 VLM 描述,再用检测器或 OCR 验证关键事实,不一致就降级到人工。
10. 评估基准与指标
VLM 评估比传统视觉难得多,因为输出是自由文本。
| 基准 | 任务 | 指标 |
|---|---|---|
| VQAv2 | 视觉问答 | 答案与人工答案的一致率 |
| TextVQA | 图中文字问答 | 精确匹配 |
| GQA | 组合推理 | 准确率 |
| MMBench | 综合能力 | 选择题准确率 |
| MME | 感知与认知 | 是/否准确率与得分 |
| POPE | 幻觉 | 是/否偏差 |
| ChartQA | 图表理解 | 宽松准确率 |
工程上更该自建领域基准:从真实业务里抽 200 到 500 个样本,人工标注标准答案,固定下来做回归测试。公开榜单反映的是通用能力,与你的场景相关性有限。
评估的两个陷阱:一是用强模型当裁判(LLM-as-Judge)有位置偏差与冗长偏好,需要随机交换答案顺序;二是样本量太小,几十个样本的准确率差异不显著。
11. 部署成本与显存估算
VLM 推理成本由三部分构成,量级差异很大:
| 部分 | 显存/算力来源 | 优化手段 |
|---|---|---|
| 视觉编码器 | 图像分辨率与 patch 数 | 降分辨率、切片复用 |
| 预填充 | 视觉 token 数的平方 | token 压缩、KV 缓存复用 |
| 解码 | 输出长度 × 参数量 | 量化、投机解码、限制输出 |
估算显存的经验公式(推理,FP16):
显存 ≈ 参数量(B) × 2GB + KV_cache + 激活峰值
KV_cache ≈ 2 × layers × heads × head_dim × seq_len × batch × 2B
7B 模型 FP16 权重约 14GB,加 KV 与激活通常要 20GB 以上;INT4 量化后权重降到约 4GB,可在单张 24GB 卡上跑。视觉 token 让 seq_len 从几百涨到两三千,KV 缓存与预填充算力同步放大。
延迟上,预填充是算力受限、解码是带宽受限。视觉 token 多会显著拉长预填充,因此「先压缩视觉 token 再推理」往往比换更小的语言模型更有效。更多推理优化手段见 大模型推理优化 与 多模态模型部署 。
12. 代码:显存与吞吐估算
上线前先算一遍,避免买错卡。
def kv_cache_gb(layers=32, heads=32, head_dim=128, seq_len=2048, batch=1, dtype_bytes=2):
# 2 表示 K 与 V 各一份
return 2 * layers * heads * head_dim * seq_len * batch * dtype_bytes / 1e9
def weights_gb(params_b, dtype_bytes=2):
return params_b * 1e9 * dtype_bytes / 1e9
def estimate(params_b=7, vision_tokens=1025, out_tokens=128, batch=1, quant=2):
w = weights_gb(params_b, quant)
prefill_kv = kv_cache_gb(seq_len=vision_tokens + 64, batch=batch, dtype_bytes=2)
decode_kv = kv_cache_gb(seq_len=vision_tokens + out_tokens, batch=batch, dtype_bytes=2)
total = w + max(prefill_kv, decode_kv) + 2.0 # 2GB 留给激活与框架
return dict(weights=round(w, 1), kv=round(decode_kv, 2), total=round(total, 1))
print(estimate(7, 1025, 128, 1, quant=2)) # FP16
print(estimate(7, 1025, 128, 1, quant=0.5)) # INT4
按这个公式,7B VLM 在 1025 视觉 token 下 FP16 需要约 17 到 20GB,INT4 约 7 到 9GB。把视觉 token 压到 256 个,KV 部分能省一半以上。
权衡取舍
| 维度 | 倾向 | 代价 |
|---|---|---|
| 高分辨率与低成本 | OCR 用高分辨率 | token 数平方增长 |
| 开源与闭源 | 可控与私有化用开源 | 能力上限略低 |
| 全量微调与 LoRA | 显存紧用 LoRA | 能力上限略低 |
| 自然语言与结构化输出 | 下游系统要结构化 | 需约束解码或后处理 |
| 端到端 VLM 与级联小模型 | 级联更可控更便宜 | 工程链路更长 |
| 激进压缩与保真 | 描述任务可压缩 | 细节任务不能压 |
几条实用原则:
- 先算显存与延迟再选模型,不要先选模型再想办法塞进去。
- 视觉 token 是成本主因,压缩的收益通常大于换小模型。
- 幻觉在关键场景必须靠外部工具交叉验证,不能只靠 prompt。
- 自建领域基准比刷公开榜单更有价值,样本 200 条起。
常见坑清单
- 图像 token 未归一化:CLIP 特征不归一化,相似度排序错乱。
- 分辨率与 patch 不匹配:patch 14 需要尺寸为 14 的倍数,否则要 padding。
- 多轮重复塞图:每轮重放图像 token,上下文线性膨胀,成本失控。
- 忽略预填充瓶颈:只优化解码,视觉 token 导致预填充占了大头。
- 用公开榜单代替领域评估:通用能力与业务效果相关性弱。
- LLM 当裁判不换序:位置偏差导致结论不可靠。
- 过度压缩视觉 token:OCR 与图表任务信息直接丢失。
- 幻觉不设兜底:关键字段靠 VLM 直接输出,错误直接进业务系统。
- 微调时冻结全部视觉编码器:领域差异大时视觉特征不匹配,需解冻后几层。
- 数据构造全用强模型生成:有蒸馏与合规风险,需人工抽检。
- 忘记输出长度限制:模型生成冗长描述,延迟与成本远超预期。
- 显存估算漏掉 KV:按权重估卡,上线后并发一开就 OOM。
小结
多模态 VLM 工程的主线是:选视觉编码器(分辨率优先)→ 投影层对齐 → 指令微调 → 视觉 token 压缩控成本 → 检索增强补知识 → 交叉验证治幻觉 → 自建领域基准做回归。记住三个判断点:视觉 token 数是成本主因、幻觉必须靠外部工具兜底、评估要分检索与生成两段。
落地顺序建议先用 CLIP 类模型做检索与零样本分类,验证数据与场景可行性,再引入生成式 VLM 做问答。私有化部署优先选开源 7B 级模型加 LoRA 微调,配合 INT4 量化在单卡上跑通。当业务对结构化输出有硬要求时,把 VLM 定位成「理解器」,下游接规则或小模型做抽取,比让 VLM 直接输出结构化结果更稳。
延伸阅读
- 视觉 Transformer 与多模态模型 — ViT 与 CLIP 的原理基础
- 多模态图像理解 — 从大模型应用视角看图像理解
- 多模态模型部署 — 服务化与成本优化
- 向量数据库选型 — 图文混合检索的索引方案
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。