LabHub

ブログ

Mixture of Experts(MoE)アーキテクチャ深掘り分析:Switch TransformerからMixtralまでの発展と効率的スケーリング戦略

한국어English日本語中文

Mixture of Experts Architecture Analysis

はじめに

大規模言語モデル(LLM)の性能を高める最も直観的な方法は、パラメータ数を増やすことである。しかし Dense モデルでパラメータ数を倍にすると、学習と推論に必要な演算量もほぼ倍に増える。Mixture of Experts(MoE) アーキテクチャは、この問題に対する優雅な解法を示す。モデルの総パラメータ数は大きく増やしつつ、各入力トークンに対しては全パラメータのうち一部だけを活性化して、演算コストを一定に保つのである。

このアイデアは 1991 年に Jacobs らが提案したもとの MoE 論文まで遡るが、ここ数年で急激な発展を遂げた。Google の Switch Transformer(2021)は単一エキスパートルーティングで MoE の複雑さを劇的に減らし、Mistral AI の Mixtral 8x7B(2024)は Top-2 ルーティングでオープンソース LLM の新しい基準を打ち立て、DeepSeek-MoE(2024)は細粒度のエキスパート分割でエキスパートの特化を最大化した。そして DeepSeek-V3(2024)は 671B パラメータのうち 37B だけを活性化する構造で、最高水準の効率を達成した。

本記事では、MoE アーキテクチャの基本原理から主要モデルの設計の違い、ルーティング機構の比較、学習/推論の最適化戦略、運用時の注意点、障害事例とプロダクションチェックリストまでを総合的に扱う。

MoE アーキテクチャの基本原理

ゲーティング機構

MoE レイヤーの中心は ゲーティングネットワーク(Gating Network) である。入力トークン x が与えられると、ゲーティングネットワークは各エキスパートに対する確率分布を出力する。

G(x)=softmax(Wgx)G(x) = \text{softmax}(W_g \cdot x)

ここで W_g は学習可能なゲーティング重み行列である。出力ベクトル G(x) の各要素は、そのエキスパートが選ばれる確率を表す。

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

class GatingNetwork(nn.Module):
    """MoE の基本ゲーティングネットワーク"""
    def __init__(self, d_model, num_experts, top_k=2):
        super().__init__()
        self.top_k = top_k
        self.gate = nn.Linear(d_model, num_experts, bias=False)

    def forward(self, x):
        # x: (batch_size, seq_len, d_model)
        logits = self.gate(x)  # (batch_size, seq_len, num_experts)

        # Top-k エキスパートの選択
        top_k_logits, top_k_indices = torch.topk(logits, self.top_k, dim=-1)
        top_k_gates = F.softmax(top_k_logits, dim=-1)

        return top_k_gates, top_k_indices

Sparse Activation

Dense モデルではすべてのパラメータが入力ごとに活性化されるが、Sparse MoE では Top-k のエキスパートだけが活性化される。N 個のエキスパートのうち k 個だけが演算に参加するので、モデル容量は N 倍に増やしながら演算量は k 倍程度に保てる。

MoE レイヤーの出力は、選ばれたエキスパートの加重和として計算される。

y=iTop-kG(x)iEi(x)y = \sum_{i \in \text{Top-k}} G(x)_i \cdot E_i(x)

ここで E_i(x) は i 番目のエキスパートネットワークの出力である。

class MoELayer(nn.Module):
    """基本的な Sparse MoE レイヤー"""
    def __init__(self, d_model, d_ff, num_experts, top_k=2):
        super().__init__()
        self.num_experts = num_experts
        self.top_k = top_k
        self.gate = GatingNetwork(d_model, num_experts, top_k)

        # 各エキスパートは独立した FFN
        self.experts = nn.ModuleList([
            nn.Sequential(
                nn.Linear(d_model, d_ff),
                nn.GELU(),
                nn.Linear(d_ff, d_model)
            )
            for _ in range(num_experts)
        ])

    def forward(self, x):
        B, T, D = x.shape
        gates, indices = self.gate(x)  # gates: (B,T,k), indices: (B,T,k)

        # 出力の初期化
        output = torch.zeros_like(x)

        # エキスパートごとにバッチ処理
        for i in range(self.num_experts):
            # i 番目のエキスパートに割り当てられたトークンのマスク
            expert_mask = (indices == i).any(dim=-1)  # (B, T)
            if not expert_mask.any():
                continue

            # 該当トークンの抽出とエキスパートの演算
            expert_input = x[expert_mask]
            expert_output = self.experts[i](expert_input)

            # ゲートの重みを適用
            gate_values = gates[indices == i]
            output[expert_mask] += gate_values.unsqueeze(-1) * expert_output

        return output

