大模型原理--混合专家模型

举报
剑指南天 发表于 2026/05/03 20:24:17 2026/05/03
【摘要】 混合专家模型(MoE,Mixture of Experts)核心思想是:使用多个并行的 FeedForward(专家)替代单一的 FeedForward 层,并通过 Router(路由器)根据输入 Token 选择其中少量最合适的专家参与计算。这样大幅增加了模型的总参数量,又不会等比例增加计算量

1.概述

混合专家模型(MoE,Mixture of Experts)核心思想是:使用多个并行的 FeedForward(专家)替代单一的 FeedForward 层,并通过 Router(路由器)根据输入 Token 选择其中少量最合适的专家参与计算。这样大幅增加了模型的总参数量,又不会等比例增加计算量。

2. MoE的工作流程

2.1 前向传播的具体步骤如下:

(1)路由得分计算

Router 接收 Token 的隐藏表示,并通过线性映射为所有专家生成一组得分。

(2)选择 Top-k 专家

从得分中选取得分最高的 k 个专家,其余专家在本次计算中不参与处理。

(3)计算路由权重

对被选中的专家得分执行 softmax,得到归一化权重,用于表示各专家在最终输出中的贡献比例。

(4)专家执行前向计算

被激活的专家分别对输入 Token 进行独立计算,生成各自的输出。

(5)加权合并输出

将所有激活专家的输出按照路由权重加权求和,得到 Token 在 MoE 层的最终表示

2.2 负载均衡辅助损失

负载均衡辅助损失(Load Balancing Auxiliary Loss) 是一个额外加到总训练损失上的惩罚项,它的作用是鼓励路由器将 token 均匀地分配给各个专家,避免出现某些专家被频繁使用而其他专家几乎闲置的“专家坍塌”问题。其数学表达如下:

下面是Switch Transformer 负载均衡辅助损失的数学原理:

已知柯西不等式:

因为路由概率Pi​和分配统计fi有很强的正相关,那么最小化Pi​fi就几乎等同于最小化Pi2(或∑fi2)。

于是把问题转成:在fi=1fi≥0的条件下,求∑fi2的最小值。

构造两组实数:a=(f1,f2,…,fN),b=(1,1,…,1)。根据柯西不等式:

等号成立当且仅当 (f1,f2,…,fN​) 和 (1,…,1)成比例,即所有fi相等,即每个fi

这就是为什么损失设计成这个样子,最小值为 1,而且这个最小值点与完全均衡的状态完美对应,这个完全均衡的状态自然就会成梯度方向。

2.3 专家并行

要在前向传播中引入 All-to-All 通信:根据路由结果,将 token 分发到对应专家的设备,计算后再收集回来。通常建议直接使用已有的 MoE 并行库(如 DeepSpeed-MoE、FairScale、Megablocks)。

2.4 代码实现

import torch
import torch.nn as nn
import torch.nn.functional as F


class Expert(nn.Module):
    """单个专家:前馈网络"""
    def __init__(self, d_model, d_ff):
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff)
        self.w2 = nn.Linear(d_ff, d_model)

    def forward(self, x):
        return self.w2(F.gelu(self.w1(x)))


