MoEアーキテクチャ大解剖
「4GBで500Bを動かす」鍵となるMixture of Expertsを、メモリ割り当てからソースコードまで完全分解
目次
🔀 MoEとは何か — 疎活性化の革命
MoE(Mixture of Experts, 専門家の混合)とは、「モデル全体のごく一部のパラメータだけを、各トークンで活性化させる」アーキテクチャだ。従来のDenseモデルが全パラメータを毎回使うのに対し、MoEは入力ごとに最適な数個の「専門家(Expert)」だけを選んで走らせる。この「疎活性化(Sparse Activation)」こそが、MoEの核心だ。
- ・DeepSeek V3: 671B総パラメータ、アクティブ37B(5.5%のみ使用)
- ・Mixtral 8x7B: 実質47B総パラメータ、アクティブ13B(28%のみ使用)
- ・GPT-4(推定): 1.76T総パラメータ、アクティブ約280B(16%のみ使用)
💾 なぜMoEがメモリ問題を解くのか
UIAPduinoページで語った「500Bを4GBで」の実現に直結する重要セクションだ。
| 項目 | Dense 500B | MoE 500B (Top-2 / 8 experts) |
|---|---|---|
| 総パラメータ | 500B | 500B |
| 1トークンあたりの計算量 | 500B × 2 FLOPS | 125B × 2 FLOPS(1/4) |
| メモリ常駐量(最小) | 500B 全部 | Routing層 + Top-K Expert分のみ |
| 理論上のSSDオフロード可能性 | 困難(全部同時に使う) | 容易(未使用Expertは捨てられる) |
| Q2量子化後のVRAM最小要求 | 125GB | 30〜40GB → さらにストリーミング可 |
Denseモデルでは全重みを毎トークンで使うため、SSDオフロードは推論速度が壊滅的に遅くなる。MoEは「このトークンではこのExpertしか使わない」と事前に分かるため、使わないExpertは物理メモリから外して良い。これがSSDストリーミング推論と相性が良い本質的理由だ。
🧠 MoEの5つの部品
① Router (Gate)
各トークンをどのExpertに送るかを決める軽量な分類器。入力次元 × Expert数の1層線形層。Mixtralで約32KB、ほぼ無視できるサイズ。
② Experts (FFN群)
各Expertは通常のTransformerのFFN(2層MLP)。8〜256個を保持。ここがモデルサイズの大部分。
③ Top-K Selection
Routerの出力から上位K個(Mixtral=2、DeepSeek V3=8)のExpertを選択。torch.topk 一発。
④ Weighted Aggregation
選ばれたExpertの出力を、Routerのsoftmax重みで加重平均。
⑤ Load Balancing Loss
学習時のみ使う補助損失。Expertが偏って使われるのを防ぐ「死にExpert」対策。
📐 Dense vs MoE の構造比較
Dense Transformer Block (Python)
class DenseBlock(nn.Module):
def __init__(self, dim, ffn_dim):
super().__init__()
self.attn = Attention(dim)
self.ffn = FeedForward(dim, ffn_dim) # 全トークン共通
def forward(self, x):
x = x + self.attn(x)
x = x + self.ffn(x) # 毎回全パラメータ使用
return xMoE Transformer Block (Python)
class MoEBlock(nn.Module):
def __init__(self, dim, ffn_dim, num_experts=8, top_k=2):
super().__init__()
self.attn = Attention(dim)
self.moe = MixtureOfExperts(
dim, ffn_dim,
num_experts=num_experts,
top_k=top_k
)
def forward(self, x):
x = x + self.attn(x)
x = x + self.moe(x) # Top-K個のExpertだけ使用
return x🔍 ソースコード解剖:Mixtral 8x7B
Mixtralは、Mistral AI が公開した最もシンプルで参照しやすいMoEの公式実装だ。公式リポジトリ mistralai/mistral-src は約1500行。以下はその実装の骨格を、初学者向けに整理したもの。
import torch
import torch.nn as nn
import torch.nn.functional as F
class MixtralMoE(nn.Module):
def __init__(self, hidden_dim=4096, ffn_dim=14336,
num_experts=8, top_k=2):
super().__init__()
self.num_experts = num_experts
self.top_k = top_k
# ① Router: 軽量な1層線形層(4096→8 で32KBのみ)
self.gate = nn.Linear(hidden_dim, num_experts, bias=False)
# ② Experts: 独立したFFNを8個保持(ここがメモリの大部分)
self.experts = nn.ModuleList([
FeedForward(hidden_dim, ffn_dim)
for _ in range(num_experts)
])
def forward(self, x):
# x: [batch, seq_len, hidden_dim]
batch, seq, dim = x.shape
x_flat = x.view(-1, dim) # [batch*seq, dim]
# ③ Router: 各トークンの「どのExpertに行くか」のスコア計算
router_logits = self.gate(x_flat) # [batch*seq, num_experts]
# ④ Top-K選択: 上位2個のExpertだけ残す
routing_weights, selected = torch.topk(
router_logits, self.top_k, dim=-1
)
routing_weights = F.softmax(routing_weights, dim=-1)
# ⑤ 選ばれたExpertだけ実行
output = torch.zeros_like(x_flat)
for expert_id in range(self.num_experts):
# このExpertが選ばれたトークンのインデックス
mask = (selected == expert_id).any(dim=-1)
if not mask.any():
continue # このExpertは使われない → メモリから外せる
tokens = x_flat[mask]
expert_out = self.experts[expert_id](tokens)
# ⑥ Routerの重みで加重
weight = routing_weights[mask].sum(dim=-1, keepdim=True)
output[mask] += weight * expert_out
return output.view(batch, seq, dim) - ・① Routerは軽量。Mixtralで4096×8=32KBのパラメータのみ
- ・② Expertsが本体。8個×約180MB = 約1.4GB/層(FP16)
- ・③ 各トークンに対し「どのExpertが得意か」のlogitを算出
- ・④
torch.topkでスパース化。Top-2なら2/8=25%のExpertだけ使う - ・⑤ ここが省メモリの核。
if not mask.any(): continueでそのExpertを丸ごとスキップできる。SSDからロードしていない状態でも動く - ・⑥ Routerのsoftmax確率で出力を加重平均
🧮 Routerの数学とLoad Balance
Routerが雑に訓練されると「特定のExpertばかりに仕事が集中する」問題が起きる(死にExpert)。これを防ぐのがLoad Balancing Lossだ。MoEモデルの学習では、通常のCross Entropy Lossに加えてこの補助損失を混ぜる。
def load_balance_loss(router_probs, selected_experts, num_experts):
"""
router_probs: [batch*seq, num_experts] Softmax後のRouter確率
selected_experts: [batch*seq, top_k] 選ばれたExpert ID
"""
# f_i: Expert iが選ばれた割合(one-hotの平均)
mask = F.one_hot(selected_experts, num_experts).float()
f_i = mask.mean(dim=(0, 1)) # [num_experts]
# P_i: Expert iへのルーティング確率の平均
P_i = router_probs.mean(dim=0) # [num_experts]
# f_i と P_i の両方が均等なほど損失が小さくなる
return num_experts * (f_i * P_i).sum() この補助損失により、Routerは「全Expertを平均的に使うように」と逆圧力を受け、Expert間の専門性が自然に分化していく。Mixtralでは router_aux_loss_coef=0.02 程度の重みで混ぜるのが標準。
💾 メモリ割り当ての実際
前ページで掲げた「500Bを4GBで動かす」という挑戦が、MoEによって現実的になる。以下はMixtral 8x7B を例にした、設定別のメモリ要求量。
| 設定 | メモリ使用量 | 備考 |
|---|---|---|
| 全Expert常駐 FP16 | 約90GB | 普通のMixtralデプロイ、GPU 2〜4台要る |
| 全Expert常駐 Q4 | 約24GB | llama.cpp Q4_K_M デフォルト |
| 全Expert常駐 Q2 | 約12GB | アグレッシブ量子化、精度は若干低下 |
| Top-K Expertのみロード Q2 | 約3.5GB | SSDストリーミング推論 |
| + KV Cache圧縮 (GQA/MLA) | 約3.8GB | 4GB制約内に収まる |
// エキスパートキャッシュ(LRU方式)の擬似コード
struct ExpertCache {
std::unordered_map<int, ExpertWeights*> hot;
std::list<int> lru;
size_t max_hot_experts = 4; // 4GB制約内で保持できる数
ExpertWeights* get(int expert_id) {
if (hot.count(expert_id)) {
// ヒット: LRU更新
lru.remove(expert_id);
lru.push_front(expert_id);
return hot[expert_id];
}
// ミス: SSDからmmapでロード
if (hot.size() >= max_hot_experts) {
int victim = lru.back();
lru.pop_back();
munmap(hot[victim]->data, hot[victim]->size);
hot.erase(victim);
}
auto* w = load_from_ssd_mmap(expert_id);
hot[expert_id] = w;
lru.push_front(expert_id);
return w;
}
};
現実のPCIe 5.0 NVMeは14GB/s の帯域を持つ。Expert 1個(約180MB FP16 / 45MB Q2)のロード時間は3〜13ms。トークン生成が100ms間隔なら、ミス時のペナルティは許容できる。ホットExpertが十分キャッシュされれば、ほとんどのトークンでSSDアクセスは発生しない。
📊 実モデル比較:Mixtral / DeepSeek V3 / Qwen MoE
| モデル | 総パラメータ | Expert数 | Top-K | アクティブ | 特徴 |
|---|---|---|---|---|---|
| Mixtral 8x7B | 47B | 8 | 2 | 13B | 最もシンプル、公式ソース公開 |
| Mixtral 8x22B | 141B | 8 | 2 | 39B | 大型版、GPT-4 Turbo級 |
| DeepSeek V2 | 236B | 160 + 2共有 | 6 | 21B | 共有Expert導入、MLA採用 |
| DeepSeek V3 | 671B | 256 + 1共有 | 8 | 37B | MTP、FP8学習、現最強級 |
| Qwen1.5-MoE-A2.7B | 14B | 60 | 4 | 2.7B | 小型MoE、Expertが多い |
| OLMoE 7B-1B | 7B | 64 | 8 | 1B | AllenAI完全オープン |
| Grok-1 | 314B | 8 | 2 | 86B | xAI、巨大版Mixtral型 |
DeepSeek V3の「Expert 256個中Top-8」は特に興味深い。細分化が進むほどRouterの選択自由度が上がり、Expert間の専門性分化が明確になる。SSDストリーミング視点では、Expertが細かいほど「必要なものだけロード」の粒度が細かくなり有利。
🐧 最初に読むべきLLM — LLM界のUbuntu
「LinuxでいうUbuntuのように、最初に触るのに単純でいい」——この問いへの答えは明確だ。LLMのソースコードを初めて読むなら、Andrej Karpathy の nanoGPT 一択。Ubuntu と同じく、入門者が迷わないよう設計された「デフォルトの選択肢」だ。
結論:最初は nanoGPT(300行、1ファイル、PyTorchのみ)
全体を1日で読める。Transformer の要素が全て入っている。MoE は入っていないが、MoEを理解するにはまずDense Transformerを読む必要がある。
📚 候補比較表
| LLM | 規模 | 行数目安 | 言語 | 読みやすさ | 何が学べるか | Linux例え |
|---|---|---|---|---|---|---|
| nanoGPT (Karpathy) | GPT-2 124M | 300行 | Python | ★★★★★ | Transformer基礎、1ファイルで完結 | Ubuntu Desktop |
| llama2.c (Karpathy) | Llama 2 7B | 700行 | C | ★★★★★ | 純C言語での推論、メモリ管理 | Raspberry Pi OS |
| minGPT (Karpathy) | GPT系 | 800行 | Python | ★★★★ | nanoGPTの教育的拡張版 | Ubuntu LTS |
| Mistral-src 公式 | Mistral 7B / Mixtral | 1,500行 | Python | ★★★★ | MoE公式リファレンス | Debian |
| OLMoE (AllenAI) | 7B MoE | 中規模 | Python | ★★★★ | 学習コードまで完全オープン | Fedora |
| llama.cpp | 全般 | 10万行超 | C/C++ | ★★★ | 量子化、mmap、プロダクション推論 | Arch Linux |
| Transformers (HF) | 全般 | 100万行超 | Python | ★★ | 全モデル網羅、抽象度高い | Red Hat Enterprise |
| DeepSeek V3公式 | 671B MoE | 数万行 | Python | ★★ | 最先端MoE、MLA、MTP | Gentoo |
| Megatron-LM (NVIDIA) | 学習向け | 膨大 | Python | ★ | テンソル並列、分散学習 | LFS |
💡 Claudeのおすすめ順序
Step 1: nanoGPT(1週間)
まずこれ。train.py と model.py の2ファイルだけで完結する。TransformerのAttention・FFN・位置埋め込みを、100行以内の関数として目で追える。
Step 2: llama2.c(2〜3日)
C言語で推論のみ。malloc でメモリがどう確保され、行列演算がどう走るかを肉眼で見る。「メモリ割り当てから理解したい」派には神ファイル。
Step 3: Mistral-src(1週間)
公式の最小MoE実装。nanoGPT と比べて MoE の追加要素だけ差分で理解できる。ここで Router・Top-K・Expert を完全に把握する。
Step 4: OLMoE or DeepSeek V3 リファレンス(1ヶ月〜)
本気の現代MoE。共有Expert、細粒度ルーティング、MLA、補助損失なしのLB(DeepSeek独自)まで踏み込む。
🗺️ 学習ロードマップ
Phase 1: Transformer 完全理解(2週間)
nanoGPTを写経→学習→推論。Attentionの数式を手書きで追う。"The Annotated Transformer" を併読。
Phase 2: C言語で推論を実装(1週間)
llama2.cを1行ずつ読み、自分で別モデルを動かす。mmap(2) の man を読む。
Phase 3: MoE 構造の把握(2週間)
Mistral-src を読解。Mixtralのweightをダウンロードし、自作Routerで推論を再現。
Phase 4: 4GB推論エンジン自作(3ヶ月〜)
UIAPduinoページのPhase 1と合流。llama.cpp を fork して、Expert キャッシュを実装。
🎯 まとめ:MoEは「計算量を減らすのではなく、メモリを減らす」技術
MoEの本質は、単に「計算量が4分の1になる」ことではない。それ以上に重要なのは、「メモリに何を常駐させるかを選べる」という性質だ。Denseモデルでは全重みが常時必要だが、MoEでは「今このトークンで必要なExpertだけ」を選別できる。この性質が、SSDストリーミング推論・エッジデバイスでの大規模モデル運用を可能にする。
「4GBのメモリで500Bのモデルを動かす」という挑戦は、MoEなしには成立しない。MoEは、個人がLLM基盤を持つための鍵だ。