moe-training-pipeline / docs /ARCHITECTURE_GUIDE.md
rndubs's picture
Add docs/ARCHITECTURE_GUIDE.md
6e10b32 verified
|
Raw
History Blame Contribute Delete
13.2 kB

Architecture Deep-Dive Guide

Table of Contents

  1. What is a Mixture of Experts (MoE)?
  2. How Experts Are Defined
  3. How the Router Works
  4. Two Architecture Options
  5. Key Design Decisions Explained
  6. VRAM and Compute Analysis

What is a Mixture of Experts (MoE)?

A standard Transformer has one FFN per layer that processes every token:

Token β†’ Attention β†’ FFN β†’ Output
                    ↑
              (always this one FFN)

An MoE Transformer has many FFNs (experts) per layer, but each token only uses a few:

Token β†’ Attention β†’ Router β†’ [Expert 3, Expert 17, Expert 28, Expert 31] β†’ Output
                     ↑                   ↑
              (learned selector)   (only top-K of N experts activated)

The magic: You get NΓ— more parameters (knowledge capacity) but only KΓ— the compute of a single expert. A 20B MoE model with 32 experts and top-4 routing has 20B total params but only uses ~3.6B per token β€” comparable compute to a 3.6B dense model, but with the knowledge of a 20B model.


How Experts Are Defined

You do NOT manually define what each expert "knows." Experts are:

  1. Structurally identical β€” each expert is a small SwiGLU FFN:
class Expert(nn.Module):
    def __init__(self, hidden_size=2880, intermediate_size=2880):
        self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
        self.up_proj   = nn.Linear(hidden_size, intermediate_size, bias=False)
        self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)

    def forward(self, x):
        # SwiGLU: gate Γ— value
        return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
  1. Randomly initialized β€” all experts start the same (with random weights)

  2. Automatically specialized during training β€” the router learns to send different token patterns to different experts. Through gradient flow, each expert adapts to the tokens it receives most frequently.

What Do Experts End Up Learning?

Research (DeepSeek-V3 Appendix C, OLMoE Section 5) shows experts naturally specialize in:

  • Syntactic roles: One expert handles Python function definitions, another handles imports
  • Semantic domains: Math expressions, natural language, code comments
  • Structural patterns: Opening brackets, closing statements, docstrings
  • Languages: In multilingual models, experts may specialize by programming language

You can visualize expert activation patterns after training to understand specialization, but you don't need to engineer it.


How the Router Works

The router is a simple learned linear layer:

class Router(nn.Module):
    def __init__(self, hidden_size, num_experts):
        # This single matrix IS the routing mechanism
        # Each row is an "expert embedding" β€” a learned representation
        # of what kind of tokens each expert should handle
        self.gate = nn.Linear(hidden_size, num_experts, bias=False)

    def forward(self, hidden_states):
        # Step 1: Score each expert for each token
        logits = self.gate(hidden_states)  # [num_tokens, num_experts]

        # Step 2: Convert to probabilities
        # GPT-OSS style:
        scores = torch.softmax(logits, dim=-1)
        # OR DeepSeek-V3 style:
        # scores = torch.sigmoid(logits)

        # Step 3: Select top-K experts per token
        topk_scores, topk_indices = torch.topk(scores, k=4)  # top-4

        # Step 4: Normalize gate weights (so they sum to 1)
        gate_weights = topk_scores / topk_scores.sum(dim=-1, keepdim=True)

        return gate_weights, topk_indices

Load Balancing

Without balancing, the router might send all tokens to the same 2-3 experts (rich-get-richer). This wastes the other experts and creates GPU compute imbalance.

Three approaches:

Method Used By How It Works
Auxiliary Loss GPT-OSS (coef=0.9), OLMoE (coef=0.01) Extra loss term penalizing uneven expert load
Bias-Based (aux-loss-free) DeepSeek-V3, Moonlight Per-expert bias added to routing scores; adjusted online
Z-Loss OLMoE, Qwen3 Penalizes large router logits (stabilizes training)

For your first training run, start with auxiliary loss (coef=0.01) + z-loss (coef=0.001). These are well-understood and supported in all frameworks. Aux-loss-free is better but requires custom implementation in Megatron-LM.


Two Architecture Options

Option A: GPT-OSS-20B Style (RECOMMENDED)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Token Embedding (vocab=201,088 β†’ dim=2,880)              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 0: Sliding Attention (window=128) + MoE FFN        β”‚
β”‚ Layer 1: Full Causal Attention + MoE FFN                 β”‚
β”‚ Layer 2: Sliding Attention + MoE FFN                     β”‚
β”‚ Layer 3: Full Causal Attention + MoE FFN                 β”‚
β”‚ ... (alternating pattern for 24 layers)                  β”‚
β”‚ Layer 23: Full Causal Attention + MoE FFN                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ RMS Norm β†’ LM Head (dim=2,880 β†’ vocab=201,088)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each MoE layer:
  Input β†’ RMSNorm β†’ GQA Attention β†’ Residual β†’
  RMSNorm β†’ Router β†’ Top-4 of 32 Experts β†’ Weighted Sum β†’ Residual

Key features:

  • Alternating sliding/full attention: Saves 50% attention memory. Sliding window (128 tokens) handles local patterns; full attention handles long-range dependencies.
  • GQA (64 query heads, 8 KV heads): Efficient attention with 8:1 head ratio.
  • 32 experts, top-4: Each token activates 4 of 32 experts.
  • SwiGLU with clamping (limit=7.0): Prevents activation explosions.

