Starphix
Agentic Engineering / Sources
The Lab / Sources / Agentic Engineering / ModelArchitectures/Mixture-of-Experts.md

Mixture of Experts (MoE)

Architecture, routing, training, and inference optimization for sparse expert models.


MoE Fundamentals

Architecture

Input → Router → Top-k Experts → Combine → Output
         │
         ├── Expert 1 (FFN)
         ├── Expert 2 (FFN)
         ├── ...
         └── Expert N (FFN)

Key Equations

# Router logits
router_logits = x @ W_router^T

# Top-k selection
top_k_indices, top_k_weights = top_k(router_logits, k)

# Expert outputs
expert_outputs = [expert_i(x) for i in top_k_indices]

# Weighted combination
output = Σ(top_k_weights[i] * expert_outputs[i])

Sparsity Benefits

Aspect Dense MoE (k=2, N=8)
Params (Total) 7B 47B (13B active)
FLOPs/Token 100% ~25-30%
Training Cost 1x ~1.5-2x (comm)
Inference Cost 1x ~30-40%
Quality Baseline Better at scale

Routing Mechanisms

Top-k Gating

class TopKRouter:
    def __init__(self, d_model: int, num_experts: int, k: int = 2):
        self.router = nn.Linear(d_model, num_experts, bias=False)
        self.k = k

    def forward(self, x):
        logits = self.router(x)  # [batch, seq, num_experts]
        weights, indices = torch.topk(logits, self.k, dim=-1)
        weights = F.softmax(weights, dim=-1)
        return weights, indices

Load Balancing (Critical)

# Auxiliary loss: encourage uniform expert usage
def load_balancing_loss(router_logits, top_k_indices):
    # Fraction of tokens routed to each expert
    expert_mask = F.one_hot(top_k_indices, num_classes=num_experts).float()
    tokens_per_expert = expert_mask.mean(dim=[0, 1])

    # Router probabilities
    router_probs = F.softmax(router_logits, dim=-1).mean(dim=[0, 1])

    # Loss: minimize coefficient of variation
    return num_experts * (tokens_per_expert * router_probs).sum()

# Z-loss (logit normalization)
def z_loss(router_logits):
    return torch.logsumexp(router_logits, dim=-1).mean() ** 2

Router Variants

Variant Description Pros
Top-k Fixed k experts Simple, predictable
Top-p Probability threshold Adaptive compute
Expert Choice Experts choose tokens Perfect load balance
Soft MoE Soft assignment Differentiable
Hash Routing Deterministic hash No router params

Expert Architectures

Standard (FFN Experts)

class Expert(nn.Module):
    def __init__(self, d_model: int, d_ff: int):
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff, bias=False)
        self.w2 = nn.Linear(d_ff, d_model, bias=False)
        self.gate = nn.Linear(d_model, d_ff, bias=False)  # SwiGLU

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

Shared + Specialized

class SharedExpertMoE(nn.Module):
    def __init__(self, d_model, num_experts, shared_experts=1):
        self.shared = nn.ModuleList([Expert(d_model, d_ff) for _ in range(shared_experts)])
        self.routed = nn.ModuleList([Expert(d_model, d_ff) for _ in range(num_experts)])
        self.router = Router(d_model, num_experts)

    def forward(self, x):
        # Shared always active
        shared_out = sum(e(x) for e in self.shared)
        # Routed top-k
        routed_out = self.moe_forward(x)
        return shared_out + routed_out

DeepSeek MoE (Fine-Grained)

# DeepSeek-V2/V3: 160-256 experts, 6-8 active
# Key innovations:
# 1. Fine-grained experts (smaller d_ff)
# 2. Shared experts (always active, capture common knowledge)
# 3. Auxiliary-loss-free load balancing (device-level)
# 4. MLA (Multi-head Latent Attention) for KV compression

class DeepSeekMoE(nn.Module):
    def __init__(self, config):
        self.num_experts = 160  # Fine-grained
        self.num_shared = 2     # Always active
        self.k = 6              # Top-6 routed
        # ... implementation