Capacity Factor

各エキスパートが処理できるトークン数の上限を Capacity Factor(CF) で制御する。理想的に均等分配された場合、各エキスパートは T / N 個のトークンを処理し、CF を掛けて実際の容量を決める。

Expert Capacity=CF×TN\text{Expert Capacity} = \text{CF} \times \frac{T}{N}

CF が 1.0 なら完全な均等分配を仮定することになり、実際には 1.25~1.5 を使う。CF が低すぎるとトークンがドロップ(overflow)され、高すぎるとメモリが無駄になる。

Switch Transformer の分析

単一エキスパートルーティングの革新

Google Brain の Switch Transformer(Fedus et al., 2021)は、それまでの MoE の常識を破った。従来は Top-2 以上のエキスパートを活性化しなければ安定した学習はできないと考えられていたが、Switch Transformer は Top-1(単一エキスパート) ルーティングでも優れた性能を達成できることを示した。

単一エキスパートルーティングの利点は三つある。

  1. ルーター演算の削減: 一つのエキスパートだけを選ぶのでゲーティング演算が単純になる
  2. エキスパート容量の効率化: 各トークンが一つのエキスパートにだけ割り当てられるので、同じ Capacity Factor でバッチサイズを倍にできる
  3. 通信コストの削減: 分散学習の際、トークンを一つのエキスパートのデバイスへ送るだけで済む
class SwitchRouter(nn.Module):
    """Switch Transformer のルーター (Top-1)"""
    def __init__(self, d_model, num_experts, capacity_factor=1.25):
        super().__init__()
        self.num_experts = num_experts
        self.capacity_factor = capacity_factor
        self.gate = nn.Linear(d_model, num_experts, bias=False)

    def forward(self, x):
        B, T, D = x.shape
        # ゲーティングのロジット
        logits = self.gate(x)  # (B, T, num_experts)
        probs = F.softmax(logits, dim=-1)

        # Top-1 の選択
        gate_values, expert_indices = probs.max(dim=-1)  # (B, T)

        # エキスパート容量の計算
        capacity = int(self.capacity_factor * T / self.num_experts)

        # 容量超過トークンのドロップ処理
        dispatch_mask = torch.zeros(B, T, self.num_experts, dtype=torch.bool)
        for i in range(self.num_experts):
            expert_mask = (expert_indices == i)
            # 容量を超えた分をドロップ
            positions = expert_mask.nonzero(as_tuple=True)
            if len(positions[1]) > capacity:
                drop_indices = positions[1][capacity:]
                expert_mask[positions[0][capacity:], drop_indices] = False
            dispatch_mask[:, :, i] = expert_mask

        return gate_values, expert_indices, dispatch_mask

ロードバランシング損失

Switch Transformer は、エキスパート間の負荷不均衡を防ぐために補助損失(Auxiliary Loss)を導入する。各エキスパートへ割り当てられたトークンの比率(f_i)と、そのエキスパートの平均ルーティング確率(P_i)の内積として定義される。

Lbalance=αNi=1NfiPiL_{\text{balance}} = \alpha \cdot N \sum_{i=1}^{N} f_i \cdot P_i
def load_balancing_loss(gates, expert_indices, num_experts, alpha=0.01):
    """Switch Transformer のロードバランシング補助損失"""
    B, T = expert_indices.shape

    # f_i: 各エキスパートへ割り当てられたトークンの比率
    f = torch.zeros(num_experts, device=gates.device)
    for i in range(num_experts):
        f[i] = (expert_indices == i).float().sum() / (B * T)

    # P_i: 各エキスパートの平均ルーティング確率
    probs = F.softmax(gates, dim=-1)  # 全エキスパートに対する確率
    P = probs.mean(dim=[0, 1])  # (num_experts,)

    # バランシング損失
    loss = alpha * num_experts * (f * P).sum()
    return loss

性能の結果

Switch Transformer は、T5-Base と同じ演算量で 7 倍速い事前学習速度 を達成した。1.6 兆(1.6T)パラメータまでスケールしながら学習の安定性を保っており、これは bfloat16 学習とルーター z-loss の導入によるものだった。

Mixtral 8x7B のアーキテクチャ

構造の設計

Mistral AI の Mixtral 8x7B(2024)は、オープンソース MoE モデルの代表例である。Mistral 7B と同じ Transformer アーキテクチャを基盤に、各レイヤーの FFN ブロックを 8 個のエキスパートへ置き換え、Top-2 ルーティングを適用した。

