我所理解的多模态大模型的常见设计模式:
多模态大模型的工作原理:
- 以大语言模型为基础架构,作为统一的推理和生成骨干网络
- 多模态输入处理:通过各自的编码器(encoder)+适配器(adapter)的方式处理图片、音频等多模态输入,将不同模态对齐到语言模型的语义空间
- 通过专门的编码器(encoder)处理各类输入:
- 图像编码器(如CLIP, ViT)
- 音频编码器(如Wav2Vec)
- 视频编码器等
- 对齐策略的多样性:
- 适配器(adapter)对齐
- 投影层对齐
- 交叉注意力对齐
- 通过专门的编码器(encoder)处理各类输入:
- 特征融合和生成
- 多模态特征的交互和融合:
- 通过交叉注意力机制实现模态间信息交换
- 多层次的特征融合
- 解码过程:
- 基于融合特征的序列生成
- 上下文感知的多模态理解
- 多模态特征的交互和融合:
简要阅读了InternVL1.5的论文
InternVL 1.5 模型架构:
InternVL 1.5 的训练过程:
-
基础模型准备:
- 视觉基础模型: InternViT-6B
- 语言基础模型: InternLM2-20B(Chat版本)
-
视觉编码器的持续学习(Continuous Learning)优化:
A. InternViT-6B-448px-V1.2 的优化:
- 移除最后 3 层(从48层减少到45层)
- 分辨率从 224 提升到 448
- 使用图像描述和OCR数据集训练
- 此时是与 Nous-Hermes-2-Yi-34B 配合
B. InternViT-6B-448px-V1.5 的进一步优化:
- 基于 V1.2 版本继续训练
- 扩展到动态 448×448 分辨率(1-12块)
- 增强数据规模、质量和多样性
- 提升 OCR 能力和高分辨率处理能力
-
两阶段训练流程:
A. 预训练阶段:
- 训练目标: 优化视觉编码器和 MLP 投影层
- 预训练数据:
- 图像描述 (53.9%)
- OCR 数据 (40.9%)
- 检测数据 (5.2%)
B. 微调阶段:
- 训练目标: 整个模型端到端训练
- 微调数据类型: 多样化的下游任务数据,提高多模态理解能力
- 图像描述
- 通用问答
- 科学理解
- 图表理解
- 数学推理
- 知识问答
- OCR 任务
- 文档理解
- 视觉定位
- 多轮对话
一个重要发现是:
论文特别提到:"值得注意的是,尽管 InternVL 1.5 中的 LLM 从 Nous-Hermes-2-Yi-34B 换成了 InternLM2-20B,但 InternViT 仍然保持了与新 LLM 的出色兼容性和可移植性。这表明 InternViT-6B 在 MLLM 预训练阶段学习到的视觉特征具有广泛的适用性,并不与特定的 LLM 紧密绑定。"
这说明:
- 视觉编码器的训练是相对独立的
- 训练好的视觉编码器可以和不同的LLM配合使用
- 这种设计增加了模型的灵活性和可复用性
xtuner
的环境:复用此前 L1 G5000
课程配好的xtuner环境,额外执行下面一行即可:
pip install -U 'xtuner[deepspeed]' timm==1.0.9
lmdeploy
的环境:
pip install lmdeploy gradio==4.44.1 timm==1.0.9
lmdeploy推理的核心代码可以总结为:
- 🔧 初始化配置(创建管线)
- ⚙️ 设置参数(控制生成)
- 🗣️ 开始对话(首次对话)
- 💬 持续对话(后续对话)
"""
LMDeploy 多模态对话系统示例
该示例展示了如何使用 LMDeploy 构建一个支持图文多轮对话的系统
"""
## 1. 导入必要的包
# lmdeploy 核心功能
from lmdeploy import pipeline, TurbomindEngineConfig, GenerationConfig
# 图像加载工具
from lmdeploy.vl import load_image
## 2. 初始化模型和配置
# 设置模型路径
model_path = "your_model_path"
# 创建推理管线
# TurbomindEngineConfig: 推理引擎配置
pipe = pipeline(model_path,
backend_config=TurbomindEngineConfig(session_len=8192))
## 3. 准备输入数据
# 加载图片 (支持多种格式如 jpg, png)
# 也可以使用 PIL.Image.open() 加载
image = load_image('your_image_path')
## 4. 设置生成参数
# GenerationConfig: 控制文本生成的行为
gen_config = GenerationConfig(
top_p=0.8, # 采样时的累积概率阈值
temperature=0.8 # 温度参数,调节输出的随机性
)
## 5. 开始首轮对话
# pipe.chat(): 启动对话
# - 输入: ('提示词', 图片对象)
# - gen_config: 上面设置的生成参数
# 返回: session 对象,包含对话状态
sess = pipe.chat(
('describe this image', image), # 元组形式传入提示词和图片
gen_config=gen_config
)
# 打印模型回复
print("模型回复:", sess.response.text)
## 6. 继续多轮对话
# 传入:
# - 新的问题
# - session: 上一轮返回的会话状态,保持对话连贯性
# - gen_config: 生成参数
sess = pipe.chat(
'What is the woman doing?', # 新的问题
session=sess, # 传入上一轮对话的 session
gen_config=gen_config # 保持相同的生成参数
)
# 打印新的回复
print("模型新回复:", sess.response.text)
运行提供的gradio代码,在UI界面体验与InternVL2的对话:
在微调之前,模型的表现有点难绷。
了解XTuner,并利用给定数据集微调InternVL2-2B后,再次启动UI界面,体验模型美食鉴赏能力的变化。
-
训练策略:
- 只在语言模型部分加入 LoRA 进行训练
- 视觉编码器完全冻结,且没有使用 LoRA
- LoRA 配置:
- 在语言模型部分使用了 LoRA
- rank(r) = 128
- lora_alpha = 256
- dropout = 0.05
- 注释掉了视觉编码器的 LoRA 配置
-
训练超参数:
- 批次大小:4
- 梯度累积:2 次
- 最大训练轮次:10 epochs
- 学习率:3e-5
- 优化器:AdamW (β1=0.9, β2=0.999)
- 权重衰减:0.05
- 使用了混合精度训练 (AmpOptimWrapper)
-
学习率调度:
- 使用了两阶段学习率调度:
- warmup阶段:LinearLR,热身比例为 3% 的总轮次
- 主要训练阶段:CosineAnnealingLR,从热身结束到训练结束
- 使用了两阶段学习率调度:
-
数据处理:
- 最大序列长度:8192 tokens
- 使用 LengthGroupedSampler 进行采样
- 设置了 4 个数据加载工作进程
是一个中餐美食相关的问答多模态数据集。
阅读数据的sivqa_llava.json
文件,可以看出:
- 每个样本包含:
- 一张食物图片
- 一轮问答对话(human 提问,gpt 回答)
[
{
"image": "图片相对路径",
"conversations": [
{
"from": "human/gpt", # 对话角色
"value": "对话内容" # 包含问题或答案
},
...
]
},
...
]
具体处理流程:
- 图像处理:
- 图片会被视觉编码器单独处理,转换成视觉特征(visual features)
- InternVL 使用的是类 CLIP 的视觉编码器,会将图片编码成固定维度的特征向量
# 伪代码表示
image = load_image("images/14456664_217_IMG_3854.jpeg")
# 预处理:调整大小、裁剪、标准化等
processed_image = preprocess(image)
# 通过视觉编码器获取特征
image_features = vision_encoder(processed_image)
- 文本处理:
# 使用模板格式化对话
formatted_text = PROMPT_TEMPLATE.internlm2_chat(
human="图片中的食物通常属于哪个菜系?\n<image>",
assistant="新疆菜,图中的菜是烤羊肉串"
)
# 转换为token ids
input_ids = tokenizer(formatted_text)
# 创建标签(-100表示不计算loss的位置)
labels = input_ids.clone()
labels[:human_text_end] = -100 # 人类问题部分不计算loss
- 最终批处理:
batch = {
"image_features": torch.stack([sample1_img, sample2_img, ...]), # [B, num_patches, hidden_size]
"input_ids": torch.stack([sample1_ids, sample2_ids, ...]), # [B, seq_len]
"attention_mask": torch.stack([sample1_mask, sample2_mask, ...]),
"labels": torch.stack([sample1_labels, sample2_labels, ...])
}
最终,模型会:
- 使用视觉编码器处理 image_features
- 使用语言模型处理 input_ids
- 将两种模态的特征融合
- 生成回答并计算与 labels 的损失
这也解释了为什么配置文件中只训练语言模型的 LoRA:主要的适配工作是在多模态融合后的语言生成部分。
XTuner使用流程:
- 准备好微调的数据
- 写好微调配置文件
- 开跑!
使用课程提供的配置文件,显存占用在32G左右:
训练结束,共用时1h33min:
在 xtuner
的环境下进行模型转换。
然后运行WebUI: