Qwen-VL 视觉大模型 计算图拆解及SFT实操
1. 视觉大模型介绍
Qwen - Visual Language, 基于 Qwen-7B 的多模态视觉语言大模型. 支持图文理解、文档解析、OCR、视觉问答等任务.
它的视觉编码参考了 CLIP, 所以一并介绍.
2. 计算图拆解
基于 Qwen-7B, 新增了 a language-aligned visual encoder and a position aware adapter.
2.1 Large Language Model
Qwen-7B.
2.2 视觉编码器 - ViT
即Vision Transformer (ViT) 架构, 并使用了来自 OpenCLIP 的 ViT-bigG 预训练权重作为其视觉编码器的基础。
ViT 的论文见参考 [3], 名字是 “An Image is Worth 16x16 Words”, 挺有意思.

ViT 的工作原理
第一步: 原始图像
resize 到固定分辨率(如 224×224) 。内存中就是一张 H×W×C = 224×224×3 的 float32 tensor,像素值归一化到 [0,1] 或 [-1,1]。占用显存 224×224×3×4 bytes ≈ 602KB,很小。
第二步:Patch 划分
约定一个 patch 为 16 x 16 = 256 个像素, 每个像素有 RGB 三通道, 共 16×16×3 = 768 个 tensor 元素(数值), 对应 shape 为 [C=3, H=16, W=16].
因为上步得到的分辨率为 224, 所以水平、垂直方向各 224÷16 = 14 个 patch,一共 196 个 patch。
第三步:Linear Projection(线性投影)
每个 patch 展平成一个 768 维的向量,然后通过一个可学习的线性层把它映射到模型的隐藏维度。ViT-B 的隐藏维度是 768,所以这个线性层的权重矩阵是 768×768。
本质上就是把每个 patch 的像素信息压缩成一个 768 维的"词向量"——跟 NLP 里把 token ID 查表得到 embedding 是完全对称的操作。所以论文标题才说"An Image is Worth 16x16 Words",每个 16×16 的 patch 就是一个"word"。
注意: 所有 196 个 patch 共享同一个线性投影层.
第四步:加 CLS Token 和 Position Embedding
在 196 个 patch token 前面拼接一个可学习的 [CLS] token(维度也是 768),序列长度变成 197。这个 CLS token 的作用是聚合全局信息,最后取它的输出来做分类。
然后给每个 token 加上位置编码。位置编码也是 768 维的可学习向量,197 个位置各一个,直接 element-wise 加到对应的 token 上。这一步之后模型就能区分"这个 patch 来自图片左上角还是右下角"。
在 Qwen-VL 中,视觉编码器以 14 的步长(stride) 对图像进行分块处理,生成一组图像特征。这些特征捕获了图像的视觉内容,为后续的视觉-语言对齐提供了基础。
为什么选择 ViT
与传统卷积神经网络(CNN)相比,ViT 具有以下优势:
- 全局注意力机制:能够捕捉图像中长距离的依赖关系,对理解图像整体语义更有帮助。
- 可扩展性:随着模型规模和训练数据的增加,ViT 的性能提升更加明显。
- 与 Transformer 架构的统一:与语言模型使用相同的 Transformer 架构,简化了多模态融合的设计。
在 Qwen-VL 的训练过程中,视觉编码器在预训练阶段与语言模型一起优化,学习将视觉特征与文本描述对齐,这是实现高质量视觉-语言理解的关键。
2.3 Position-aware Vision-Language Adapter
Adapter 是独立于 ViT 的一个模块,它夹在 ViT 和 LLM 之间. 作用有
- 是把 ViT 输出的 不定长 (跟分辨率直接相关) 的 token 数量压缩到固定 256 个 token.
- 注入位置信息. ViT 原始输出只带 1D 位置编码(序列顺序),但图像本质是 2D 结构,"左上角"和"右下角"这种空间关系用 1D 编码表达得不够好
Adapter 在 cross-attention 的 Q 和 K 之间加入了 2D 绝对位置编码,让压缩后的每个 token 隐式携带"这个特征来自图像哪个区域"的信息。这就是"Position-aware"这个前缀的由来
整个数据流是这样的:
原始图像 → ViT(输出比如 1024 个 patch token,每个 768 维) → Adapter(压缩到 256 个 token,对齐到 LLM 的隐藏维度) → LLM
3. 官方阶段训练

