- Published on
Sparse Mixture of Experts(MoE)アーキテクチャ深層分析:設計原理からDeepSeek-V3・Qwen3まで
- Authors
- Name
- はじめに
- MoEの基本構造と数学的原理
- ルーティング戦略:Top-k、Expert Choice、Hash Routing
- ロードバランシングとAuxiliary Loss
- Switch TransformerからDeepSeek-V3までの進化
- 学習安定性とトラブルシューティング
- 推論最適化:Expert Parallelism、Offloading
- 運用チェックリスト
- 失敗事例と復旧
- 参考資料

はじめに
大規模言語モデル(LLM)のパラメータ数は指数関数的に増加しているが、すべてのパラメータを各トークンごとに活性化するDenseモデルは計算コストの壁に直面している。GPT-4レベルのDenseモデルを学習するには数万枚のGPUが数ヶ月間稼働する必要があり、推論コストもパラメータ数に比例して増加する。この根本的な非効率を解決するために条件付き計算(Conditional Computation)パラダイムが台頭し、その中心にSparse Mixture of Experts(MoE)アーキテクチャがある。
MoEの核心的なアイデアはシンプルである。数百のエキスパート(Expert)ネットワークを配置しつつ、各入力トークンに対して少数のエキスパートのみを活性化し、計算量を劇的に削減する。総パラメータ数がモデル容量(capacity)を決定し、活性パラメータ数が実際の計算コストを決定するため、高品質と低コストを同時に達成できる。Mixtral 8x7Bは総47Bパラメータからトークンあたり13Bのみを活性化し、DeepSeek-V3は671Bパラメータ中37Bのみを活性化する。
本記事では、MoEの数学的基礎からルーティング戦略、ロードバランシング、Switch TransformerからDeepSeek-V3およびQwen3-235B-A22Bまでのアーキテクチャの進化、そして学習と推論の実践的な最適化戦略を深く掘り下げる。各テーマをPyTorchコードとともに解説し、運用環境で発生する失敗事例と復旧手順も含める。
MoEの基本構造と数学的原理
Sparse Activationの数学的定義
MoEレイヤーはN個のエキスパートネットワークE_1, E_2, ..., E_Nとゲーティングネットワークgから構成される。入力トークンxに対するMoEレイヤーの出力yは以下のように定義される。
y = sum_{i=1}^{N} G(x)_i * E_i(x)
ここでG(x)はゲーティング関数で、入力xに対してN次元ベクトルを出力する。Dense MoEではすべてのG(x)_iが0でない値を持つが、Sparse MoEではTop-Kエキスパートのみを選択し、残りのゲーティング値を0にする。
G(x)_i = softmax(W_g * x + noise)_i (if i in TopK)
G(x)_i = 0 (otherwise)
ノイズ項は学習時の探索(exploration)を促進し、エキスパート活用の多様性を高める役割を果たす。このスパース性のおかげで、総パラメータ数はNに比例して増加するが、実際の計算量(FLOPs)はKに比例し、Denseモデルの N/K倍効率的である。
エキスパートネットワークの構造
各エキスパートは一般的にTransformerのFeed-Forward Network(FFN)を置き換える。Self-Attentionはすべてのトークンが共有し、FFN部分のみをエキスパートごとに分離するのが標準的な設計である。これはSelf-Attentionがトークン間の関係を捉えるグローバルな役割を果たし、FFNが個々のトークンの表現を変換するローカルな役割を果たすためである。
import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass
@dataclass
class MoEConfig:
d_model: int = 1024
d_ff: int = 4096
num_experts: int = 8
top_k: int = 2
dropout: float = 0.1
aux_loss_weight: float = 0.01
class SwiGLUExpert(nn.Module):
"""SwiGLU活性化を使用するエキスパートFFN。
LLaMA、Mistralなど最新モデルで標準として採用されている構造。"""
def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1):
super().__init__()
self.w_gate = nn.Linear(d_model, d_ff, bias=False)
self.w_up = nn.Linear(d_model, d_ff, bias=False)
self.w_down = nn.Linear(d_ff, d_model, bias=False)
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
gate = F.silu(self.w_gate(x))
up = self.w_up(x)
return self.w_down(self.dropout(gate * up))
class TopKGating(nn.Module):
"""Top-Kゲーティングネットワーク。
Noisy Top-K Gating(Shazeer et al., 2017)の実装。"""
def __init__(self, d_model: int, num_experts: int, top_k: int = 2):
super().__init__()
self.num_experts = num_experts
self.top_k = top_k
self.gate = nn.Linear(d_model, num_experts, bias=False)
self.noise_linear = nn.Linear(d_model, num_experts, bias=False)
def forward(self, x: torch.Tensor):
# x: (batch * seq_len, d_model)
logits = self.gate(x)
if self.training:
noise = F.softplus(self.noise_linear(x))
logits = logits + noise * torch.randn_like(logits)
probs = F.softmax(logits, dim=-1)
top_k_probs, top_k_indices = torch.topk(probs, self.top_k, dim=-1)
# 再正規化:選択されたエキスパートの重み合計が1になるように
top_k_probs = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)
return top_k_probs, top_k_indices, probs
上記コードのSwiGLUExpertは、LLaMA、Mistral、Qwenなど最新モデルが採用するSwiGLU活性化関数を使用するエキスパートである。従来のReLUやGELUと比較して学習効率が高いことが経験的に確認されている。TopKGatingはShazeer et al.(2017)が提案したNoisy Top-K Gatingを実装したもので、学習時にゲーティングロジットに学習可能なノイズを追加してエキスパートの探索を促進する。
Dense vs Sparse MoE定量比較
| 項目 | Dense 70B | Sparse MoE 8x7B (Top-2) | Sparse MoE 256x3B (Top-2) |
|---|---|---|---|
| 総パラメータ | 70B | 47B | 768B |
| 活性パラメータ | 70B | 13B | 6B |
| FLOPs/token | 140 TFLOPs | 26 TFLOPs | 12 TFLOPs |
| GPUメモリ(FP16) | 140 GB | 94 GB | 1.5 TB |
| 学習コスト比率 | 1.0x | 0.35x(FLOPs基準) | 0.17x(FLOPs基準) |
| 推論速度 | 基準 | 2〜3倍高速 | エキスパートロードがボトルネック |
注目すべき点はMoEモデルのメモリ要件である。活性パラメータは少ないが、全パラメータをメモリに保持する必要があるため、エキスパート数が非常に多い場合はDenseモデルよりメモリが大きくなることがある。これがExpert ParallelismとOffloading戦略が必要な根本的な理由である。
ルーティング戦略:Top-k、Expert Choice、Hash Routing
ルーティング(Routing)はMoEの核心であり、最も困難な設計問題である。どのエキスパートを活性化するかを決定する戦略によって、モデルの品質、学習安定性、推論効率が大きく変わる。
Top-K Routing
最も伝統的なルーティング方式で、ゲーティングネットワークが各エキスパートに対するスコアを計算し、上位K個を選択する。Shazeer et al.(2017)がTop-2を提案し、Switch Transformer(Fedus et al., 2022)がTop-1に簡略化して通信コストを半減させた。
Top-1の利点:各トークンが正確に1つのエキスパートのみを使用するため、分散環境でのAll-to-All通信量が最小化される。実装もシンプルである。
Top-1の欠点:単一エキスパートに依存するため表現力が制限され、ゲーティング決定が離散的(discrete)であるため、学習初期にエキスパート崩壊(expert collapse)のリスクが高い。
Top-2の折衷案:Mixtral 8x7Bおよび多くの最新モデルがTop-2を採用している。2つのエキスパートの出力を加重和するため表現力が豊かであり、1つのエキスパートが不安定になっても他方が補完する。
Expert Choice Routing
Zhou et al.(2022)が提案したExpert Choice Routingは視点を転換する。トークンがエキスパートを選択するのではなく、エキスパートが処理するトークンを選択する。各エキスパートが自身に最も適したトークンK個を選択するため、ロードバランシングが構造的に保証される。
class ExpertChoiceGating(nn.Module):
"""Expert Choice Routingの実装。
各エキスパートが処理するトークンを直接選択し、
ロードバランシングを構造的に保証する。"""
def __init__(
self,
d_model: int,
num_experts: int,
capacity_factor: float = 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: torch.Tensor):
# x: (num_tokens, d_model)
num_tokens = x.shape[0]
expert_capacity = int(
num_tokens * self.capacity_factor / self.num_experts
)
# ゲーティングスコア:(num_tokens, num_experts)
gate_logits = self.gate(x)
# エキスパート視点でのスコア計算:(num_experts, num_tokens)
gate_scores = F.softmax(gate_logits.T, dim=-1)
# 各エキスパートが上位capacity個のトークンを選択
top_k_scores, top_k_indices = torch.topk(
gate_scores, expert_capacity, dim=-1
) # (num_experts, capacity)
# ディスパッチマスクの生成
dispatch_mask = torch.zeros(
self.num_experts, num_tokens,
device=x.device, dtype=x.dtype,
)
dispatch_mask.scatter_(1, top_k_indices, top_k_scores)
return dispatch_mask, top_k_indices, top_k_scores
Expert Choiceの核心的な利点は、補助損失(auxiliary loss)なしでも完全なロードバランシングを達成することである。各エキスパートが同数のトークンを処理するよう強制されるため、エキスパート崩壊の問題が根本的に排除される。ただし、1つのトークンが複数のエキスパートに選択されたり、どのエキスパートにも選択されなかったりする非対称性が存在する。
Hash Routing
Roller et al.(2021)が提案したHash Routingは、学習可能なゲーティングを完全に排除し、ハッシュ関数でトークンをエキスパートに割り当てる。ゲーティングネットワークのパラメータと計算が不要になるため、推論時のオーバーヘッドが最小化される。しかし、固定された割り当てルールであるため入力の意味を反映できず、実践では学習可能なルーティングと比較して品質が低く、主流としては採用されていない。
ルーティング戦略の比較
| 戦略 | ロードバランシング | 表現力 | 通信コスト | 実装複雑度 | 代表モデル |
|---|---|---|---|---|---|
| Top-1 | Aux Loss必要 | 低 | 最小 | 低 | Switch Transformer |
| Top-2 | Aux Loss必要 | 中 | 中 | 中 | Mixtral 8x7B |
| Top-K (K=6,8) | Aux Loss必要 | 高 | 高 | 中 | DeepSeek-V3 (Top-8/256) |
| Expert Choice | 構造的に保証 | 高 | 中 | 高 | 研究用モデル |
| Hash Routing | 完全 | 低 | 最小 | 最小 | 研究用モデル |
ロードバランシングとAuxiliary Loss
MoE学習で最も深刻な問題はエキスパート崩壊(Expert Collapse)である。ゲーティングネットワークが少数のエキスパートにのみトークンを集中的に送り、残りのエキスパートが学習機会を失って事実上死んだパラメータになる現象である。これを防止するために様々なロードバランシング手法が開発された。
Auxiliary Loss(補助損失)
Switch Transformerが提案した標準的なアプローチである。エキスパート別のトークン配分が均等になるよう誘導する損失項をメイン言語モデリング損失に追加する。
def compute_load_balancing_loss(
gate_probs: torch.Tensor,
top_k_indices: torch.Tensor,
num_experts: int,
top_k: int,
) -> torch.Tensor:
"""Switch Transformerスタイルのロードバランシング補助損失の計算。
Args:
gate_probs: ゲーティング確率 (num_tokens, num_experts)
top_k_indices: 選択されたエキスパートインデックス (num_tokens, top_k)
num_experts: エキスパート数
top_k: 選択されるエキスパート数
Returns:
補助損失のスカラー値
"""
num_tokens = gate_probs.shape[0]
# f_i: エキスパートiに割り当てられたトークンの割合
expert_mask = F.one_hot(top_k_indices, num_experts).float()
# (num_tokens, top_k, num_experts) -> (num_tokens, num_experts)
expert_mask = expert_mask.sum(dim=1)
tokens_per_expert = expert_mask.sum(dim=0) # (num_experts,)
f = tokens_per_expert / (num_tokens * top_k)
# P_i: エキスパートiに対する平均ゲーティング確率
P = gate_probs.mean(dim=0) # (num_experts,)
# 補助損失: N * sum(f_i * P_i)
# 均等配分時に最小値を取る
aux_loss = num_experts * (f * P).sum()
return aux_loss
class MoELayerWithAuxLoss(nn.Module):
"""補助損失を含む完全なMoEレイヤーの実装。"""
def __init__(self, config: MoEConfig):
super().__init__()
self.config = config
self.experts = nn.ModuleList([
SwiGLUExpert(config.d_model, config.d_ff, config.dropout)
for _ in range(config.num_experts)
])
self.gating = TopKGating(
config.d_model, config.num_experts, config.top_k
)
self.aux_loss_weight = config.aux_loss_weight
def forward(self, x: torch.Tensor):
batch_size, seq_len, d_model = x.shape
x_flat = x.view(-1, d_model)
top_k_probs, top_k_indices, gate_probs = self.gating(x_flat)
# 補助損失の計算
aux_loss = self.aux_loss_weight * compute_load_balancing_loss(
gate_probs, top_k_indices,
self.config.num_experts, self.config.top_k,
)
# エキスパート出力の計算
output = torch.zeros_like(x_flat)
for k in range(self.config.top_k):
expert_indices = top_k_indices[:, k] # (num_tokens,)
expert_weights = top_k_probs[:, k] # (num_tokens,)
for i in range(self.config.num_experts):
mask = (expert_indices == i)
if mask.any():
expert_input = x_flat[mask]
expert_output = self.experts[i](expert_input)
output[mask] += expert_weights[mask].unsqueeze(-1) * expert_output
output = output.view(batch_size, seq_len, d_model)
return output, aux_loss
Auxiliary Lossの重みalphaは非常に敏感なハイパーパラメータである。Switch Transformerはalpha=0.01を推奨したが、モデル規模とエキスパート数に応じた調整が必要である。alphaが大きすぎると言語モデリングの品質が低下し、小さすぎるとロードバランシング効果が微弱である。ST-MoE(Zoph et al., 2022)はrouter z-lossを追加してゲーティングロジットの大きさ自体を制約する方法を提案した。
DeepSeekのAuxiliary-Loss-Free戦略
DeepSeek-V3(2024)は補助損失なしでロードバランシングを達成する革新的な方法を提案した。各エキスパートに学習不可能なバイアス項を追加し、学習中にトークンが過度に集中するエキスパートのバイアスを下げ、活用が不足しているエキスパートのバイアスを上げる動的調整メカニズムを使用する。このアプローチは補助損失がメイン学習目的関数に干渉する問題を完全に排除し、学習安定性と最終モデル品質の両方で利点を示した。
Switch TransformerからDeepSeek-V3までの進化
Switch Transformer(Fedus et al., 2022)
Switch TransformerはMoEルーティングをTop-1に簡略化した核心的な論文である。既存のTop-2ルーティングの通信コストを半減させつつ、適切なcapacity factorとauxiliary lossで学習安定性を確保した。1.6Tパラメータモデルを T5-XXL比で4倍高速に学習し、同等の品質を達成した。
核心的な設計決定:
- Top-1ルーティング:通信コストの最小化
- Capacity Factor:エキスパートバッファサイズを動的に調整してトークンドロップを防止
- Selective Precision:ゲーティングはFP32、エキスパート演算はBF16で混合し、安定性と効率性を同時に達成
GShard(Lepikhin et al., 2021)
Googleが提案したGShardは600BパラメータMoEモデルの分散学習パイプラインを確立した。Top-2ルーティングとGroup-levelバランシングを使用し、SPMD(Single Program Multiple Data)プログラミングモデルで数千のTPUで効率的に学習できるフレームワークを提示した。
Mixtral 8x7B(Jiang et al., 2024)
Mistral AIのMixtralはオープンソースMoEモデルの実用性を証明したマイルストーンである。8つのエキスパート中Top-2を選択する構造で、総47Bパラメータから活性13Bを使用する。LLaMA-2 70Bと同等またはそれ以上のベンチマーク性能を示しながら、推論FLOPsは70Bの1/3レベルである。
DeepSeek-V3(DeepSeek, 2024)
DeepSeek-V3はMoEアーキテクチャの複数の設計領域で革新を達成した。
- Fine-grained Expert Segmentation:256個の小規模エキスパートを配置しTop-8を選択する。エキスパート数を増やしサイズを縮小すると特化(specialization)が深まり、モデル品質が向上する。
- Shared Expert:1つの共有エキスパートがすべてのトークンを処理して共通知識を担当し、ルーティングされたエキスパートは特化知識を担当する。
- Auxiliary-Loss-Free Load Balancing:先述のバイアスベース動的バランシングで補助損失の副作用を排除した。
- Multi-Token Prediction(MTP):一度に複数トークンを予測する学習目標を追加してデータ効率を向上させた。
- FP8学習:671BパラメータモデルをH800 GPU 2048台でFP8精度で学習し、コストを劇的に削減した。
Qwen3-235B-A22B(Alibaba, 2025)
Qwen3-235B-A22Bは総235Bパラメータ中22Bのみを活性化するMoEモデルで、128のエキスパート中Top-8を選択する。既存のQwen2.5シリーズのDenseアーキテクチャをMoEに転換し、GPT-4oレベルの性能を約10分の1の推論コストで達成した。
MoEモデル比較
| モデル | 総パラメータ | 活性パラメータ | エキスパート数 | Top-K | 共有エキスパート | ルーティング戦略 |
|---|---|---|---|---|---|---|
| Switch Transformer | 1.6T | 約100B | 128 | 1 | なし | Learned Top-1 |
| GShard | 600B | 約20B | 2048 | 2 | なし | Learned Top-2 |
| Mixtral 8x7B | 47B | 13B | 8 | 2 | なし | Learned Top-2 |
| DeepSeek-V3 | 671B | 37B | 256+1 | 8 | 1つ | Bias-adjusted |
| Qwen3-235B-A22B | 235B | 22B | 128 | 8 | あり | Learned Top-K |
| DBRX | 132B | 36B | 16 | 4 | なし | Learned Top-4 |
学習安定性とトラブルシューティング
エキスパート崩壊(Expert Collapse)の診断
エキスパート崩壊はMoE学習で最も一般的で致命的な問題である。ゲーティングネットワークが特定のエキスパートにのみトークンを集中的に送ると、残りのエキスパートの勾配が0に収束して学習が停止し、この不均衡が自己強化(self-reinforcing)ループを形成して悪化する。
import logging
from collections import defaultdict
logger = logging.getLogger(__name__)
class ExpertUtilizationMonitor:
"""エキスパート活用モニタリングおよび崩壊検知ツール。
学習中の各エキスパートの活用率を追跡し、
崩壊の兆候を早期に検知する。
"""
def __init__(
self,
num_experts: int,
collapse_threshold: float = 0.01,
window_size: int = 100,
):
self.num_experts = num_experts
self.collapse_threshold = collapse_threshold
self.window_size = window_size
self.history: list[dict[int, float]] = []
def record(self, expert_counts: dict[int, int], total_tokens: int):
"""バッチごとのエキスパート活用を記録。"""
utilization = {
i: expert_counts.get(i, 0) / max(total_tokens, 1)
for i in range(self.num_experts)
}
self.history.append(utilization)
if len(self.history) > self.window_size:
self.history = self.history[-self.window_size:]
def detect_collapse(self) -> list[int]:
"""エキスパート崩壊の検知。活用率がthreshold以下のエキスパートを返す。"""
if len(self.history) < self.window_size // 2:
return []
collapsed = []
for expert_id in range(self.num_experts):
recent_util = [
h[expert_id] for h in self.history[-self.window_size:]
]
avg_util = sum(recent_util) / len(recent_util)
if avg_util < self.collapse_threshold:
collapsed.append(expert_id)
if collapsed:
logger.warning(
f"Expert collapse detected! "
f"Experts {collapsed} have utilization below "
f"{self.collapse_threshold:.2%}. "
f"Consider increasing aux_loss_weight or "
f"reinitializing collapsed experts."
)
return collapsed
def get_load_imbalance_ratio(self) -> float:
"""ロード不均衡比率の計算。
1.0は完全な均衡、値が大きいほど不均衡。"""
if not self.history:
return 0.0
latest = self.history[-1]
utils = list(latest.values())
max_util = max(utils) if utils else 0
min_util = min(utils) if utils else 0
avg_util = sum(utils) / len(utils) if utils else 0
if avg_util == 0:
return float("inf")
return max_util / avg_util
学習不安定の原因と対策
| 症状 | 原因 | 対策 |
|---|---|---|
| Loss spike(損失急騰) | ゲーティングロジットの爆発 | Router z-lossの追加、ゲーティングFP32維持 |
| エキスパート崩壊 | Aux loss不足、LR過大 | Aux loss重みの増加、LRウォームアップの延長 |
| エキスパート間の重複 | 初期化の類似性 | エキスパートの直交初期化、多様性正則化 |
| トークンドロップ | Capacity factor不足 | Capacity factorを1.25〜1.5に増加 |
| ゲーティングの振動 | 学習率過大 | ゲーティングLRをメインLRの0.1倍に分離 |
安定した学習のためのハイパーパラメータガイド
学習安定性のための核心的原則は以下の通りである。第一に、ゲーティングネットワークの演算は必ずFP32で実行する。BF16やFP16ではsoftmaxの数値不安定によりルーティングが振動する。第二に、学習率ウォームアップをDenseモデルより2〜3倍長く取る。ゲーティングが安定する前に高い学習率を適用するとエキスパート崩壊が発生する。第三に、バッチサイズを可能な限り大きく設定する。小さなバッチではゲーティングのトークン配分がノイズに敏感で不安定になる。
推論最適化:Expert Parallelism、Offloading
MoEモデルの推論はDenseモデルとは根本的に異なる課題を提起する。活性パラメータは少ないが全パラメータをアクセス可能な状態に維持する必要があるため、メモリ管理とエキスパート配置戦略が核心となる。
Expert Parallelism
Expert Parallelism(EP)はエキスパートを複数のGPUに分散配置する戦略である。N個のエキスパートをP個のGPUに分配すると、各GPUはN/P個のエキスパートのみを保持する。トークンが特定のエキスパートにルーティングされると、All-to-All通信で該当GPUにトークンを送信し、演算結果を元のGPUに返送する。
import torch
import torch.distributed as dist
from typing import Optional
class ExpertParallelRouter:
"""Expert Parallelismのためのトークンディスパッチ/収集の実装。
各GPUがエキスパートの一部を担当し、
All-to-All通信でトークンをルーティングする。
"""
def __init__(
self,
num_experts: int,
ep_group: Optional[dist.ProcessGroup] = None,
):
self.num_experts = num_experts
self.ep_group = ep_group
self.ep_size = dist.get_world_size(ep_group) if ep_group else 1
self.ep_rank = dist.get_rank(ep_group) if ep_group else 0
self.experts_per_rank = num_experts // self.ep_size
def dispatch(
self,
tokens: torch.Tensor,
expert_indices: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
"""トークンを担当GPUにディスパッチ。
Args:
tokens: (num_tokens, d_model) 入力トークン
expert_indices: (num_tokens,) 各トークンの対象エキスパートインデックス
Returns:
dispatched_tokens: このGPUが処理するトークン
recv_counts: 各GPUから受信したトークン数
"""
# 各GPUに送るトークン数を計算
send_counts = torch.zeros(
self.ep_size, dtype=torch.long, device=tokens.device
)
for rank in range(self.ep_size):
start_expert = rank * self.experts_per_rank
end_expert = start_expert + self.experts_per_rank
mask = (expert_indices >= start_expert) & (
expert_indices < end_expert
)
send_counts[rank] = mask.sum()
# All-to-Allで受信カウントを交換
recv_counts = torch.zeros_like(send_counts)
dist.all_to_all_single(
recv_counts, send_counts, group=self.ep_group
)
# トークンのソートとAll-to-All転送
sorted_indices = torch.argsort(expert_indices)
sorted_tokens = tokens[sorted_indices]
send_splits = send_counts.tolist()
recv_splits = recv_counts.tolist()
dispatched_tokens = torch.zeros(
int(recv_counts.sum()), tokens.shape[1],
dtype=tokens.dtype, device=tokens.device,
)
dist.all_to_all_single(
dispatched_tokens, sorted_tokens,
output_split_sizes=recv_splits,
input_split_sizes=send_splits,
group=self.ep_group,
)
return dispatched_tokens, recv_counts
Expert Offloading
GPUメモリが不足する場合、非活性エキスパートをCPUメモリやNVMe SSDに保持し、必要な時だけGPUにロードする戦略である。DeepSpeed-MoEやMixtralの推論最適化で核心的に活用されている。
Offloadingの核心はプリフェッチ(prefetching)である。現在のレイヤーのエキスパート演算と同時に、次のレイヤーで活性化されるエキスパートを非同期的にGPUにロードすれば、エキスパート交換の遅延を隠蔽できる。PCIe 4.0 x16基準で約32 GB/sの帯域幅が利用可能であり、1つのエキスパート(数百MB)を数ミリ秒以内に転送できる。
推論最適化戦略の比較
| 戦略 | GPUメモリ | 推論遅延 | スループット | 適合シナリオ |
|---|---|---|---|---|
| Full Model on GPU | 最大 | 最小 | 最大 | ハイエンドマルチGPUサーバ |
| Expert Parallelism | 分散 | 通信オーバーヘッド | 高 | マルチGPUクラスタ |
| CPU Offloading | 最小 | ローディング遅延 | 中 | 限定的なGPU環境 |
| NVMe Offloading | 最小 | 高ローディング遅延 | 低 | 単一GPU環境 |
| Speculative Expert Prefetch | 中 | 中 | 高 | バッチ推論サーバ |
運用チェックリスト
MoEモデルをプロダクションにデプロイする際に必ず確認すべき項目を整理する。
学習フェーズ
- ゲーティング精度の確認:ゲーティングネットワークのforward/backwardがFP32で実行されているか検証する。BF16ゲーティングは学習初期には正常に見えるが、数万ステップ後に不安定を引き起こす可能性がある。
- ロードバランシングメトリクスダッシュボードの構築:エキスパート別トークン割当量、最大/最小活用比率、補助損失値をリアルタイムでモニタリングする。
- チェックポイント戦略:エキスパート並列化環境ではチェックポイントがGPU別に分離保存される場合がある。モデル全体を1つに統合(consolidate)するスクリプトを事前に準備する。
- Capacity Factorチューニング:トークンドロップ率が1%以上の場合はcapacity factorを上げる。ドロップされたトークンはresidual connectionを通じてのみ伝達されるため品質が低下する。
- エキスパート崩壊アラートの設定:特定エキスパートの活用率が平均の10%以下に低下した場合にアラートを発生させ、必要に応じて該当エキスパートを再初期化する。
推論/デプロイメントフェーズ
- メモリプロファイリング:全パラメータのGPUメモリへの搭載可否を確認し、不可能な場合はEPまたはOffloading戦略を選択する。
- バッチサイズの最適化:MoE推論ではバッチサイズがエキスパート活用効率に直接影響する。小さなバッチでは一部のエキスパートのみが活性化され、GPU活用率が低下する。
- KV Cacheの管理:MoEモデルもAttentionレイヤーはDenseと同一であるためKV Cache管理が必要である。PagedAttention(vLLM)との組み合わせが効率的である。
- ルーティング一貫性テスト:同一入力に対して同一のエキスパートが選択されるか確認する。特にTensor ParallelismとExpert Parallelismを混合する場合、数値誤差でルーティングが変わる可能性がある。
- フォールバック戦略:特定エキスパートのロードに失敗した場合、次順位のエキスパートで代替するフォールバックロジックを実装する。
- A/Bテストパイプライン:DenseモデルとMoEモデルの品質同等性をサービング環境で検証する。
失敗事例と復旧
事例1:エキスパート崩壊による品質低下
症状:学習3万ステップ後に突然ベンチマークスコアが低下する。Loss自体は正常に減少するが、生成品質が低下する。
原因分析:モニタリングの結果、8つのエキスパート中2つが全トークンの60%以上を処理し、3つのエキスパートは活用率2%未満であった。補助損失の重み(alpha=0.001)が低すぎてバランシング効果が不足していた。
復旧手順:
- エキスパート崩壊直前のチェックポイント(2万ステップ)にロールバック
- 補助損失の重みを0.001から0.01に10倍増加
- 崩壊したエキスパートのパラメータを活性エキスパートのパラメータで再初期化
- ゲーティングネットワークの学習率をメイン学習率の0.1倍に分離設定
- 再学習後、エキスパート活用率が均等(平均12.5%基準で8〜17%の範囲)になるまでモニタリング
事例2:All-to-All通信のボトルネック
症状:Expert Parallelismで64個のGPUで学習する際、GPU活用率が40%に急減する。プロファイラでAll-to-All通信が全学習時間の45%を占めていることが確認される。
原因分析:ネットワークトポロジの分析結果、エキスパート配置がネットワーク構造を考慮しておらず、ノード間通信が過度に発生していた。同一ノード内GPU間通信(NVLink、900 GB/s)とノード間通信(InfiniBand、400 Gb/s)の帯域幅差が20倍以上であった。
復旧手順:
- Hierarchical All-to-Allに切り替え:ノード内通信とノード間通信を2段階に分離
- エキスパート配置をトポロジ認識(topology-aware)に再配置:頻繁に同時に活性化されるエキスパートを同じノードに配置
- 通信-演算のオーバーラップ:エキスパート演算と次のバッチのトークンディスパッチをパイプラインでオーバーラップ
事例3:推論時のエキスパートローディング遅延
症状:CPU OffloadingでMixtral 8x7Bを単一GPU(24GB)でサービングする際、最初のトークンまでの遅延(TTFT)が5秒を超える。
原因分析:各レイヤーで2つのエキスパートをCPUからGPUにロードするたびに100〜200msがかかり、32レイヤーを順次処理するため累積遅延が3.2〜6.4秒に達する。
復旧手順:
- エキスパートプリフェッチの実装:現在のレイヤー処理中に次のレイヤーのゲーティングスコアを事前計算し、必要なエキスパートを非同期ロード
- ホットエキスパートキャッシング:活性化頻度が高い上位2〜3個のエキスパートをGPUに常駐させる
- エキスパート重みの量子化:INT4量子化でエキスパートサイズを75%縮小し、転送時間を短縮
- PCIe帯域幅がボトルネックの場合、pinned memoryの使用でCPU-GPU転送を最適化
事例4:学習中のLoss Spike発生
症状:大規模MoEモデル(100B以上)の学習時に数千ステップごとに繰り返しlossが急騰する。各spike後に回復はするが、学習時間が無駄になる。
原因分析:ゲーティングネットワークのsoftmax入力ロジットが間欠的に非常に大きな値を取り、数値不安定を引き起こす。特にBF16学習時にはゲーティングロジットの範囲がFP32より狭く、overflowが発生しやすい。
復旧手順:
- Router z-lossを追加してゲーティングロジットの大きさを直接制約する。
def router_z_loss(gate_logits: torch.Tensor) -> torch.Tensor:
"""ST-MoEスタイルのRouter z-loss。
ゲーティングロジットの大きさを制約して数値安定性を向上させる。
Args:
gate_logits: (num_tokens, num_experts) ゲーティングロジット
Returns:
z_lossスカラー
"""
log_z = torch.logsumexp(gate_logits, dim=-1) # (num_tokens,)
z_loss = (log_z ** 2).mean()
return z_loss
- ゲーティング演算をFP32に強制して数値安定性を確保する。
- 勾配クリッピングをゲーティングネットワークに別途適用(max_norm=1.0)する。
- 学習率ウォームアップ期間を全学習の5〜10%まで拡張する。
参考資料
Fedus, W., Zoph, B., & Shazeer, N. (2022). Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity. JMLR, 23(120), 1-39. https://arxiv.org/abs/2101.03961
DeepSeek-AI. (2024). DeepSeek-V3 Technical Report. https://arxiv.org/abs/2401.06066
Cai, W. et al. (2024). A Survey on Mixture of Experts. https://arxiv.org/abs/2407.10671
FriendliAI. (2024). MoE Models Comparison: Architectures and Performance. https://friendli.ai/blog/moe-models-comparison
Zilliz. (2024). What is Mixture of Experts? A Complete Guide. https://zilliz.com/learn/what-is-mixture-of-experts
Wikipedia. Mixture of Experts. https://en.wikipedia.org/wiki/Mixture_of_experts
Shazeer, N. et al. (2017). Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. ICLR 2017. https://arxiv.org/abs/1701.06538
Jiang, A. Q. et al. (2024). Mixtral of Experts. https://arxiv.org/abs/2401.04088