中心的な仕様は次のとおりである。

class MixtralMoELayer(nn.Module):
    """Mixtral 8x7B スタイルの MoE レイヤー"""
    def __init__(self, d_model=4096, d_ff=14336, num_experts=8, top_k=2):
        super().__init__()
        self.num_experts = num_experts
        self.top_k = top_k

        # ルーター: 線形ゲーティング
        self.gate = nn.Linear(d_model, num_experts, bias=False)

        # 8 個の独立した SwiGLU FFN エキスパート
        self.experts = nn.ModuleList([
            SwiGLUExpert(d_model, d_ff) for _ in range(num_experts)
        ])

    def forward(self, x):
        B, T, D = x.shape
        x_flat = x.view(-1, D)  # (B*T, D)

        # ルーティング確率の計算
        logits = self.gate(x_flat)  # (B*T, 8)
        weights, indices = torch.topk(logits, self.top_k, dim=-1)
        weights = F.softmax(weights, dim=-1)  # Top-k 内での正規化

        # エキスパート出力の加重合算
        output = torch.zeros_like(x_flat)
        for i in range(self.top_k):
            expert_idx = indices[:, i]  # (B*T,)
            gate_weight = weights[:, i]  # (B*T,)

            for j in range(self.num_experts):
                mask = (expert_idx == j)
                if mask.any():
                    expert_out = self.experts[j](x_flat[mask])
                    output[mask] += gate_weight[mask].unsqueeze(-1) * expert_out

        return output.view(B, T, D)


class SwiGLUExpert(nn.Module):
    """Mixtral で使われる SwiGLU ベースの FFN エキスパート"""
    def __init__(self, d_model, d_ff):
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff, bias=False)
        self.w2 = nn.Linear(d_ff, d_model, bias=False)
        self.w3 = nn.Linear(d_model, d_ff, bias=False)

    def forward(self, x):
        return self.w2(F.silu(self.w1(x)) * self.w3(x))

Top-2 ルーティングの利点

Mixtral が Top-2 を採用した理由は次のとおりである。

DeepSeek-MoE: 細粒度のエキスパート分割

中心的な戦略

DeepSeek-MoE(2024)は、二つの中心的な戦略でエキスパートの特化を最大化した。

戦略 1: 細粒度のエキスパート分割(Fine-grained Expert Segmentation)

従来の MoE が 16 個の大型エキスパートから Top-2 を選ぶのに対し、DeepSeek-MoE は同じ総パラメータを 64 個の小型エキスパートへ分割し Top-8 を選ぶ。各エキスパートの FFN 中間次元を 1/4 に減らして 4 倍多いエキスパートを置くことで、可能なエキスパートの組み合わせ数を劇的に増やす。

class DeepSeekMoELayer(nn.Module):
    """DeepSeek-MoE スタイルのレイヤー: 細粒度エキスパート + 共有エキスパート"""
    def __init__(
        self,
        d_model=2048,
        d_ff=10944,
        num_routed_experts=64,
        num_shared_experts=2,
        top_k=6,
    ):
        super().__init__()
        self.top_k = top_k

        # 細粒度のルーティングエキスパート (小さい FFN)
        expert_d_ff = d_ff // 4  # 従来比で 1/4 のサイズ
        self.routed_experts = nn.ModuleList([
            SwiGLUExpert(d_model, expert_d_ff)
            for _ in range(num_routed_experts)
        ])

        # 共有エキスパート (常に活性)
        self.shared_experts = nn.ModuleList([
            SwiGLUExpert(d_model, d_ff)
            for _ in range(num_shared_experts)
        ])

        # ルーター
        self.gate = nn.Linear(d_model, num_routed_experts, bias=False)

    def forward(self, x):
        B, T, D = x.shape
        x_flat = x.view(-1, D)

        # 共有エキスパートの出力 (常に活性)
        shared_out = sum(expert(x_flat) for expert in self.shared_experts)

        # ルーティングエキスパートの出力
        logits = self.gate(x_flat)
        weights, indices = torch.topk(logits, self.top_k, dim=-1)
        weights = F.softmax(weights, dim=-1)

        routed_out = torch.zeros_like(x_flat)
        for i in range(self.top_k):
            expert_idx = indices[:, i]
            gate_weight = weights[:, i]
            for j in range(len(self.routed_experts)):
                mask = (expert_idx == j)
                if mask.any():
                    out = self.routed_experts[j](x_flat[mask])
                    routed_out[mask] += gate_weight[mask].unsqueeze(-1) * out

        return (shared_out + routed_out).view(B, T, D)

