首先我们看一下源码的结构再一步一步说明
Qwen3VLMoeForConditionalGeneration(
(model): Qwen3VLMoeModel(
(visual): Qwen3VLMoeVisionModel(
(patch_embed): Qwen3VLMoeVisionPatchEmbed(
(proj): Conv3d(3, 1152, kernel_size=(2, 16, 16), stride=(2, 16, 16))
)
(pos_embed): Embedding(2304, 1152)
(rotary_pos_emb): Qwen3VLMoeVisionRotaryEmbedding()
(blocks): ModuleList(
(0-26): 27 x Qwen3VLMoeVisionBlock(
(norm1): LayerNorm((1152,), eps=1e-06, elementwise_affine=True)
(norm2): LayerNorm((1152,), eps=1e-06, elementwise_affine=True)
(attn): Qwen3VLMoeVisionAttention(
(qkv): Linear(in_features=1152, out_features=3456, bias=True)
(proj): Linear(in_features=1152, out_features=1152, bias=True)
)
(mlp): Qwen3VLMoeVisionMLP(
(linear_fc1): Linear(in_features=1152, out_features=4304, bias=True)
(linear_fc2): Linear(in_features=4304, out_features=1152, bias=True)
(act_fn): PytorchGELUTanh()
)
)
)
(merger): Qwen3VLMoeVisionPatchMerger(
(norm): LayerNorm((1152,), eps=1e-06, elementwise_affine=True)
(linear_fc1): Linear(in_features=4608, out_features=4608, bias=True)
(act_fn): GELU(approximate='none')
(linear_fc2): Linear(in_features=4608, out_features=4096, bias=True)
)
(deepstack_merger_list): ModuleList(
(0-2): 3 x Qwen3VLMoeVisionPatchMerger(
(norm): LayerNorm((4608,), eps=1e-06, elementwise_affine=True)
(linear_fc1): Linear(in_features=4608, out_features=4608, bias=True)
(act_fn): GELU(approximate='none')
(linear_fc2): Linear(in_features=4608, out_features=4096, bias=True)
)
)
)
(language_model): Qwen3VLMoeTextModel(
(embed_tokens): Embedding(151936, 4096)
(layers): ModuleList(
(0-93): 94 x Qwen3VLMoeTextDecoderLayer(
(self_attn): Qwen3VLMoeTextAttention(
(q_proj): Linear(in_features=4096, out_features=8192, bias=False)
(k_proj): Linear(in_features=4096, out_features=512, bias=False)
(v_proj): Linear(in_features=4096, out_features=512, bias=False)
(o_proj): Linear(in_features=8192, out_features=4096, bias=False)
(q_norm): Qwen3VLMoeTextRMSNorm((128,), eps=1e-06)
(k_norm): Qwen3VLMoeTextRMSNorm((128,), eps=1e-06)
)
(mlp): Qwen3VLMoeTextSparseMoeBlock(
(gate): Qwen3VLMoeTextRouter(in_features=4096, out_features=128, bias=False)
(experts): Qwen3VLMoeTextExperts(
(act_fn): SiLU()
)
)
(input_layernorm): Qwen3VLMoeTextRMSNorm((4096,), eps=1e-06)
(post_attention_layernorm): Qwen3VLMoeTextRMSNorm((4096,), eps=1e-06)
)
)
(norm): Qwen3VLMoeTextRMSNorm((4096,), eps=1e-06)
(rotary_emb): Qwen3VLMoeTextRotaryEmbedding()
)
)
(lm_head): Linear(in_features=4096, out_features=151936, bias=False)
)
模型主要由三个部分组成:
- Vision Model (视觉编码器):负责提取图像/视频特征。
- Merger / Adapter (连接层):将视觉特征压缩并投影到语言模型的维度。
- Language Model (语言模型):基于MoE架构的文本解码器,负责理解和生成。
一:视觉编码器
这部分的作用是将输入的图像或视频转化为特征向量,方面之后让LLM看懂图像
-
Patch Embedding (块嵌入)
Conv3d(3, 1152, kernel_size=(2, 16, 16), stride=(2, 16, 16))* 输入通道为3(RGB)。 * 输出通道(Hidden Size)为 **1152**。 * `kernel_size=(2, 16, 16)`: 表示它一次处理2帧画面(时间维度),以及16x16像素的空间区域。 * 这意味着视觉部分的基础Token维度是 **1152**。 -
Positional Embedding (位置编码)
pos_embed: 学习到的绝对位置编码。
rotary_pos_emb: 旋转位置编码(RoPE),增强模型对相对位置的感知。 -
Encoder Blocks (编码器层)
数量: 27层
结构: 标准的Transformer Encoder结构。
Attention:
qkv: 输入1152 →\to→ 输出3456。这是标准的 1152×31152 \times 31152×3 (Query, Key, Value),没有使用GQA,是全注意力机制。
MLP:
中间层维度扩张:1152→43041152 \to 43041152→4304 (约为 1152×41152 \times 41152×4 的SwiGLU变体结构)。
注意为这个部分是dense结构,因为vit encoder的主要作用是读取图片的信息,所以需要一个较大MLP层。联想MHA的原论文,MLP也进行了四倍参数的扩大,但是苦于encoder的输出与输出维度要相同,所以最后又回归了一倍参数。但是其中的思想是越大的MLP层能存储越多的信息
关于 4304:标准的 Transformer MLP 通常放大 4 倍(1152×4)=4608)。这里选择了 4304(约为 3.7倍),这通常是出于显存优化或硬件对齐(如 TPU/GPU 的 Tile 大小)的工程考量,差异不大。 -
Mergers (特征融合与对齐)
关键层:merger和deepstack_merger_list。
原理: 视觉编码器的输出维度是1152,但语言模型的输入维度是4096。此外,为了减少Token数量,通常会进行空间池化(Pooling)。
merger(Qwen3VLMoeVisionPatchMerger):
linear_fc1: 输入 4608 →\to→ 输出 4608。
为什么是4608?1152×4=46081152 \times 4 = 46081152×4=4608。这意味着模型进行了 2x2 的空间合并(Patch Merging)。将相邻的4个视觉Token拼接在一起,作为一个新的Token。
linear_fc2: 输入 4608 →\to→ 输出 4096。
这里完成了维度的对齐,将视觉特征投影到了LLM的隐藏层维度(4096)。
deepstack_merger_list: 包含3个额外的Merger。这暗示模型可能使用了多层特征聚合(类似于Qwen-VL),不仅仅使用最后一层的视觉特征,可能还融合了倒数几层的特征,以保留更丰富的细节。
第二部分:Language Model (语言模型)
负责处理文本和经过对齐的视觉Token
-
Embedding (词嵌入)
Embedding(151936, 4096)
词表大小: 151,936 (与Qwen2.5系列一致)。
Hidden Size (模型宽度): 4096。 -
Decoder Layers (解码器层)
数量: 94层。这是一个非常深的模型,通常意味着极大的参数量(例如A14B或者72B级别的Base模型)。
Self-Attention (自注意力机制):
q_proj: 4096→81924096 \to 81924096→8192。
k_proj/v_proj: 4096→5124096 \to 5124096→512。
解读: 这是非常激进的 GQA (Grouped Query Attention)。
假设头维度(Head Dim)为128(根据q_norm形状推断)。
Query Heads: 8192/128=648192 / 128 = 648192/128=64 个头。
KV Heads: 512/128=4512 / 128 = 4512/128=4 个头。
GQA Group: 16。这极大地减少了KV Cache的显存占用,适合长上下文推理。
MoE MLP (混合专家模块):
类名:Qwen3VLMoeTextSparseMoeBlock。
gate:Linear(4096, 128)。
这表明模型总共有 128个专家 (Experts)。
Router会根据输入动态选择Top-K个专家进行计算。
这种稀疏结构允许模型拥有巨大的总参数量(94层 x 128专家),但推理时的激活参数量(计算量)却保持在较低水平。 -
Norms: 使用了RMSNorm,且在Q/K上也加了Norm (
q_norm,k_norm),这是Qwen系列为了训练稳定性引入的特性。
第三部分:LM Head (输出层)
模块路径: lm_head
Linear(in_features=4096, out_features=151936, bias=False)
将LLM的输出投影回词表大小,计算下一个Token的概率。
数据流与维度变换总结 (Pipeline)
假设输入一段视频(或图片)和一段文本提示:
- 视觉输入:
- 形状:
(Batch, 3, T, H, W)
- 形状:
- 视觉编码 (Vision Encoder):
- 经过Conv3d Patch Embed →\to→
(Batch, Num_Patches, 1152) - 经过27层Transformer处理,特征维度保持
1152。
- 经过Conv3d Patch Embed →\to→
- 视觉-语言对齐 (Merger):
- Concat/Pooling: 将相邻的 2×22\times22×2 个Patch拼接 →\to→ 维度变成
1152 * 4 = 4608。同时Patch数量减少为原来的 1/41/41/4。 - Projection: 线性层将
4608映射为4096。 - 输出视觉Token形状:
(Batch, Reduced_Patches, 4096)。
- Concat/Pooling: 将相邻的 2×22\times22×2 个Patch拼接 →\to→ 维度变成
- 文本输入:
- Tokenize →\to→ Embedding →\to→
(Batch, Seq_Len, 4096)。
- Tokenize →\to→ Embedding →\to→
- 拼接 (Concatenation):
- 将视觉Token和文本Token在序列长度维度拼接。
- MoE LLM推理:
- 输入维度
4096。 - 经过94层处理,每一层通过Attention聚合信息,并通过Router选择特定的专家处理特征。
- 输入维度
- 输出:
- 生成回答。
疑问:Merger 中的维度突变(空间换通道)
merger 的输入为什么突然变成了 4608?它是怎么从 1152 变成 4608 的?
这里发生了一个物理空间上的合并操作(Patch Merging),这是视觉模型连接 LLM 的关键步骤。
1. 视觉编码器的输出状态
假设视觉编码器(ViT)输出的特征图是一个 H×WH \times WH×W 的网格,每个格子的特征深度是 1152。
可以把它想象成一堆 1×11 \times 11×1 的乐高积木块,每个块里存了 1152 个数字。
2. Patch Merging(2x2 合并)
为了减少 LLM 需要处理的 Token 数量(因为 LLM 处理长序列很慢且显存昂贵),模型将相邻的 2x2 个小方块拼成一个大方块。
- 操作:拿出相邻的 4 个 Token(左上、右上、左下、右下)。
- 拼接(Concatenation):这 4 个 Token 不做加法,而是首尾相连拼起来。
- 计算:
1152 (左上)+1152 (右上)+1152 (左下)+1152 (右下)=4608 1152 \text{ (左上)} + 1152 \text{ (右上)} + 1152 \text{ (左下)} + 1152 \text{ (右下)} = \mathbf{4608} 1152 (左上)+1152 (右上)+1152 (左下)+1152 (右下)=4608
形象图解:
[ 1152 ] [ 1152 ] 变成一个大 Token
[ 1152 ] [ 1152 ] ==> [ 1152 | 1152 | 1152 | 1152 ]
↓
总长度 = 4608
结果:
- Token 的数量变成了原来的 1/4(序列变短了,LLM 跑得更快)。
- Token 的维度变成了原来的 4倍(由 1152 变成了 4608)。
3. Merger 层的内部处理
现在输入已经是 4608 维了,Merger 层的代码逻辑如下:
linear_fc1(4608 →\to→ 4608):- 输入就是刚才拼出来的 4608 维大向量。这一层主要是对拼接后的特征进行融合,让这 4 个位置的信息不仅仅是简单的排列,而是产生交互。
act_fn(GELU):- 非线性激活。
linear_fc2(4608 →\to→ 4096):- 最关键的一步:语言模型(LLM)的“接口宽度”(Hidden Size)是 4096。
- 视觉特征现在的宽度是 4608。
- 这一层就是一个翻译/投影层,把视觉的 4608 维特征压缩并映射到 LLM 能理解的 4096 维空间。
为什么视觉编码器使用MHA,LLM使用GQA
视觉编码器只运行一次,没有KV Cache压力,追求极致的特征质量,所以用 MHA。
语言模型需要逐字生成,KV Cache是显存杀手,追求推理速度和长窗口,所以用 GQA。
关于MLA和GQA的选择
深度补偿:
这个模型有 94 层。
即使每一层因为 GQA 丢失了一点点“检索精度”,但通过 94 次的反复检索和修正,模型完全有能力弥补这些细微的损失。
MoE 容量补偿:
这是一个 MoE 模型,拥有 128 个专家。
虽然注意力机制稍微模糊了一点,但 MLP 部分的容量是巨大的。模型可以将更多的知识存储在 MLP 的参数中,减少对长上下文精确检索的过度依赖。
注意力冗余论:
大量研究表明,Transformer 的注意力机制存在巨大的冗余。很多时候,64 个头里的很多头都在关注相似的东西。
将 KV 头压缩到 4 个,迫使模型学习更紧凑、更通用的特征表示,甚至可能有正则化的效果。在实践中,Qwen2.5-72B 证明了即使是激进的 GQA,在长文本大海捞针测试中依然能达到 100% 准确率。
其实我觉得更重要的是生态原因,因为MLA的生态还不成熟,作为开源模型还是使用GQA较为稳妥。
数据是怎么经过这94层的?
数据经过这 94 层的方式是严格的串行,相当于一个输入要经过94层运算,才能得到第一个输出token
为什么要经过这么多层的计算
层数 = 推理的步数。
每一层都是在上一层处理结果的基础上,再进行一次加工。层数越多,模型能进行的逻辑变换就越复杂,能处理的因果链条就越长。
同时也可以从数学角度非线性变换去理解这个问题,不断地线性激活去区分复杂数据关系。


903

被折叠的 条评论
为什么被折叠?