3.1 ViT 预训练
冻结 LLM,只训视觉编码器,用大规模图文对数据建立视觉-文本关联
3.2 全参数预训练
Multi-Task Pre-training
Image Captioning
<img>cc3m/01581435.jpg</img>Generate the caption in English: the beautiful flowers for
design.<eos>
Vision Question Answering
<img>VG_100K_2/1.jpg</img> Does the bandage have a different color than the wrist band?
Answer: No, both the bandage and the wrist band are white.<eos>
3.3 SFT - Qwen-VL-Chat
这是 Qwen-VL 从预训练模型到对话模型(Qwen-VL-Chat)的有监督微调(SFT)阶段。SFT 本身就能出 Chat 模型, 后续没有 RLHF 和 DPO 等对齐.
冻结 ViT,只调 LLM 和 Adapter,用 ChatML 格式的指令数据微调出 Chat 版本.
核心信息几个点.
数据总量:指令微调数据一共 350k 条,对于当时的多模态模型来说算比较精简的。
训练策略:冻结视觉编码器(Visual Encoder),只优化语言模型和 Adapter 模块。这是一个很常见的做法——视觉编码器在预训练阶段已经学好了特征提取能力,SFT 阶段不需要再动它,既省算力也避免灾难性遗忘。
// The Dataset Format Example of ChatML
<im_start>user
Picture 1: <img>vg/VG_100K_2/649.jpg</img>What is the sign in the picture?<im_end>
<im_start>assistant
The sign is a road closure with an orange rhombus.<im_end>
<im_start>user
How is the weather in the picture?<im_end>
<im_start>assistant
The shape of the road closure sign is an orange rhombus.<im_end>
4. 评测 benchmarks
Image Caption
General VQA
Instruction Following, 指令遵循.
5. hf-transformers 推理实操
选 Qwen2-VL-2B-Instruct + nvidia A10 24G.
5.1 大小拆解
从 hf 社区拉取 qwen 的模型文件. 模型权重 safetensors 文件占用为 4GB.
- 参数量
模型 = 语言模型(~ 1.54B,约 71%)+ 视觉编码器(~ 0.63B,约 29%), 共 2.17B. - 存储精度. 为 BF16, 2 bytes/参数.
- bf16 = 砍掉 fp32 一半尾数、保留全部指数位的"短 fp32",所以它和 fp32 之间转换非常快(移位即可),且不容易溢出——这也是它在深度学习里取代 fp16 的原因。
- 综合计算, 2.17Billion * 2 Byte = 4.3GigaByte=
4.3 GB.
语言模型
| 配置项 | 值 | 作用 |
|---|---|---|
num_hidden_layers | 28 | Transformer 层数 |
hidden_size (H) | 1536 | 隐藏维度,模型宽度 |
intermediate_size (I) | 8960 | MLP 中间维度(≈5.83×H) |
num_attention_heads (Q) | 12 | Query 头数(head_dim = 128) |
num_key_value_heads (KV) | 2 | KV 头数(GQA,远小于 QH) |
vocab_size (V) | 151936 | 词表大小 |
tie_word_embeddings | true | embedding 与 lm_head 共享权重 |
视觉编码器(vision_config)
| 配置项 | 值 | 作用 |
|---|---|---|
depth | 32 | ViT 层数(比 LLM 还多) |
embed_dim (Eh) | 1280 | ViT 隐藏维度 |
num_heads | 16 | 注意力头数(标准 MHA,不用 GQA) |
mlp_ratio | 4 | MLP 中间维度倍数 |
patch_size | 14 | 空间 patch 大小 |
temporal_patch_size | 2 | 时间 patch 大小 |
spatial_merge_size | 2 | 2×2 视觉 token 合并为 1 |
in_chans | 3 | RGB 三通道 |
5.2 processor
- 构造 messages
messages: list[dict[str, object]] = [ { "role": "user", "content": [ {"type": "image", "image": "./pics/罗小黑.jpg"}, {"type": "text", "text": "请描述这张图片中有什么?"}, ], } ] - qwen_vl_utils.
process_vision_info() .- 这个工具把 messages 里的 image path 转为 PIL.Image 对象, 原始尺寸为
(400,744). - 做 smart resize, 是 patch_size=14 的整数倍, 于是为 (392=28x14, 756=54x14)
- 这个工具把 messages 里的 image path 转为 PIL.Image 对象, 原始尺寸为
Qwen2VLProcessor.call().inputs: BatchFeature = processor( text=[text], images=images, return_tensors="pt", ) BatchFeature 继承自 UserDict, 用法上就是 dict[str, torch.Tensor] 本例 inputs 实际内容: input_ids shape = (1, 404) dtype = torch.int64 attention_mask shape = (1, 404) dtype = torch.int64 pixel_values shape = (1512, 1176) dtype = torch.float32 image_grid_thw shape = (1, 3) dtype = torch.int64- pixel_values, 该 tensor 的第一维代表patch 的个数
1512 = 392/14*756/14; 第二维=1176 = patch_size**2 * channel_num * temporal_size= 14**2 * 3 * 2. - image_grid_thw, (t=1, h=54, w=28), 其中 t = 时间维(静态图片为 1,视频才有意义)、h/w = 空间方向上的 patch 网格行列数
- pixel_values, 该 tensor 的第一维代表patch 的个数
图片切 patch 实现
以 8*8 方阵为例, 切后 img[i][j] 表示 patch 的二维排列.
错误: img.reshape(H//p, W//p, -1) -> [i][j] 是 1x4 横条
正确: img.reshape(H//p, p, W//p, p) -> 每个空间维度拆成 (块数, 块内)
.permute(0, 2, 1, 3) -> 块号维度挪到外层
.reshape(H//p, W//p, -1) -> [i][j] 是 2x2 方块
demo 可见参考[5], reshape_patch_demo.py.
5.3 model
# 加载模型 (使用 bfloat16 节省显存)
model = Qwen2VLForConditionalGeneration.from_pretrained(
"models/Qwen2-VL-2B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
6. 业务场景的 SFT 及 DPO
以上是开源工作. 发布在 HuggingFace, ModelScope 等社区, 如 Qwen3-VL-8B-Instruct
如果想结合自己的业务做SFT, 对于 Qwen-VL 8B 大小的模型, 可以选用 内存>40G的 GPU, 如 英伟达A800,
附录
chatML
Chat Markup Language, 类比于 xml, 由 openAI 推出的 将 对话 组织为样本的 结构化表示格式. 把「指令 + 输入 + 期望输出」组织成 role 化的消息序列:
- system:设定角色/规则(可选)
- user:指令 + 输入
- assistant:期望的回答(训练时算 loss 的部分)
一个例子:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
指令数据是什么?<|im_end|>
<|im_start|>assistant
指令数据是指……<|im_end|>
参考
更多推荐
所有评论(0)