27届大模型岗面试准备(十四):多模态大模型 VLM------从视觉编码器到多模态推理的完整链路
写在前面
上一篇(十三)我们讲长上下文时留了个口子:"当上下文里不只有文字,还有图像时,问题又升了一个维度。"这一篇就填这个坑------多模态大模型(Vision-Language Model,VLM)。
你正在准备华为的多模态 LLM 岗位,这一篇几乎就是你的主场题。面试官不会只问"VLM 是什么",而是会追到:图像怎么变成 token、和文本 token 怎么对齐、训练怎么分阶段、多模态推理到底难在哪。本文按"架构 → 对齐 → 训练 → 推理"的顺序展开,最后给一段可运行的多模态推理代码(用开源 VLM 接口)。
一、VLM 的主流架构:三件套
几乎所有主流 VLM 都遵循同一个骨架:视觉编码器(Vision Encoder)+ 模态连接器(Connector)+ 语言模型(LLM)。三者职责清晰,也是面试里最该讲明白的一张图:
| 组件 | 作用 | 常见实现 | 关键设计点 |
|---|---|---|---|
| 视觉编码器 | 把图像从像素变成语义向量序列 | CLIP-ViT / SigLIP / EVACLIP | 冻结还是微调、用哪个粒度的 patch |
| 模态连接器 | 把视觉向量"翻译"成 LLM 能读的词嵌入空间 | MLP 映射(LLaVA) / Q-Former(BLIP-2) / Resampler | 投影维度、是否带可学习查询 |
| 语言模型 | 接收交错的图文 token,做推理与生成 | LLaMA / Qwen / DeepSeek | 保持原 LLM 能力,不重训 |
为什么是 CLIP-ViT 当编码器? CLIP 在海量图文对上对比学习过,它的图像特征天然和文本语义在同一个对齐空间里------这正好给后面的连接器省了大事。ViT 把图像切成 N×N 个 patch,每个 patch 是一个 token,所以一张 336×336、patch=14 的图会产生 324 个视觉 token。这正是视觉 token 数量膨胀的根源,也是后面"上下文预算"讨论的入口(呼应第十三篇)。
两种连接器路线的取舍是高频追问:
- MLP 映射(LLaVA 路线):视觉特征直接过两层线性层投影到 LLM 维度,简单、训练快、效果够用。代价是视觉 token 数量不压缩(324 个就 324 个)。
- Q-Former / Resampler(BLIP-2 / Flamingo 路线):用一组可学习 query 通过交叉注意力"提炼"出固定数量(如 32 个)视觉 token。大幅压缩 token 数,但引入额外参数和训练复杂度。
一句话总结:"连接器本质是在 token 数量与信息保真之间做权衡。"
二、模态对齐:怎么让"图"和"字"说同一种语言
LLM 只懂词嵌入空间,图像特征是另一套坐标系。对齐(alignment)就是把视觉坐标映射到语言坐标。三种范式:
- 早期融合(Early Fusion):直接把视觉 token 拼到文本 token 序列里,一起进 LLM 自注意力。LLaVA、Qwen-VL、GPT-4V 类都走这条------实现最直接,但视觉 token 多会占上下文(又回到第十三篇的预算问题)。
- 交叉注意力融合(Cross-Attention):LLM 每层通过 cross-attention 去查视觉特征,视觉特征不占自注意力序列。Flamingo 路线。优点是不膨胀上下文,缺点是改动 LLM 结构、训练重。
- 混合:部分层用 early、部分层用 cross-attention,兼顾两者。
面试加分点:能点出位置编码外推在这里也有作用------图像 patch token 有自己的一套位置,和文本位置怎么交错排布(是各自编号还是统一编号、是否加模态类型 embedding 区分图文)是工程细节,也常被问。
三、训练范式:三阶段流水线
VLM 很少从零预训练,主流是在已有 LLM 和视觉编码器上做"缝合 + 分阶段训练",因为图文对齐数据贵、算力贵。标准三阶段:
| 阶段 | 训练目标 | 解冻参数 | 数据 | 目的 |
|---|---|---|---|---|
| 阶段一:特征对齐 | 让视觉特征匹配 LLM 词嵌入 | 只训连接器 | 图文对(caption)海量 | 建立"图→文"映射,不动 LLM |
| 阶段二:指令微调 | 多模态对话/问答 | 连接器 + LLM | 多模态指令数据 | 学会按指令看图作答 |
| 阶段三:能力强化 | 复杂推理/特定能力 | 全量或部分 | 高质量/RLHF 数据 | 提推理、降幻觉、对齐偏好 |
为什么阶段一只解冻连接器? 因为此时 LLM 的语言能力是宝贵的"已有资产",用海量但粗的图文对直接训 LLM 容易把语言知识冲掉(catastrophic forgetting)。先让连接器把视觉特征"塞进"LLM 能理解的区间,再在阶段二用指令数据带 LLM 学会"图文联合推理"。这个"先对齐后微调"的节奏值得主动讲。
四、多模态推理:难在哪
这是多模态岗最容易深挖、也最能体现你水平的环节。单模态 LLM 的推理是"文本进文本出",多模态推理多出三道坎:
- 视觉幻觉(Visual Hallucination):模型"看见"了图上没有的东西------凭文本先验编造,比如把图里没有的"红色杯子"描述出来。根因是视觉编码器→LLM 的信息有损,且 LLM 的文本先验太强会"脑补"。缓解靠高质量对齐数据 + 区域级 grounding(让模型输出物体边界框,强制它真的"看"到了)。
- 细粒度感知:数清图里有几个物体、读对图表里的数字、区分"左边第三个"------这些对 ViT 的平均池化式表征是难点。高分辨率切片(把图切成多张子图分别编码再拼接,如 LLaVA-NeXT、InternVL)是主流解。
- 跨模态组合推理:需要同时用图(空间关系)和文本(提问约束)才能答对的题,比如"把图里蓝色正方形旁边的物体数量告诉我"。要求模型在统一空间里做联合推理,而非各看各的。
这里可以主动接回你的研究方向------多模态 RAG/多模态推理------但注意只讲通用方法论(检索增强、多跳推理、跨模态对齐),不展开未公开的具体研究点,符合你一贯的保密要求。
五、代码实战:用开源 VLM 做多模态推理
下面演示两种调用方式。第一段用 transformers 直接加载一个开源 VLM(如 Qwen2-VL / LLaVA 类)做图文推理;第二段展示用 OpenAI 兼容接口传 base64 图像的多模态对话。皆可运行(需安装对应包,第二段注释掉本地依赖,符合"无 API 也可说明"的原则)。
python
# -*- coding: utf-8 -*-
"""多模态推理两种范式:本地 transformers / OpenAI 兼容接口(可运行需依赖)"""
# ===== 范式 A:本地 transformers 加载开源 VLM(以 Qwen2-VL 风格为例)=====
def infer_local(image_path: str, question: str) -> str:
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
import torch
model = Qwen2VLForConditionalGeneration.from_pretrained(
"Qwen/Qwen2-VL-7B-Instruct", torch_dtype="auto", device_map="auto"
)
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct")
# 构造多模态消息:图像以本地路径传入,文本为问题
messages = [{
"role": "user",
"content": [
{"type": "image", "image": image_path},
{"type": "text", "text": question},
],
}]
# 模板化 + 把图像转成模型输入(像素/特征)
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(text=[text], images=image_inputs, videos=video_inputs,
padding=True, return_tensors="pt").to(model.device)
generated = model.generate(**inputs, max_new_tokens=256)
# 去掉输入部分,只取新生成
out_ids = generated[0][inputs.input_ids.shape[1]:]
return processor.decode(out_ids, skip_special_tokens=True)
# ===== 范式 B:OpenAI 兼容接口传 base64 图像(不依赖本地大模型)=====
def infer_api(base64_image: str, question: str, api_key: str, base_url: str) -> str:
from openai import OpenAI
client = OpenAI(api_key=api_key, base_url=base_url)
resp = client.chat.completions.create(
model="multimodal-model",
messages=[{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}},
{"type": "text", "text": question},
],
}],
max_tokens=256,
)
return resp.choices[0].message.content
if __name__ == "__main__":
q = "图里有几个物体?分别是什么颜色?"
print("问题:", q)
print("范式 A(本地)需安装 transformers + qwen-vl-utils 并下载权重;")
print("范式 B(API)需 base_url 指向一个支持多模态的兼容网关。")
这段代码把"图像怎么进模型"讲清楚了:本地范式是路径/像素 → processor → 视觉 token → 拼接文本 token → 自回归生成 ;API 范式则是base64 图像随消息体上传、服务端完成编码对齐。面试时被问"图像 token 数量怎么算",你可以接回第一篇的 patch 公式:尺寸 / patch_size 的平方。
六、高频面试题与答题要点
- "VLM 和纯文本 LLM 的核心区别?" ------ 多了视觉编码器和模态连接器;推理时多了视觉 token 的编码、对齐与融合;难点在视觉幻觉和细粒度感知。
- "为什么 VLM 一般不在第一阶段训 LLM?" ------ 防灾难性遗忘,先只用海量图文对把连接器对齐,再指令微调带 LLM 学多模态推理。
- "一张 336×336、patch=14 的图产生多少视觉 token?" ------ (336/14)² = 324 个。能现场算这个说明你真懂 ViT 切分。
- "高分辨率图怎么处理不爆上下文?" ------ 切片(tile)分别编码再拼接;或 Q-Former 压缩到固定 query 数;同时配合第十三篇讲的前缀缓存与 KV 压缩。
- "怎么缓解视觉幻觉?" ------ 高质量对齐数据、区域 grounding(输出 bbox)、高分辨率切片、RLHF 偏好对齐。
小结
VLM 的骨架是"视觉编码器 + 连接器 + LLM"三件套,训练走"对齐 → 指令微调 → 强化"三阶段,难点集中在视觉幻觉、细粒度感知和跨模态推理。把它和前面讲的长上下文(视觉 token 占预算)、RAG(多模态检索增强)、Agent(带视觉的具身/文档 Agent)串起来,你就有了一条完整的多模态知识链。下一组(B 系列)我们从 B13 开始把 Agent 的记忆做成"带持久化"的版本------让 Agent 跨会话也不忘事。