Qwen3-VL MoE 模型结构解析:DeepStack 多阶段视觉特征融合架构详解

发布时间:2026/9/12 11:58:47
Qwen3-VL MoE 模型结构解析:DeepStack 多阶段视觉特征融合架构详解 Qwen3-VL MoE 模型结构解析DeepStack 多阶段视觉特征融合架构详解【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm本文以《开源大模型食用指南》中 models/Qwen3-VL/01-Qwen3-VL-MoE-模型结构解析-Blog.md 为骨架深入解析 Qwen3-VL MoE 家族的两大核心设计文本主干从 Dense 全连接升级为专家混合MoE架构以及视觉接入从最后一层特征一次注入升级为 DeepStack 多阶段特征抽取与逐层融合。读完本文你将能够从源码级别理解 Qwen3-VL MoE 的视觉编码器如何产出image_embeds与deepstack_image_embeds两路特征、文本解码器如何通过残差连接逐层注入视觉特征并掌握与 Qwen2-VL 的架构差异为后续的模型部署见 02-Qwen3-VL-4B-Instruct FastApi 部署调用与 LoRA 微调见 05-Qwen3-VL-4B-Instruct Lora 可视化微调案例 - LaTexOCR打下架构认知基础。Qwen3 VL MoE 与 Qwen2 VL 的架构对比从整体结构上看Qwen3 VL MoE 相对 Qwen2 VL 做了两个方向的重大改动这也是理解整篇文档的起点。文本主干从 Dense 全连接到 MoE 专家混合Qwen2 VL的文本部分是典型的 Dense全连接结构每一个 Transformer 层的 MLP 都是完整的稠密前馈网络所有参数对每个 token 都会参与计算。Qwen3 VL MoE的文本部分采用了专家混合Mixture of Experts, MoE架构默认共有60 个专家experts每次前向只激活其中部分专家例如top-k4并且还可以通过配置控制哪些层使用 MoE、哪些层保持 Dense。这种大参数量 稀疏激活的设计是当前主流 MoE 大模型控制推理成本、提升单位算力下模型能力的通用思路模型容量更大但单次前向的计算量只与激活的专家数相关。值得注意的是Qwen3 VL MoE 的 MoE 实现与同系列文本模型 Qwen3 的 MoE 高度类似可参考仓库中 models/Qwen3/01-Qwen3-模型结构解析-Blog.md 进行对照学习下文也会结合该文档的源码给出快速回顾。视觉接入从一次注入到 DeepStack 多阶段融合Qwen2 VL的做法是视觉编码器只提取最后一层的视觉特征然后在文本输入层一次性把全部视觉信息注入到文本序列中。Qwen3 VL MoE的做法视觉提取部分采用多阶段特征抽取DeepStack 思想在视觉编码器的多个指定层分别抽取特征并各自经过独立的 merger 层处理然后在文本解码器的不同层级逐步注入对应的视觉特征。如果说 Qwen2 VL 是一口吃成胖子——把所有图像信息堆在序列最前端交给模型消化那么 Qwen3 VL MoE 就是细水长流——把不同抽象层级的视觉特征在文本解码的不同深度按需融合。DeepStack 的思想源自论文DeepStack: Deeply Stacking Visual Tokens is Surprisingly SimplearXiv: 2406.04334其核心主张是与其让视觉 token 在序列前端排队等待与文本交互不如把视觉特征堆叠stacking并分布到文本解码的多个深度去注入从而缓解长视觉序列带来的早期注意力瓶颈。Qwen3 VL MoE 正是把这一思想落地到视觉-语言融合的具体工程实现。MoE 专家混合机制快速回顾结合 Qwen3 源码由于 Qwen3 VL MoE 的文本主干与 Qwen3 的 MoE 实现同源在深入 DeepStack 之前先回顾一下 models/Qwen3/01-Qwen3-模型结构解析-Blog.md 中梳理的 MoE 核心机制便于理解60 个专家、top-k4背后发生了什么。专家网络每个专家就是一个 MLP在 MoE 中原本的稠密 MLP 层被替换为一组专家。每个专家是独立的Qwen3MoeMLP实例内部是标准的三段式结构gate_proj / up_proj / down_proj 激活函数class Qwen3MoeMLP(nn.Module): def __init__(self, config, intermediate_sizeNone): super().__init__() self.config config self.hidden_size config.hidden_size self.intermediate_size intermediate_size if intermediate_size is not None else config.intermediate_size self.gate_proj nn.Linear(self.hidden_size, self.intermediate_size, biasFalse) self.up_proj nn.Linear(self.hidden_size, self.intermediate_size, biasFalse) self.down_proj nn.Linear(self.intermediate_size, self.hidden_size, biasFalse) self.act_fn ACT2FN[config.hidden_act] def forward(self, x): down_proj self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x)) return down_proj与 Qwen2 非 MoE 模型的 MLP 相比MoE 版专家网络的差异在于每个专家而不是整个模型拥有一组 gate_proj / up_proj / down_proj多个专家并行构成Qwen3MoeSparseMoeBlock来替换原始 MLP 层。门控路由与稀疏激活Qwen3MoeSparseMoeBlock通过一个线性门控层为每个 token 计算路由分数选出top-k个专家并对输出做加权求和self.gate nn.Linear(config.hidden_size, config.num_experts, biasFalse) # gate 的输出形状为 (batch_size, seq_len, num_experts) # 每个元素表示对应位置的输入应该分配给哪个专家的概率。 self.experts nn.ModuleList( [Qwen3MoeMLP(config, intermediate_sizeconfig.moe_intermediate_size) for _ in range(self.num_experts)] )前向计算时router_logits self.gate(hidden_states)得到各专家 logits经 softmax 归一化为权重再按expert_mask选中top-k个专家执行router_logits self.gate(hidden_states) # 计算选中的专家的 logits routing_weights F.softmax(router_logits, dim1, dtypetorch.float) # 归一化权重 # 遍历每个 expert而不是遍历 tokens # 因为 token 数量通常远大于 expert 数量按 expert 遍历可以增大并行性 for expert_idx in range(self.num_experts): expert_layer self.experts[expert_idx] idx, top_x torch.where(expert_mask[expert_idx]) # 返回当前 expert 被选中的 tokens 的索引和排名 current_state hidden_states[None, top_x].reshape(-1, hidden_dim) current_hidden_states expert_layer(current_state) * routing_weights[top_x, idx, None] final_hidden_states.index_add_(0, top_x, current_hidden_states.to(hidden_states.dtype))此外Qwen3 的Qwen3MoeSparseMoeBlock相对 Qwen2 MoE 还去除了共享专家机制Qwen2MoeSparseMoeBlock 中每个 token 除路由专家外还会经过一个共享专家final_hidden_states final_hidden_states shared_expert_output简化了结构、减少了冗余计算。理解这一背景后Qwen3 VL MoE 中默认 60 个专家、每次激活 top-k4的配置含义就非常清晰了它继承了 Qwen3 的稀疏专家路由设计并在此基础上叠加了 DeepStack 视觉融合。DeepStack 特征融合架构详解这是本篇的核心章节。Qwen3 VL MoE 将 DeepStack 思想落地为两个紧密衔接的阶段特征抽取在视觉编码器中多阶段产出视觉特征与特征注入在文本解码器中逐层融合视觉特征。下面分别结合代码逐行解读。第一阶段多阶段特征抽取首先在Qwen3VLMoeModel整个多模态模型外壳中通过get_image_features一次性拿到两路视觉特征# class Qwen3VLMoeModel def get_image_features(self, pixel_values: torch.FloatTensor, image_grid_thw: Optional[torch.LongTensor] None): pixel_values pixel_values.type(self.visual.dtype) # 获取 image_embedsdeepstack_image_embeds image_embeds, deepstack_image_embeds self.visual(pixel_values, grid_thwimage_grid_thw) split_sizes (image_grid_thw.prod(-1) // self.visual.spatial_merge_size**2).tolist() image_embeds torch.split(image_embeds, split_sizes) return image_embeds, deepstack_image_embeds这里有两个关键输出image_embeds视觉编码器最后一层经 merger 融合后的视觉 token 特征会被直接放入 input tokens即作为视觉 token 与文本 token 拼接供首层文本解码器使用deepstack_image_embeds视觉编码器在多个中间指定层抽取、并分别经过各自 merger 处理后的深度堆叠特征列表不会一次性注入而是在后续文本解码的不同层级逐步加入。split_sizes的作用是把展平的图像 patch 特征按每张图的 token 数量切分image_grid_thw记录各图的网格尺寸spatial_merge_size是 2×2 邻域合并的 patch 合并粒度从而支持 batch 内多张尺寸不一图片的解码。那么self.visual默认是一个Qwen3VLMoeVisionModel对象如果魔改模型可以替换成其他视觉编码器内部是如何同时产出这两路特征的其 forward 核心代码如下def forward(self, hidden_states: torch.Tensor, grid_thw: torch.Tensor, **kwargs) - torch.Tensor: ... # 定义 deepstack 特征列表 deepstack_feature_lists [] for layer_num, blk in enumerate(self.blocks): # 特征提取 hidden_states blk(hidden_states, cu_seqlenscu_seqlens, position_embeddingsposition_embeddings, **kwargs) # 只在特定的层收集 deepstack 特征 if layer_num in self.deepstack_visual_indexes: # 注意这里提取到的特征并不是直接用的而是每个特征又经过了一个不同的 merger 层 deepstack_feature self.deepstack_merger_listself.deepstack_visual_indexes.index(layer_num) # 记录 deepstack_feature deepstack_feature_lists.append(deepstack_feature) hidden_states self.merger(hidden_states) return hidden_states, deepstack_feature_lists逐点解读遍历全部视觉 Transformer 层for layer_num, blk in enumerate(self.blocks)依次执行每一层视觉 block得到该层的hidden_states只在指定层收集特征通过self.deepstack_visual_indexes一个层号索引集合判断当前层是否属于深堆叠抽取点。只有在这些特定层才会把该层的中间特征拿出来每个抽取点配一个独立的 mergerdeepstack_feature并非直接使用而是经过self.deepstack_merger_list中与该层一一对应的 merger 层处理每个抽取点一个独立 merger索引通过deepstack_visual_indexes.index(layer_num)对齐。merger 层负责将视觉特征投影到与文本隐藏状态一致的维度并完成必要的 reshape / 合并最后一层单独走全局 merger循环结束后最后一层的hidden_states经self.merger处理作为image_embeds返回返回两路特征(image_embeds, deepstack_feature_lists)其中deepstack_feature_lists的元素个数等于deepstack_visual_indexes的长度也就是后续文本解码器接收到的deepstack_visual_embeds列表长度。下图完整呈现了这一逐层抽取 → 独立 merger → 汇聚成深度堆叠特征列表的过程第二阶段逐层特征注入拿到deepstack_feature_lists之后特征注入发生在Qwen3VLMoeTextModelMoE 文本解码器的 forward 中。代码如下# class Qwen3VLMoeTextModel def forward(..., deepstack_visual_embeds) - Union[tuple, BaseModelOutputWithPast]: ... # decoder layers for layer_idx, decoder_layer in enumerate(self.layers): layer_outputs decoder_layer(...) hidden_states layer_outputs # 在前几层取决于 deepstack 大小的隐藏状态中添加 deepstack_feature_list 中的视觉特征 if deepstack_visual_embeds is not None and layer_idx in range(len(deepstack_visual_embeds)): hidden_states self._deepstack_process( hidden_states, visual_pos_masks, deepstack_visual_embeds[layer_idx], ) hidden_states self.norm(hidden_states) return BaseModelOutputWithPast( last_hidden_statehidden_states, past_key_valuespast_key_values, ) def _deepstack_process( self, hidden_states: torch.Tensor, visual_pos_masks: torch.Tensor, visual_embeds: torch.Tensor ): # 设备对齐确保所有张量在同一设备上 visual_pos_masks visual_pos_masks.to(hidden_states.device) visual_embeds visual_embeds.to(hidden_states.device, hidden_states.dtype) # 特征融合残差连接只在视觉token对应位置进行注入保持文本token的原有特征 local_this hidden_states[visual_pos_masks, :].clone() visual_embeds hidden_states[visual_pos_masks, :] local_this return hidden_states关键逻辑拆解逐层 Decode文本解码器正常逐层执行decoder_layer(...)得到每一层的hidden_states前 n 层注入注入条件是layer_idx in range(len(deepstack_visual_embeds))——即只有前 n 层n 等于 deepstack 特征个数会做特征注入第 n 层之后不再注入视觉特征在解码早期逐步喂入模型。这与 Qwen2 VL在输入层一次性注入形成鲜明对比残差连接融合_deepstack_process中hidden_states[visual_pos_masks, :].clone() visual_embeds把第layer_idx个 deepstack 视觉特征以加法残差方式叠加到对应位置。注意visual_pos_masks只标记了视觉 token 所在的位置因此视觉 token 的特征被增强原特征 该层对应的视觉特征文本 token 的隐藏状态完全不受影响保持原有特征融合结果写回hidden_states[visual_pos_masks, :]供后续层继续解码设备与精度对齐注入前先.to(hidden_states.device)/.to(hidden_states.device, hidden_states.dtype)保证在混合精度训练或并行环境下张量设备、dtype 一致避免隐性拷贝与精度不匹配问题。从源码结构可以看出Qwen3 VL MoE 的 DeepStack 融合有两条清晰的边界抽取侧由deepstack_visual_indexes决定从视觉编码器的哪些层取样注入侧由len(deepstack_visual_embeds)决定在文本解码器前多少层注入——两侧由深度堆叠特征列表这一中间产物精确对接形成对称的多阶段视觉-语言交互通道。DeepStack 设计带来的实际收益结合上述代码实现与架构对比可以归纳 DeepStack 特征融合相对 Qwen2 VL 的主要设计收益以下为基于源码结构的推断性总结多尺度特征利用视觉编码器不同深度对应不同抽象层级浅层偏纹理/边缘深层偏语义DeepStack 将多层级特征都送入文本解码器而非只保留最后一层信息利用率更高缓解长序列早期注意力瓶颈Qwen2 VL 将所有视觉 token 堆在序列前端一次性注入视觉-文本交互只能发生在后续的注意力计算中DeepStack 将视觉特征分散到解码器多个深度缩短了视觉信息抵达文本处理深度的路径降低深层视觉特征在前几层被稀释的风险位置精准、互不干扰通过visual_pos_masks只对视觉 token 做残差注入文本 token 特征保持纯净视觉增强与文本建模解耦工程实现简洁且高效。需要说明的是以上收益属于架构层面的合理解读具体的效果量化如各项 benchmark 分数不属于本文档范围读者可以结合官方模型卡片与仓库中的评测类文档如 04-Qwen3-VL-4B-Instruct-vLLM.md进一步验证。仓库中的配套学习路径部署与微调理解架构之后仓库提供了从部署到微调的完整配套教程可以在真实环境中验证 DeepStack 架构的实际表现FastAPI 部署02-Qwen3-VL-4B-Instruct FastApi 部署调用.md 配套代码在 01-Qwen3-VL-4B-Instruct FastApi 参考代码通过Qwen3VLForConditionalGeneration.from_pretrained(..., torch_dtypetorch.bfloat16, device_mapauto, trust_remote_codeTrue)与AutoProcessor加载模型支持图文对话与视频问答接口vLLM 高性能推理04-Qwen3-VL-4B-Instruct-vLLM.md 展示如何以 OpenAI 兼容接口提供 chat / 图像 / 视频推理能力LoRA 微调05-Qwen3-VL-4B-Instruct Lora 可视化微调案例 - LaTexOCR.md 配套代码在 05-Qwen3-VL-30B-A3B-Instruct Lora 可视化微调案例 - LaTexOCR其中train_qwen3_vl.py通过AutoConfigimportlib动态加载transformers.models.{model_type}.modeling_{model_type}中对应架构类Qwen3VLMoeForConditionalGeneration/Qwen3VLForConditionalGeneration因此同一套训练脚本可同时适配 30B-A3BMoE与 4BDense两种架构——这正是本文所讲MoE / Dense 双架构并存的直接体现。值得注意的是MoE 版 30B-A3B 与 Dense 版 4B 共享同一套 DeepStack 视觉融合逻辑区别主要在文本主干的稀疏专家路由。部署或微调 30B-A3B 时对显存要求更高仓库文档标注约需 124GB 以上显存、两张 H20 起步4B 版本仅需单张 24GB 显存即可完成实验读者可按资源情况选择。总结Qwen3-VL MoE 相比 Qwen2 VL 的架构演进可以概括为一个替换、一个升级文本主干替换为 MoE默认 60 个专家、每次激活 top-k4并可控制哪些层使用 MoE继承了 Qwen3 的稀疏专家路由与无共享专家设计实现大容量、低单次计算成本视觉接入升级为 DeepStack 多阶段融合视觉编码器在deepstack_visual_indexes指定的多个层抽取特征、经各自 merger 投影后形成deepstack_feature_lists文本解码器在其前 n 层通过_deepstack_process以残差方式、仅在视觉 token 位置逐层注入实现视觉特征从一次灌入到分层递进的转变。这两处设计共同构成了 Qwen3-VL MoE 家族如 Qwen3-VL-30B-A3B-Instruct 及其 Thinking 版本在长上下文、高分辨率图像与视频理解任务上的架构基础。掌握本文的源码级解析后再配合仓库中的部署与微调教程动手实践即可完成从看懂架构到跑通模型的完整学习闭环。【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考