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 之间. 作用有

  1. 是把 ViT 输出的 不定长 (跟分辨率直接相关) 的 token 数量压缩到固定 256 个 token.
  2. 注入位置信息. 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_layers28Transformer 层数
hidden_size (H)1536隐藏维度,模型宽度
intermediate_size (I)8960MLP 中间维度(≈5.83×H)
num_attention_heads (Q)12Query 头数(head_dim = 128)
num_key_value_heads (KV)2KV 头数(GQA,远小于 QH)
vocab_size (V)151936词表大小
tie_word_embeddingstrueembedding 与 lm_head 共享权重

视觉编码器(vision_config)

配置项作用
depth32ViT 层数(比 LLM 还多)
embed_dim (Eh)1280ViT 隐藏维度
num_heads16注意力头数(标准 MHA,不用 GQA)
mlp_ratio4MLP 中间维度倍数
patch_size14空间 patch 大小
temporal_patch_size2时间 patch 大小
spatial_merge_size22×2 视觉 token 合并为 1
in_chans3RGB 三通道

5.2 processor

  1. 构造 messages
     messages: list[dict[str, object]] = [
         {
             "role": "user",
             "content": [
                 {"type": "image", "image": "./pics/罗小黑.jpg"},
                 {"type": "text", "text": "请描述这张图片中有什么?"},
             ],
         }
     ]
    
  2. qwen_vl_utils.process_vision_info() .
    1. 这个工具把 messages 里的 image path 转为 PIL.Image 对象, 原始尺寸为 (400,744) .
    2. 做 smart resize, 是 patch_size=14 的整数倍, 于是为 (392=28x14, 756=54x14)
  • 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 网格行列数

图片切 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|>

参考

  1. Qwen-VL
  2. Qwen2-VL: Enhancing Vision-Language Model’s Perception of the World at Any Resolution
  3. ViT 论文, 2020 , Google Research An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale
  4. my blog, LLM 大模型的 SFT 及 DPO 实践
  5. my code, reshape_patch_demo.py

更多推荐