解读Qwen1.5 MoE:稀疏大模型的高效智能
引言
3月28日,阿里正式宣布开源其首个MoE技术大模型——Qwen1.5-MoE-A2.7B。说实话,这个消息在当时引起了不少关注,因为它背后的逻辑很有意思:用更少的激活参数,去挑战更大参数规模模型的性能。

这款模型以现有的Qwen-1.8B模型为基础,激活参数仅为2.7亿,但在多项基准评估中表现出色,达到了7B级别模型的水平。更重要的是,相比传统的7B模型,它在训练成本和推理速度上都展示出了显著优势。接下来,我们就结合官方博客和开源代码,一步步拆解这个模型的设计细节与实现逻辑。
文章会从参数规模、模型结构、训练推理效率这几个核心维度展开,最后再深入代码层面,看看它的MoE机制究竟是如何工作的。
参数量
Qwen1.5-MoE-A2.7B
对比一下更直观:Qwen1.5-7B的Non-Embedding参数是65亿,而Qwen1.5-MoE-A2.7B仅有20亿,只有前者的三分之一。而结果呢?训练成本直接降低了75%,推理速度提升到了1.74倍。换句话说,花更少的钱,跑得更快,效果还差不多——这正是MoE架构的魅力所在。
官方评测也印证了这一点:
Qwen1.5-MoE-A2.7B在与最佳的7B模型相比,取得了非常接近的性能
模型结构
Qwen1.5-MoE在结构上做了一些很有意思的改进。通常,像Mixtral这样的模型会在每个Transformer块中安排8个专家,并使用top-2门控进行路由。但Qwen团队觉得这里面还有很大的优化空间,于是从三个方面下了功夫:
Finegrained Experts
这个概念早在DeepSeek-MoE和DBRX中就已经被验证过是有效的。从FFN层过渡到MoE层时,常见的做法是简单复制多次FFN来生成多个专家。但Finegrained Experts的思路不同:它希望在
不增加总体参数数量的前提下,生成更多的专家
初始化策略
初始化这一步非常关键。早期实验表明,如果从零开始训练MoE模型,不仅效率低,而且很难达到预期的最优性能。所以,Qwen团队选择了一条更务实的路:直接基于已有的Qwen-1.8B进行改造,把它变成Qwen1.5-MoE-A2.7B。与此同时,在初始化阶段引入一定程度的随机性,可以显著加快收敛速度。不过,这个随机性具体引入在哪个环节,目前在博客和代码中还没有明确体现,算是一个值得继续深挖的细节。
新的路由机制
现在MoE领域有一个明显的趋势:引入共享专家与路由专家相结合的机制。从宏观角度看,这其实是一种广义的路由方法——如果没有共享专家,就会退化为传统的MoE路由设置。Qwen1.5-MoE的做法是:整合了
4个总是被激活的共享专家
60个路由专家
总结一下这个模型的MoE配置:
- “类似”8×1.8B的Top2激活MoE:但这里并不是真的8个专家,而是采用了Finegrained Experts,
总共64个专家,每次激活8个“Finegrained Expert”
- Finegrained Experts:参照DeepSeek-MoE和DBRX的思路,将单个FFN拆分成几部分,最终形成64个专家,激活8个
- 初始化:基于Qwen-1.8B,并引入随机性来加速收敛
- 路由机制:4个共享专家(始终激活) + 60个路由专家(每次激活4个)
训练成本与推理效率
MoE模型与Dense模型在训练成本上的差异非常明显。虽然MoE模型的总参数规模通常更大,但得益于稀疏性,训练开销反而能显著降低。我们先看看几个关键参数:总参数量、激活参数量和Non-Embedding参数量。
- 尽管MoE模型的总参数量较大,但Non-Embedding激活参数量远小于7B模型
- 实际操作中,使用Qwen1.5-MoE-A2.7B相比Qwen1.5-7B,训练成本降低了75%
- 由于采用了特殊的初始化方法,模型不需要训练同样数量的token就能达到很好的效果,这进一步压低了训练成本
代码解读
Config配置解析
从配置文件中,我们可以窥见许多设计细节:
{
"architectures": [
"Qwen2MoeForCausalLM"
],
"attention_dropout": 0.0,
"bos_token_id": 151643,
"eos_token_id": 151643,
"hidden_act": "silu",
"hidden_size": 2048,
"initializer_range": 0.02,
"intermediate_size": 5632,
"max_position_embeddings": 8192,
"max_window_layers": 21,
"model_type": "qwen2_moe",
"num_attention_heads": 16,
"num_hidden_layers": 24,
"num_key_value_heads": 16,
"rms_norm_eps": 1e-06,
"rope_theta": 1000000.0,
"sliding_window": 32768,
"tie_word_embeddings": false,
"torch_dtype": "bfloat16",
"transformers_version": "4.39.0.dev0",
"use_cache": true,
"use_sliding_window": false,
"vocab_size": 151936,
"decoder_sparse_step": 1,
"moe_intermediate_size": 1408,
"shared_expert_intermediate_size": 5632,
"num_experts_per_tok": 4,
"num_experts": 60,
"norm_topk_prob": false,
"output_router_logits": false,
"router_aux_loss_coef": 0.001
}
- 激活函数:silu
- 上下文长度:8K
- rope_theta:100万
- tie_word_embeddings:未启用参数共享
- moe_intermediate_size与shared_expert_intermediate_size:对应了4个共享专家
- num_experts_per_tok与num_experts:对应60个专家选4个
- router_aux_loss_coef:专家负载均衡的辅助损失系数
模型主结构介绍
Qwen2MoeForCausalLM基于混合专家架构,采用稀疏激活策略,每个输入序列只激活少数专家。我们来拆解一下它的核心组件:
模型构造(__init__方法)
self.model:由多个Qwen2MoeDecoderLayer组成的解码器模型,每个解码层包含注意力层和前馈网络(可能是Qwen2MoeMLP或Qwen2MoeSparseMoeBlock)self.lm_head:一个线性层,将解码器输出映射为词汇表长度的logitsself.router_aux_loss_coef:用于辅助损失计算的系数,帮助平衡各专家之间的负载
前向传播(forward方法)
- 调用
self.model执行解码器前向传播 - 隐藏状态送入
self.lm_head生成logits - 如果提供了labels,则计算交叉熵损失
- 如果需输出路由器logits,则计算并返回路由每个token到不同专家的概率分布
这个模型的几个特色部分:
- :通过
Mixture of Experts(MoE)
Qwen2MoeSparseMoeBlock实现,根据路由决策将计算分配给不同专家 - :通过
共享专家
self.shared_expert定义了一个总被激活的全连接层 - :使用
稀疏激活
routing_weights和selected_experts实现,每个输入仅激活权重最高的几个专家 - :通过
路由器辅助损失
load_balancing_loss_func促进负载均衡,避免少数专家被过度使用 - :通过
参数初始化
_init_weights方法引入随机性,提高收敛速度
简而言之,Qwen2MoeForCausalLM通过混合专家架构、稀疏激活、共享与路由专家结合、辅助损失以及精巧的初始化策略,实现了参数利用率与计算效率的双重提升。
路由机制
接下来是核心代码部分——Qwen2MoeSparseMoeBlock模块,它是整个MoE机制的关键实现:
class Qwen2MoeSparseMoeBlock(nn.Module):
def __init__(self, config):
super().__init__()
self.num_experts = config.num_experts
self.top_k = config.num_experts_per_tok
self.norm_topk_prob = config.norm_topk_prob
self.gate = nn.Linear(config.hidden_size, config.num_experts, bias=False)
self.experts = nn.ModuleList(
[Qwen2MoeMLP(config, intermediate_size=config.moe_intermediate_size) for _ in range(self.num_experts)]
)
self.shared_expert = Qwen2MoeMLP(config, intermediate_size=config.shared_expert_intermediate_size)
self.shared_expert_gate = torch.nn.Linear(config.hidden_size, 1, bias=False)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
batch_size, sequence_length, hidden_dim = hidden_states.shape
hidden_states = hidden_states.view(-1, hidden_dim)
router_logits = self.gate(hidden_states)
routing_weights = F.softmax(router_logits, dim=1, dtype=torch.float)
routing_weights, selected_experts = torch.topk(routing_weights, self.top_k, dim=-1)
if self.norm_topk_prob:
routing_weights /= routing_weights.sum(dim=-1, keepdim=True)
routing_weights = routing_weights.to(hidden_states.dtype)
final_hidden_states = torch.zeros(
(batch_size * sequence_length, hidden_dim), dtype=hidden_states.dtype, device=hidden_states.device
)
expert_mask = torch.nn.functional.one_hot(selected_experts, num_classes=self.num_experts).permute(2, 1, 0)
for expert_idx in range(self.num_experts):
expert_layer = self.experts[expert_idx]
idx, top_x = torch.where(expert_mask[expert_idx])
if top_x.shape[0] == 0:
continue
top_x_list = top_x.tolist()
idx_list = idx.tolist()
current_state = hidden_states[None, top_x_list].reshape(-1, hidden_dim)
current_hidden_states = expert_layer(current_state) * routing_weights[top_x_list, idx_list, None]
final_hidden_states.index_add_(0, top_x, current_hidden_states.to(hidden_states.dtype))
shared_expert_output = self.shared_expert(hidden_states)
shared_expert_output = F.sigmoid(self.shared_expert_gate(hidden_states)) * shared_expert_output
final_hidden_states = final_hidden_states + shared_expert_output
final_hidden_states = final_hidden_states.reshape(batch_size, sequence_length, hidden_dim)
return final_hidden_states, router_logits
__init__方法
num_experts:专家总数top_k:每个token选择的专家数norm_topk_prob:是否对top-k权重归一化gate:线性层,生成每个专家的logitsexperts:专家网络列表,每个都是Qwen2MoeMLPshared_expert:始终激活的共享专家shared_expert_gate:控制共享专家在最终输出中的权重
forward方法
- 将隐藏状态reshape为[batch_size * sequence_length, hidden_dim]
- 通过
gate计算路由器logits,再经softmax获得路由权重 - 选取top_k权重最大的专家
- 若启用归一化,则按行归一化
- 创建零张量用于累积专家计算结果
- 对每个专家:检查是否有token被分配,若有则计算该专家的输出,并按路由权重缩放后累加
- 共享专家处理所有隐藏状态,经sigmoid门控缩放后加到最终结果上
- 将结果恢复为[batch_size, sequence_length, hidden_dim]并返回
整个流程清晰实现了MoE机制:将隐藏状态分配给各个专家和一个共享专家,汇总所有输出后再传递给下一层。这样既大幅提升了模型的表示能力,又保持了计算效率。
几个值得留意的点
- 路由机制是60选4,通过
self.gate线性层和top-k实现 - 虽然代码中只出现了一个shared_expert,但在配置中shared_expert的隐藏层大小已经设置为4倍于普通专家——所以实际上这4个共享专家被“打包”在了一起
总结
阿里首个MoE模型Qwen1.5-MoE-A2.7B,用不到三分之一的激活参数,达到了当前最优秀7B模型的水平。更重要的是,它在训练成本和推理速度上实现了非常可观的优化。对于正在探索平衡模型性能与计算成本的团队来说,这个方向确实值得深入研究。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名