class MoE_Layer_Efficient(nn.Module):
    """
    高效 MoE 前馈层(稀疏激活版本)
    Args:
        d_model: 输入/输出维度
        d_ff: 专家内部隐藏维度
        num_experts: 专家总数
        top_k:  token 激活的专家数
    """
    def __init__(self, d_model, d_ff, num_experts, top_k=2):
        super().__init__()
        assert top_k <= num_experts, f"top_k ({top_k}) 不能超过 num_experts ({num_experts})"
        self.num_experts = num_experts
        self.top_k = top_k

        # 路由器(无偏置)
        self.router = nn.Linear(d_model, num_experts, bias=False)
        # 专家模块
        self.experts = nn.ModuleList([Expert(d_model, d_ff) for _ in range(num_experts)])

    def forward(self, x, return_aux_loss=False):
        """
        x: [batch_size, seq_len, d_model]
        返回:
            out: 专家加权求和输出 [batch_size, seq_len, d_model]
            aux_loss: 负载均衡辅助损失 ( return_aux_loss=True,否则仅返回 out)
        """
        batch_size, seq_len, d_model = x.shape
        N = batch_size * seq_len                    # 展平后的 token         flat_x = x.reshape(N, d_model)              # [N, d_model]

        # 1. 路由得分
        router_logits = self.router(x)              # [batch, seq, num_experts]

        flat_logits = router_logits.reshape(N, self.num_experts)  # [N, E]
        # 2. TopK 选择
        top_k_logits, top_k_indices = torch.topk(flat_logits, self.top_k, dim=-1)  # [N, K]
        top_k_weights = F.softmax(top_k_logits, dim=-1)                            # [N, K]

        # 3. 构建稀疏权重矩阵(仅存储权重,不计算所有专家!)
        #    形状 [N, num_experts],大部分位置为 0
        sparse_weights = torch.zeros_like(flat_logits)           # [N, E]
        sparse_weights.scatter_(1, top_k_indices, top_k_weights)  #  TopK 权重填入对应位置

        # 4. 仅对每个专家计算被其选中的 token
        out = torch.zeros_like(flat_x)                          # [N, d_model]
        for expert_idx in range(self.num_experts):
            # 找出所有使用当前专家的 token(该列权重非零)
            col = sparse_weights[:, expert_idx]                 # [N]
            mask = col != 0                                     # 哪些 token 用了这个专家
            if not mask.any():
                continue
            token_indices = mask.nonzero(as_tuple=True)[0]      # 这些 token  flat_x 中的索引

            x_selected = flat_x[token_indices]                  # [M, d_model]
            expert_out = self.experts[expert_idx](x_selected)   # [M, d_model]
            weights = col[token_indices].unsqueeze(-1)          # [M, 1]
            # 将加权结果累加到对应 token 的输出中(index_add 保证同一专家多次出现时累积)
            out.index_add_(0, token_indices, expert_out * weights)

        out = out.reshape(batch_size, seq_len, d_model)         # 恢复形状

        if not return_aux_loss:
            return out

        # ---------- 计算负载均衡辅助损失 ----------
        # 路由概率(对所有 token  softmax        router_probs = F.softmax(flat_logits, dim=-1)           # [N, E]
        # 每个专家的平均路由概率 P_i
        P_i = router_probs.mean(dim=0)     # [E]

        # 每个专家被选中的 token 数量(权重非零即算选中)
        counts = (sparse_weights != 0).sum(dim=0).float()       # [E]
        # 分派比例 f_i (注意:所有 f_i 的和等于 top_k        f_i = counts / N
        # 标准辅助损失(Switch Transformer 形式)
        aux_loss = self.num_experts * (f_i * P_i).sum()
        return out, aux_loss

3. 能力与优势

MoE 结构通过引入稀疏激活机制,显著提升了模型的能力和效率,主要具备以下三个核心优势:

(1)高容量、低计算的效率结构

MoE 模型通过稀疏激活机制,使每次前向传播仅有少数个专家参与计算。这种设计在不增加实际计算量的前提下,大幅提升了模型的总参数量(模型容量),实现了高效的“高容量、低计算”结构。

(2)专家分工协作,提升泛化与适应性

通过路由器(Router)机制的动态分配,不同的输入 Token 会被导向最适合处理它们的专家。这种机制促使专家自动形成功能分化,每个专家专注于学习特定的模式或数据子集,从而显著增强了模型的表达能力和泛化性能。

(3)天然适合大规模分布式扩展

MoE 结构中的专家模块是相互独立的,这使得专家可以轻松地分布到大规模计算集群的不同设备上并行运行,极大地提升了模型的可扩展性(Scalability)和在大规模环境下的训练与推理效率。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。