Training MoE

Parallelism Strategy

# Expert Parallelism (EP)
# Each expert on different device
# Data parallel within expert

# Tensor Parallelism (TP) within expert
# Sequence Parallelism (SP)

# Pipeline Parallelism (PP) across layers

# 3D Parallelism: DP × TP × EP
# DeepSeek-V3: 2048 GPUs, 3D parallelism

Training Stability

# Gradient clipping per expert
def clip_expert_gradients(model, max_norm=1.0):
    for expert in model.experts:
        nn.utils.clip_grad_norm_(expert.parameters(), max_norm)

# Router gradient scaling
router_grad_scale = 1.0 / math.sqrt(num_experts)

Communication Optimization

# All-to-all for token dispatch
# Overlap computation with communication
async def moe_forward(x):
    # 1. Compute router logits
    logits = router(x)

    # 2. Dispatch tokens (async all-to-all)
    dispatch_future = async_all_to_all(x, indices)

    # 3. Compute shared experts while waiting
    shared_out = shared_experts(x)

    # 4. Wait for routed tokens
    routed_x = await dispatch_future

    # 5. Compute routed experts
    routed_out = routed_experts(routed_x)

    # 6. Combine
    return shared_out + combine(routed_out, weights)

Inference Optimization

Expert Caching

class ExpertCache:
    def __init__(self, max_experts_in_memory=8):
        self.cache = {}  # expert_id → (weights, device)
        self.max_size = max_experts_in_memory
        self.access_order = []

    def get(self, expert_id):
        if expert_id in self.cache:
            self.access_order.remove(expert_id)
            self.access_order.append(expert_id)
            return self.cache[expert_id]

        # Load from disk
        weights = load_expert(expert_id)
        self.evict_if_needed()
        self.cache[expert_id] = weights
        self.access_order.append(expert_id)
        return weights

Speculative Expert Activation

# Predict which experts needed next
class SpeculativeRouter:
    def __init__(self, router, predictor):
        self.router = router
        self.predictor = predictor  # Small model predicting next experts

    def forward(self, x, prev_experts=None):
        # Use predictor to pre-load experts
        if prev_experts:
            predicted = self.predictor(prev_experts)
            preload_experts(predicted)

        return self.router(x)

Quantization

# Expert-specific quantization
def quantize_moe(model):
    for i, expert in enumerate(model.experts):
        # High-traffic experts: higher precision
        if expert.traffic > threshold:
            quantize(expert, bits=8)  # INT8
        else:
            quantize(expert, bits=4)  # INT4

    # Router: keep FP16/BF16
    quantize(model.router, bits=16)

MoE vs Dense: When to Use

Scenario Recommendation
Training from scratch, >10B params MoE (compute-efficient)
Fine-tuning dense model Keep dense
Latency-critical inference Dense or small MoE
Throughput-critical, batch inference MoE (better utilization)
Limited GPU memory MoE (offload experts)
Multilingual MoE (language-specific experts)
Code + Natural language MoE (specialized experts)

Notable MoE Models

Model Total Params Active Params Experts Active Key Features
Mixtral 8x7B 47B 13B 8 2 Open, strong
Mixtral 8x22B 141B 39B 8 2 Larger
DeepSeek-V2 236B 21B 160 6 Fine-grained, MLA
DeepSeek-V3 671B 37B 256 8 Aux-loss-free, MLA
Qwen-MoE ? ? 64 4 Open
Nemotron-4-340B 340B ? ? ? NVIDIA
GPT-4 ~1.8T ~220B 16? 2? Rumored
GLaM 1.2T 97B 64 2 Google


Sources: "GShard" (Lepikhin et al.), "Switch Transformers" (Fedus et al.), "Mixtral" (Mistral AI), "DeepSeek-V2/V3" (DeepSeek), "GLaM" (Google), "MoE" surveys, "Expert Choice Routing", "Soft MoE", "Scaling MoE" papers, vLLM/SGLang MoE support.

From the Agentic Engineering research vault · CC BY 4.0 · all notes · raw markdown