戦略 2: 共有エキスパートの分離(Shared Expert Isolation)

一部のエキスパートを 共有エキスパート(Shared Experts) として指定し、すべてのトークンに対して常に活性化する。共有エキスパートが共通知識(一般的な言語パターン、構文構造など)を担い、ルーティングエキスパートは特化した知識に集中できるようにする。これによりルーティングエキスパート間の知識の重複が減り、専門化の度合いが高まる。

性能

DeepSeek-MoE 16B は、約 40% の演算量だけで Dense モデルである DeepSeek 7B と同等の性能を達成した。DeepSeek-MoE 2B は、1.5 倍多いエキスパートパラメータと演算量を使う GShard 2.9B と同等の性能を示した。

ルーティング機構の比較

Top-k ルーティング

最も一般的な方式で、ゲーティングネットワークの上位 k 個のエキスパートを選ぶ。

Expert Choice ルーティング

Google Research(2022)が提案した Expert Choice 方式では、トークンがエキスパートを選ぶのではなく、エキスパートが自分の処理するトークンを選択する。各エキスパートが Top-k のトークンを選ぶので、完全なロードバランシングが保証される。

class ExpertChoiceRouter(nn.Module):
    """Expert Choice ルーティング: エキスパートがトークンを選ぶ"""
    def __init__(self, d_model, num_experts, capacity_factor=1.0):
        super().__init__()
        self.num_experts = num_experts
        self.capacity_factor = capacity_factor
        self.gate = nn.Linear(d_model, num_experts, bias=False)

    def forward(self, x):
        B, T, D = x.shape
        x_flat = x.view(-1, D)  # (N, D) where N = B*T
        N = x_flat.shape[0]

        logits = self.gate(x_flat)  # (N, num_experts)
        scores = F.softmax(logits, dim=0)  # トークン次元で softmax

        # 各エキスパートが処理するトークン数
        k = int(self.capacity_factor * N / self.num_experts)

        # エキスパートごとの Top-k トークンの選択
        top_k_scores, top_k_indices = torch.topk(
            scores.t(), k, dim=-1
        )  # (num_experts, k)

        return top_k_scores, top_k_indices

Hash ルーティング

学習可能なゲーティングの代わりに ハッシュ関数 でトークンをエキスパートへ割り当てる方式である。ルーティングの演算がないのでオーバーヘッドはほぼゼロで、ルーティングが不安定になる問題も原理的に消える。ただし入力の特性に合った最適な割り当てはできないので、性能はやや低い。

MoE モデルの比較表

項目Switch TransformerMixtral 8x7BDeepSeek-MoE 16BDeepSeek-V3
発表時期2021.012024.012024.012024.12
総パラメータ1.6T (最大)46.7B16.4B671B
活性パラメータ可変12.9B2.8B37B
エキスパート数128864 ルーティング + 2 共有256 ルーティング + 1 共有
活性エキスパート1 (Top-1)2 (Top-2)6 (Top-6)8 (Top-8)
ルーティング方式Learned Top-1Learned Top-2Learned Top-kLearned Top-k
ロードバランシングAuxiliary Loss非公開Auxiliary LossAuxiliary-Loss-Free
共有エキスパートなしなしあり (2 個)あり (1 個)
基盤アーキテクチャT5 (Encoder-Decoder)Mistral 7B (Decoder)Decoder-onlyDecoder-only
主な成果7x の学習速度向上Llama 2 70B 比で 6x 速い推論40% の演算で Dense 7B 同等オープンソース最高性能

学習と推論の最適化

Expert Parallelism

MoE モデルの分散学習では Expert Parallelism(EP) が中心になる。各エキスパートを別々の GPU へ配置し、All-to-All 通信でトークンを該当エキスパートへ送る。

# DeepSpeed MoE の設定例
deepspeed_config = {
    "train_batch_size": 256,
    "fp16": {"enabled": True},
    "zero_optimization": {"stage": 2},
    "moe": {
        "enabled": True,
        "ep_size": 8,            # Expert Parallelism: 8 個の GPU へエキスパートを分散
        "num_experts": 64,
        "top_k": 2,
        "capacity_factor": 1.25,
        "min_capacity": 4,
        "use_residual": True,    # 残差 MoE
        "moe_param_group": True  # エキスパートのパラメータを別グループに
    }
}