Option B: DeepSeek-V2-Lite Style

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Token Embedding (vocab=102,400 β†’ dim=2,048)              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 0: MLA Attention + Dense FFN (10,944 hidden)       β”‚ ← Dense!
β”‚ Layer 1: MLA Attention + MoE FFN (64 experts, top-6)     β”‚
β”‚ Layer 2: MLA Attention + MoE FFN                         β”‚
β”‚ ... (all MoE after layer 0)                              β”‚
β”‚ Layer 26: MLA Attention + MoE FFN                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ RMS Norm β†’ LM Head                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each MoE layer:
  Input β†’ RMSNorm β†’ MLA Attention β†’ Residual β†’
  RMSNorm β†’ [2 Shared Experts (always)] + [Router β†’ Top-6 of 64] β†’ Residual

Key features:

  • MLA (Multi-Head Latent Attention): Compresses KV cache from 4096 to 576 floats per token per layer (86% reduction). Enables much longer context at inference.
  • Shared + Routed experts: 2 experts always active (shared knowledge) + 6 of 64 routed (specialized). 8 total active per token.
  • Fine-grained experts: 64 small experts (1408 hidden dim) instead of fewer large ones. More combinatorial routing flexibility.
  • Layer 0 is dense: Early layers don't benefit from MoE routing.

Key Design Decisions Explained

1. How Many Experts?

Configuration Total Experts Active Sparsity Quality Compute
GPT-OSS-20B 32 4 8Γ— Good Low
DeepSeek-V2-Lite 64 6 ~11Γ— Good Low
GPT-OSS-120B 128 4 32Γ— Great Low
DeepSeek-V3 256 8 32Γ— SOTA Medium
Kimi K2 384 8 48Γ— SOTA Medium

Rule of thumb: More experts = more knowledge capacity = better quality at same compute. But more experts = more memory for weights and more complex routing.

For 4Γ—H100 inference: 32-64 experts is the sweet spot. All weights fit comfortably in BF16.

2. Tokenizer Choice

Option Vocab Size Pros Cons
o200k_harmony (GPT-OSS) 201,088 Best code tokenization, GPT-4o compatible Large embedding table (579M params)
DeepSeek tokenizer 102,400 Good code+multilingual Requires custom setup
Train your own (BPE) 32K-64K Optimized for your domain, smaller embeddings Training cost, may miss rare tokens

Recommendation: Use o200k_harmony (201K vocab) if you want GPT-OSS compatibility. Use a custom 64K vocab tokenizer if you want to minimize embedding params and optimize for your specific code/language distribution.

3. Context Length Strategy

Phase 1a: Pre-train at 4K context β†’ cheap, covers most code files
Phase 1c: Extend to 32K β†’ covers full files, multi-file context
Phase 1c: Extend to 131K with YaRN β†’ repo-level understanding

YaRN (Yet Another RoPE Extension) lets you extend context without retraining from scratch. GPT-OSS uses rope_type: "yarn" with factor: 32 (extends 4K β†’ 131K).

4. Training Precision

Precision Memory Speed Quality H100 Support
FP32 4 bytes/param 1Γ— Best βœ“
BF16 2 bytes/param ~2Γ— Excellent βœ“
FP8 1 byte/param ~4Γ— Very Good βœ“ (native)

Recommendation: Start with BF16. Switch to FP8 if you need faster training β€” H100 has native FP8 tensor cores. DeepSeek-V3 proved FP8 training works at scale.


VRAM and Compute Analysis

Training (16Γ—H100, 1.28TB total VRAM)

For GPT-OSS-20B style (21B params):

Model weights (BF16):           21B Γ— 2 bytes = 42GB
Optimizer states (AdamW):       21B Γ— 8 bytes = 168GB  (fp32 master + momentum + variance)
Gradients:                      21B Γ— 2 bytes = 42GB
Activations (per micro-batch):  ~5-10GB per GPU (depends on seq length)
Total without parallelism:      ~260GB

With parallelism (EP=4, PP=4):
  Each PP stage: 6 layers β†’ ~1/4 of model
  Each EP rank: 8 experts β†’ ~1/4 of MoE params
  Optimizer sharded (ZeRO-1): split across all 16 GPUs
  Per-GPU memory: ~20-30GB β†’ comfortable on 80GB H100

Inference (4Γ—H100, 320GB total VRAM)

Model weights (BF16):           42GB β†’ fits on single H100 (80GB)
With tensor parallelism (TP=4): 10.5GB per GPU

KV Cache at 4K context:         24 layers Γ— 2 Γ— 512 Γ— 4K Γ— 2B = 201MB
KV Cache at 131K context:       24 layers Γ— 2 Γ— 512 Γ— 131K Γ— 2B = 6.3GB

Total (131K context):           ~48GB on 4 GPUs = 12GB per GPU
Remaining VRAM:                 68GB per GPU β†’ large batch inference possible

With MXFP4 quantization:        ~13GB model β†’ fits on single 16GB GPU!

References

Resource Link
GPT-OSS-120B model openai/gpt-oss-120b
GPT-OSS-20B model openai/gpt-oss-20b
GPT-OSS paper arxiv:2508.10925
DeepSeek-V2 Lite deepseek-ai/DeepSeek-V2-Lite
DeepSeek-V2 paper arxiv:2405.04434
DeepSeek-V3 paper arxiv:2412.19437
DeepSeekMoE paper arxiv:2401.06066
Moonlight (Kimi MoE) moonshotai/Moonlight-16B-A3B-Instruct
Moonlight paper arxiv:2502.16982
OLMoE paper arxiv:2409.02060
Megatron-LM MoE paper arxiv:2603.07685
Megatron-LM repo github.com/NVIDIA/Megatron-LM
The Stack v2 bigcode/the-stack-v2
FineWeb-Edu HuggingFaceFW/fineweb-edu
OpenR1-Math open-r1/OpenR1-Math-220k