1.直观案例解读-图文问答
假设我们的输入是一张包含小猫的图片,以及一个文本提问:“其中是否有小猫?”。下面我将以最详尽的方式,描述数据在nanoVLM
模型中从输入到输出的完整流动过程,并解释每一步中数据的形状和含义。
为方便理解,我们设定一些具体的维度(这些维度与nanoVLM
中的常见配置类似):
- 图像输入尺寸: 224x224 像素
- ViT Patch 大小: 16x16
- ViT 隐藏层维度: 768
- 语言模型隐藏层维度: 2560
- 词汇表大小: 51200
1.1 第一步:图像编码 (Vision Transformer)
这是模型的“眼睛”看到并理解图像的过程。
-
输入数据: 一张图片。
- 形状: 在送入模型前,图片被预处理成一个
[1, 3, 224, 224]
的张量(Tensor)。 - 含义:
[批量大小, 颜色通道, 高度, 宽度]
。这是一个代表了原始像素值的四维数组。
- 形状: 在送入模型前,图片被预处理成一个
-
分块与嵌入 (Patch Embedding): 图片进入
ViT
的ViTPatchEmbeddings
层。- 过程: 一个16x16的卷积核以16像素的步长滑过整张图片,将图片分割成 (224/16) x (224/16) = 14 x 14 = 196个小块(Patches)。同时,这个卷积操作将每个Patch从像素值(16x16x3)直接线性投射成一个768维的向量。模型还会额外添加一个特殊的
[CLS]
向量用于聚合全局信息。 - 输出数据:
vision_embeds
- 形状:
[1, 197, 768]
- 含义:
[批量大小, Patch数量+1, ViT隐藏维度]
。现在,原始的图片被转换成了一个由197个向量组成的序列。每个向量都是对一小块局部图像区域的浓缩数学描述。
- 过程: 一个16x16的卷积核以16像素的步长滑过整张图片,将图片分割成 (224/16) x (224/16) = 14 x 14 = 196个小块(Patches)。同时,这个卷积操作将每个Patch从像素值(16x16x3)直接线性投射成一个768维的向量。模型还会额外添加一个特殊的
1.2 第二步:模态投影 (Modality Projection)
这是连接“视觉世界”和“语言世界”的桥梁。
-
输入数据:
vision_embeds
- 形状:
[1, 197, 768]
- 形状:
-
投影与重塑: 数据进入
ModalityProjector
层。- 过程: 首先,一个线性层将每个768维的视觉向量投射到与语言模型匹配的2560维。然后,通过
PixelUnshuffle
等操作,将这196个Patch向量([CLS]
向量通常被暂时分离)展平成一个更长的序列,比如256个向量。这样做是为了让图像信息在序列长度上更接近于一段文字,方便语言模型处理。 - 输出数据:
projected_vision_embeds
- 形状:
[1, 256, 2560]
- 含义:
[批量大小, 图像Token数量, 语言模型隐藏维度]
。这些可以被看作是**“图像词元”**。视觉信息现在被编码成了语言模型能够直接理解的格式,每个向量的维度都和语言模型中普通词汇的向量维度完全一样。
- 过程: 首先,一个线性层将每个768维的视觉向量投射到与语言模型匹配的2560维。然后,通过
1.3 第三步:文本编码与多模态拼接
这是将我们的问题和处理好的图像信息融合在一起的过程。
-
文本分词 (Tokenization):
- 输入: 文本字符串 “其中是否有小猫?”
- 过程: 分词器(Tokenizer)将文本切分成Token序列,例如
['其中', '是', '否', '有', '小', '猫', '?']
。为了进行对话,模型还会加上特殊的控制Token,例如['<|user|>', '其中', '是', '否', '有', '小', '猫', '?', '<|assistant|>']
。假设这变成了10个Token。 - 输出:
input_ids
,一个由Token ID组成的序列。 - 形状:
[1, 10]
-
文本嵌入 (Text Embedding):
input_ids
进入语言模型的词嵌入层。- 过程: 每个Token ID都会被映射成一个2560维的向量。
- 输出:
text_embeds
- 形状:
[1, 10, 2560]
-
拼接 (Concatenation):
- 过程: 模型将处理好的图像词元 (
projected_vision_embeds
) 和文本词元 (text_embeds
) 拼接成一个统一的序列。 - 输出数据:
final_inputs_embeds
- 形状:
[1, 266, 2560]
(256个图像Token + 10个文本Token) - 含义: 这是送入语言模型主体的最终输入。它是一个完整的、融合了多模态信息的上下文序列,既包含了“看”到的内容,也包含了“听”到的问题。
- 过程: 模型将处理好的图像词元 (
1.4 第四步:自回归文本生成 (Language Model)
这是模型进行“思考”并“逐字作答”的核心循环过程。
-
初次前向传播 (生成第一个字):
- 输入:
final_inputs_embeds
(形状:[1, 266, 2560]
) - 过程: 整个序列通过语言模型的多个Transformer层进行计算。在最后一层,模型会输出一个logits张量。
- 数据 (Logits):
- 形状:
[1, 266, 51200]
- 含义:
[批量, 序列长度, 词汇表大小]
。这个张量代表了在序列中每个位置的下一个Token可能是什么的概率分布。
- 形状:
- 选择下一个字: 我们只关心序列最后一个位置(第266个)的预测结果。模型取出这个位置的logits
[1, 51200]
,通过采样(例如argmax
或温度采样)选择概率最高的一个Token。假设选中的是**“是”**。
- 输入:
-
循环生成 (生成后续的字):
- 更新输入: 将刚刚生成的Token“是”也进行嵌入,得到一个
[1, 1, 2560]
的向量。然后,将这个新向量拼接到final_inputs_embeds
的末尾。 - 新的输入:
new_inputs_embeds
- 形状:
[1, 267, 2560]
- 含义: 上下文现在变成了“图像+问题+‘是’”。
- 形状:
- 再次前向传播: 将这个新的、更长的序列再次送入语言模型,重复上述过程:获取最后一个位置(第267个)的logits
[1, 51200]
,从中采样得到下一个Token,比如**“的”**。 - 持续循环: 这个“预测->拼接->再预测”的过程不断重复。模型会接着生成 “,”、“图”、“片”、“中”、“有”、“一”、“只”、“小”、“猫”、“。” 等Token。每生成一个新字,它都会成为下一次预测的上下文的一部分。
- 更新输入: 将刚刚生成的Token“是”也进行嵌入,得到一个
-
生成结束:
- 过程: 当模型生成一个特殊的终止符(
[EOS]
token)或者达到预设的最大长度时,循环停止。 - 最终输出: 将所有生成的Token ID (
['是', '的', ',', ..., '。']
)通过分词器解码,变回人类可读的字符串:“是的,图片中有一只小猫。”
- 过程: 当模型生成一个特殊的终止符(
2.直观案例解读-视频问答
结合业界对视频处理的现有知识,来详细解读一个典型的**视频问答(VideoQA)**模型是如何工作的。
视频问答的核心挑战在于:如何将视频中随时间变化的信息(动态信息)有效地表示出来,并与问题相结合。
下面,我们将遵循与之前类似的分析框架,详细描述当用户输入一段视频和问题“视频里的人在做什么?”时,数据在模型中的流动、形状和含义。
2.1 第一步:视频采样 (Frame Sampling)
模型无法一次性处理视频的每一帧,这在计算上是不可行的。因此,第一步是从视频中提取出有代表性的关键帧。
-
输入数据: 一段视频文件(例如
running.mp4
)。- 形状: 一个视频文件流。
- 含义: 包含了连续图像帧和音频的原始多媒体数据。
-
采样过程:
- 过程: 通过一个采样策略,从视频中每隔一段时间抽取一帧。例如,一个10秒的视频,每秒抽一帧,就可以得到10帧。或者,也可以采用更复杂的采样方法,只在镜头变化或物体运动显著时才采样。假设我们均匀采样了
N
帧(例如N=16
)。 - 输出数据: 一个由
N
帧图像组成的批次。 - 形状:
[N, 3, H, W]
,例如[16, 3, 224, 224]
。 - 含义:
[帧数, 颜色通道, 高度, 宽度]
。我们将连续的视频信号转换成了一个离散的、有序的图像序列,这是模型可以开始处理的格式。
- 过程: 通过一个采样策略,从视频中每隔一段时间抽取一帧。例如,一个10秒的视频,每秒抽一帧,就可以得到10帧。或者,也可以采用更复杂的采样方法,只在镜头变化或物体运动显著时才采样。假设我们均匀采样了
2.2 第二步:空间特征提取 (Spatial Feature Extraction)
这一步与nanoVLM
中的图像处理非常相似,但我们需要对采样出来的每一帧都进行操作。
-
输入数据: 上一步得到的
N
帧图像。- 形状:
[16, 3, 224, 224]
- 形状:
-
逐帧编码:
- 过程: 将这16帧图像逐一送入一个预训练的图像编码器(例如
nanoVLM
中的ViT
)。ViT
会对每一帧都执行“分块+嵌入”操作。 - 输出数据:
frame_features
,一个包含了所有帧特征的序列。 - 形状:
[16, 197, 768]
- 含义:
[帧数, 每帧的Patch数+1, ViT隐藏维度]
。这个三维张量非常关键,它既包含了每一帧图像的空间信息(197x768这部分),也通过第一个维度(16)保留了时间顺序。
- 过程: 将这16帧图像逐一送入一个预训练的图像编码器(例如
2.3 第三步:时序特征聚合 (Temporal Feature Aggregation)
这是视频问答区别于图像问答的核心步骤。我们需要将分散在16帧中的信息,融合成一个能代表整个视频动态内容的向量。
-
输入数据:
frame_features
- 形状:
[16, 197, 768]
- 形状:
-
聚合过程: 业界有多种方法来聚合时序信息,这里介绍一种主流的思路:
- 过程: 使用一个时序模型(例如Transformer Encoder或GRU)来学习这些帧之间的时间依赖关系。你可以将这16个
[197, 768]
的特征块看作是16个“时间步”,然后让模型学习这些时间步之间的关联。例如,一个Transformer编码器可以计算第8帧和第3帧之间的“注意力”,从而理解动作的连续性。 - 输出数据:
aggregated_video_features
- 形状: 经过时序模型处理后,通常会将输出的序列进行池化(Pooling)或只取特定
[CLS]
Token的输出来得到一个固定大小的张量。例如,[1, 256, 2560]
。 - 含义:
[批量大小, 视频Token数, 语言模型隐藏维度]
。这个向量/向量序列是整个视频时空信息的精华。它不再是零散的帧,而是对视频“发生了什么”的整体性、动态性描述,并且已经被投影到了语言模型可以理解的维度空间。
- 过程: 使用一个时序模型(例如Transformer Encoder或GRU)来学习这些帧之间的时间依赖关系。你可以将这16个
2.4 第四步:文本编码与多模态拼接
这一步与nanoVLM
中的流程完全相同。
-
文本处理: 将问题 “视频里的人在做什么?” 进行分词和嵌入。
- 输出形状:
[1, 12, 2560]
(假设问题被分成12个Token)。
- 输出形状:
-
拼接: 将处理好的视频特征 (
aggregated_video_features
) 和文本特征拼接成一个统一的序列。- 输出数据:
final_inputs_embeds
- 形状:
[1, 268, 2560]
(256个视频Token + 12个文本Token)。 - 含义: 这是送入语言模型最终决策者的完整上下文,包含了视频的动态内容和用户的具体提问。
- 输出数据:
第五步:自回归文本生成
这一步也和nanoVLM
的流程完全相同。
- 输入:
final_inputs_embeds
- 过程: 语言模型接收这个融合了时空信息的序列,并开始自回归地逐字生成答案。
- T=1: 模型根据整个视频和问题,预测出第一个最可能的词,例如“他们”。
- T=2: 模型将“他们”作为新的上下文,结合原始视频和问题,预测出第二个词,例如“正在”。
- T=3…: 不断重复,直到生成一个完整的句子,例如“他们正在公园里跑步。”,并最终生成一个结束符。
通过以上步骤,模型就成功地将一个动态的视频内容转化为了静态的、结构化的答案。
3.训练过程
在典型的图像多模态大模型(如nanoVLM
)的训练中,采用分阶段的训练策略是至关重要的。这主要是为了高效地利用强大的预训练模型,并稳定地教会它们协同工作,同时控制巨大的计算开销。
一般的操作逻辑可以分为两个核心阶段,有时会加上一个可选的第三阶段。
3.1 第一阶段:特征对齐训练 (Alignment Training)
这个阶段的目标是搭建两种模态之间的“桥梁”,让语言模型能够“看懂”视觉编码器输出的特征。
-
操作逻辑:
- 冻结 (Freeze) 视觉编码器 (Vision Encoder):像ViT这样的视觉模型已经在海量图像数据上进行了预训练,具备了强大的通用视觉特征提取能力。我们暂时不希望破坏这种能力,因此将其所有参数锁定,不进行任何更新。它只作为一成不变的特征提取器。
- 冻结 (Freeze) 语言模型 (Language Model):LLM(如Phi-2, Llama)同样是预训练好的,拥有强大的语言理解和生成能力。如果在一开始就让不成熟的梯度信号传入,可能会严重破坏其内部的语言结构,造成“灾难性遗忘”。因此,我们也完全冻结它。
- 只训练 (Train Only) 模态投影器 (Modality Projector):这个投影器是新加入的、唯一从零开始训练的组件。它的参数在训练开始时是随机初始化的。
-
为什么这么做? (Why?):
- 效率最高:训练一个几百万参数的投影器,远比训练两个数十亿参数的主干模型要快得多,对显存(VRAM)的需求也小得多。
- 稳定性好:这是最关键的原因。此阶段的唯一任务是,教会投影器如何将视觉编码器输出的“视觉语言”(一堆向量)翻译成语言模型能理解的“文本化语言”(格式和维度都与词向量一致的向量)。由于两个主干模型被冻结,来自投影器的、最初不稳定的梯度信号不会“污染”它们。
- 目标明确:先把“翻译官”训练好,让它能准确地传话。
-
数据流动示意:
图像
->[冻结的ViT]
->视觉特征
->[训练中的Projector]
->对齐后的视觉特征
->(与文本拼接)
->[冻z结的LLM]
->(计算损失,但只更新Projector)
3.2 第二阶段:指令微调 (Instruction Fine-tuning)
当投影器能够提供基本靠谱的“翻译”后,我们就可以开始教整个模型如何根据用户的指令,结合图像内容进行思考和回答了。
-
操作逻辑:
- 继续冻结 (Keep Frozen) 视觉编码器:通常情况下,视觉编码器的通用特征提取能力已经足够好,继续冻结可以节省计算资源,并防止其在指令数据上过拟合。
- 解冻 (Unfreeze) 语言模型:这是此阶段的核心。现在我们要让语言模型学习如何理解和运用投影器传来的视觉信息,并将其融入到它的语言逻辑中。
- 继续训练 (Continue Training) 模态投影器:投影器也可以在这一步与语言模型一起进行微调,以更好地协同工作。
-
一种更高效的变体:LoRA微调
在nanoVLM
这个项目中,采用了一种更高效的方式来“解冻”语言模型,即LoRA (Low-Rank Adaptation)。它并不解冻整个语言模型,而是在模型的关键部分(如Attention层)旁边挂上一些小型的、可训练的“适配器”矩阵。- 操作: 保持语言模型主干依然冻结,只训练这些新增的、轻量级的LoRA层。
- 优势: 效果与完全解冻(全量微调)相近,但需要更新的参数量骤减(只有原来的1%甚至更少),极大地降低了显存消耗,使得在消费级显卡(如4090)上微调数十亿参数的LLM成为可能。
-
为什么这么做? (Why?):
- 学习推理:在第一阶段,LLM只是被动地“接收”了视觉信息。在这一阶段,它要主动学习如何将这些信息用于遵循指令、进行对话、回答问题等复杂的认知任务。
- 深度融合:让LLM的内部权重进行调整,以便更深入地将视觉概念(例如“一只猫的纹理”)与语言概念(单词“猫”)联系起来。
-
数据流动示意 (LoRA):
图像
->[冻结的ViT]
->视觉特征
->[训练中的Projector]
->对齐后的视觉特征
->(与文本拼接)
->[冻结的LLM主干 + 训练中的LoRA层]
->(计算损失,更新Projector和LoRA层)
3.3 第三阶段(可选):端到端微调 (End-to-End Fine-tuning)
在某些情况下,为了追求极致的性能,研究人员可能会进行一个可选的最终阶段。
-
操作逻辑:
- 解冻 (Unfreeze) 所有组件:将视觉编码器、投影器和语言模型(或其LoRA层)全部设为可训练。
- 使用非常小的学习率:对整个模型进行非常谨慎的、端到端的微调。
-
为什么这么做? (Why?):
- 全局最优化:允许视觉编码器也进行微调,可能会让它学会提取一些“更有利于”当前语言模型进行理解”的特定视觉特征,实现最深度的协同。
- 风险与成本高: 这是计算成本最高的阶段,且学习率需要精心调整,否则模型性能很容易崩溃。因此,这个阶段并不常用。
这个**“冻结 -> 训练部分 -> 再解冻 -> 微调”**的分阶段逻辑,是平衡性能、效率和稳定性的关键,也是当前多模态大模型训练的标准范式。
好的,我们从头开始,以最完整、连贯的顺序,详细描述当您向 Qwen2.5-VL 输入一段视频和一句问题时,数据在模型内部是如何流动的。
场景设定:
- 输入视频: 一段10秒的视频,内容是一个男人在公园里跑步。
- 输入问题: “视频中的男人在做什么?”
第一步:视频预处理 (Video Pre-processing)
模型的第一项任务是把连续、动态的视频信号,转换成离散、有序的、计算机可以处理的单元。
- 动态帧率采样 (Dynamic FPS Sampling):
- [cite_start]过程: 模型不会分析视频的每一帧,这在计算上是巨大的浪费。它会智能地进行采样。例如,对于跑步这种动作连续的视频,它可能决定每秒采样2帧。如果视频后半段该男子坐下休息,采样率可能会自动降低。假设最终从10秒的视频中均匀采样了 16帧 图像 [cite: 32, 147]。
- 输出数据: 16张独立的、代表了视频关键瞬间的静态图像。
- 形状:
[16, 3, 224, 224]
(假设图像都被处理成224x224分辨率)。 - 含义:
[总帧数, 颜色通道, 高度, 宽度]
。视频流成功转换成了一个有序的图像序列。
第二步:时空特征提取 (Spatio-Temporal Feature Extraction)
这一步由经过特殊改造的 Vision Transformer (ViT) 视觉编码器 完成,其目标是同时捕捉图像的空间细节和帧与帧之间的时间变化。
- 3D图像块划分 (3D Patch Partitioning):
- [cite_start]过程: 这是Qwen2.5-VL的一个关键效率优化。它并非逐帧处理,而是将连续的两帧分为一组 [cite: 74]。这16帧因此被分成了8个“帧对”。然后,一个
2x14x14
的3D卷积核会同时在这8个帧对上进行滑动。每一次滑动,都相当于从一个2x14x14
大小的“时空立方体”中提取特征。 - 输出: 每一帧被划分为
(224/14) * (224/14) = 196
个图像块(patch)。因为是两帧一组,所以总共提取了8 * 196 = 1568
个时空特征向量。 - 输出数据:
raw_video_features
。 - 形状:
[1568, 1280]
(假设ViT的隐藏层维度是1280)。 - 含义:
[总时空块数量, ViT隐藏维度]
。这是一个初步的、海量的、融合了局部空间信息和短时程时间变化(2帧内)的特征集合。
- [cite_start]过程: 这是Qwen2.5-VL的一个关键效率优化。它并非逐帧处理,而是将连续的两帧分为一组 [cite: 74]。这16帧因此被分成了8个“帧对”。然后,一个
第三步:视觉特征压缩与对齐 (Vision Feature Compression & Alignment)
第二步产生的特征向量序列太长(1568个),维度也可能与语言模型不匹配。这一步通过一个 MLP-based Vision-Language Merger(基于MLP的视觉-语言合并器)来解决这个问题。
-
分组与拼接 (Grouping & Concatenation):
- [cite_start]过程: 模型将空间上相邻的4个图块特征分为一组 [cite: 59][cite_start]。因此,1568个特征向量被分成了
1568 / 4 = 392
组。在每一组内,4个1280维的向量被**拼接(Concatenate)**起来 [cite: 60]。 - 中间数据形状:
[392, 4 * 1280]
,即[392, 5120]
。
- [cite_start]过程: 模型将空间上相邻的4个图块特征分为一组 [cite: 59][cite_start]。因此,1568个特征向量被分成了
-
MLP投影 (MLP Projection):
- [cite_start]过程: 将这392个5120维的特征向量,送入一个两层的MLP(多层感知机)。这个MLP会将特征压缩并投影到一个与语言模型词嵌入维度相匹配的维度 [cite: 60],例如8192维(对于72B模型)。
- 输出数据:
compressed_video_features
。 - 形状:
[392, 8192]
。 - 含义:
[视频词元数量, 语言模型隐藏维度]
。现在,我们得到了一个长度大大缩短、维度完全对齐的**“视频词元”**序列。它既保留了视频的核心信息,又显著降低了后续计算的负担,为与文本的拼接做好了准备。
第四步:文本处理与多模态融合 (Text Processing & Multimodal Fusion)
现在,模型将用户的文本问题与处理好的视频信息融合在一起。
-
文本编码:
- 过程: 问题 “视频中的男人在做什么?” 被分词器(Tokenizer)转换成一个Token ID序列,然后通过词嵌入层,映射成向量。
- 输出数据:
text_embeds
。 - 形状:
[15, 8192]
(假设问题被分成15个Token)。
-
最终拼接:
- 过程: 将压缩好的
compressed_video_features
(形状[392, 8192]
)和text_embeds
(形状[15, 8192]
)按顺序拼接在一起。 - 输出数据:
final_inputs_embeds
。 - 形状:
[1, 407, 8192]
(392个视频词元 + 15个文本词元)。 - 含义: 这才是最终送入语言模型解码器的、数据完全衔接的输入序列。它是一个完整的、融合了多模态信息的上下文,既包含了“看”到的内容,也包含了“听”到的问题,且所有向量的维度都已统一。
- 过程: 将压缩好的
第五步:带有绝对时间信息的自回归生成
这是模型进行“思考”并“逐字作答”的最后一步,也是Qwen2.5-VL的创新核心所在。
-
应用绝对时间对齐的MROPE:
- [cite_start]过程: 在将
final_inputs_embeds
序列送入语言模型的Transformer层之前,模型会为其计算多模态旋转位置嵌入(MROPE) [cite: 93][cite_start]。这里的关键创新是:序列中属于视频的那392个词元,其位置编码中的时间维度ID是与视频的真实采样时间戳对齐的 [cite: 90, 100]。例如,来自第0秒的视频词元,其时间ID就是0.0;来自第0.5秒的,ID就是0.5。 - 含义: 通过这种方式,绝对时间信息被无缝地注入到了每一个视频词元的位置编码中。这使得语言模型在后续处理中,能够通过计算这些时间ID之间的间隔来感知事件的快慢、持续时间和发生时刻。
- [cite_start]过程: 在将
-
语言模型解码与循环生成:
- 过程: 携带了绝对时间位置信息的序列进入 Qwen2.5 LM Decoder 进行计算。
- 生成第一个字: 模型通过多层注意力机制,综合考虑整个视频内容和问题,在序列的最后一个位置(第407个)输出一个对整个词汇表的概率分布,并从中采样出第一个生成的字,例如“视频”。
- 循环与结束: 将“视频”这个新生成的字再次嵌入并附加到输入序列的末尾,形成一个更长的、包含新上下文的序列。模型再次对这个新序列进行处理,预测出下一个字“中”。这个**“预测->拼接->再预测”**的过程不断循环,直到生成完整的答案“视频中的男人正在跑步。”,并最终生成一个特殊的结束符(End-of-Sentence Token),标志着回答完毕。
通过以上五个紧密衔接的步骤,Qwen2.5-VL成功地将一个动态的视频、一个静态的问题,转化成了一段流畅、准确、且可能包含精确时间信息的自然语言回答。