一般には EP と Data Parallelism(DP)を組み合わせる。たとえば 64 個の GPU で EP=8, DP=8 と構成すると、8 個の GPU が一つのエキスパートグループを担い、8 個のレプリカがデータを並列処理する。

ロードバランシングの戦略

学習の安定性のためのロードバランシング手法を整理する。

1. Auxiliary Loss (Switch Transformer)

基本的な補助損失で、エキスパートへの割り当て比率とルーティング確率の積を最小化する。係数 alpha は 0.01~0.1 の範囲でチューニングする。

2. Router z-loss (ST-MoE)

ゲーティングロジットの大きさをペナルティ化して学習の安定性を高める。ルーターへ入る大きなロジット値を抑え、浮動小数点の丸め誤差を減らす。

Lz=1BTb,t(logi=1Negb,t,i)2L_z = \frac{1}{BT} \sum_{b,t} \left(\log \sum_{i=1}^{N} e^{g_{b,t,i}}\right)^2

3. Auxiliary-Loss-Free (DeepSeek-V3)

補助損失を完全に取り除き、各エキスパートへ学習可能なバイアス項を加えてルーティング確率を動的に調整する。補助損失がモデル性能に与える悪影響を原理的に取り除く。

推論の最適化

MoE モデルの推論で最大の課題は メモリ使用量 である。すべてのエキスパートを GPU メモリへ載せる必要があるため、実際の演算に参加しないエキスパートもメモリを占有する。

主な最適化手法は次のとおりである。

運用上の注意事項

メモリ管理

MoE モデルは活性パラメータに比べて総パラメータがはるかに大きいので、メモリ計画に注意が必要である。Mixtral 8x7B の場合、活性パラメータは 12.9B だが、全体の 46.7B を GPU へ載せなければならない。FP16 基準で約 93GB の GPU メモリが必要で、これは単一の A100 80GB では足りない。

Expert Collapse

Expert Collapse(エキスパート崩壊) は MoE 学習で最もよくある問題である。少数のエキスパートへ大半のトークンが集中し、残りのエキスパートは事実上使われなくなる現象だ。原因と対策は次のとおりである。

ルーティングの不安定性

学習の初期にルーティングの判断が急激に変わる Routing Instability が起きることがある。これを防ぐために次の手法を使う。

障害事例と解決策

事例 1: 推論時に特定エキスパートで OOM

症状: 特定のバッチで一つのエキスパートへトークンが過度に集中し、その GPU で OOM が発生する

原因: 入力データの分布が学習データと大きく異なるとき、ルーターが特定のエキスパートへ偏った判断を下す

解決策:

事例 2: 学習中の Loss 発散

症状: 学習開始から数千ステップ後に突然 Loss が発散する

原因: ルーターのロジット値が大きくなりすぎて softmax 出力が 0 または 1 に近づき、gradient が爆発する

解決策:

事例 3: Expert Parallelism の通信ボトルネック

症状: GPU 使用率が低く、All-to-All 通信に大半の時間を費やしている

原因: エキスパート数に対して GPU 数が多すぎ、通信のオーバーヘッドが演算時間を上回っている

解決策:

プロダクションチェックリスト

MoE モデルをプロダクションへ配備するときに確認すべき項目である。

メモリとインフラ

学習の安定性

推論の最適化

モニタリング

参考資料

おわりに

MoE アーキテクチャは、「より大きなモデルをより少ないコストで」という LLM スケーリングの中心的な課題に対する、最も実用的な解法である。Switch Transformer が単一エキスパートルーティングの可能性を開き、Mixtral がオープンソースのエコシステムで MoE の実用性を証明し、DeepSeek-MoE/V3 が細粒度のエキスパート戦略で効率の新しい基準を示した。

しかし MoE には依然として課題が残る。エキスパート崩壊とルーティングの不安定性は学習時の継続的な管理を必要とし、総パラメータに対する高いメモリ要求は配備コストを押し上げる。Expert Choice ルーティングや Auxiliary-Loss-Free なバランシングといった最新の手法がこうした問題を少しずつ解決しているが、完全な答えはまだ出ていない。

今後の MoE の発展の方向は次のように見込まれる。エキスパートの動的な生成と削除、マルチモーダルなエキスパートの特化、推論時の適応的なエキスパート数の調整、そしてハードウェアと密接に co-design された MoE アーキテクチャが登場するだろう。DeepSeek-V3 のようなモデルが示したとおり、MoE はすでに最前線の AI モデルの中心的な構成要素になっており、この流れは今後さらに加速するはずだ。

コメント

まだコメントはありません。

ログインするとコメントできます