Expert Networks › Code Implementation
Language AI HandbookAll chapters Expert Networks › Code ImplementationOne section · Sections in this chapter8 / 12 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Let's implement a simple expert network structure and explore capacity-constrained processing.
import torch.nn as nn
import torch.nn.functional as F
# Expert as a standard FFN with SiLU activation
class Expert(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff, bias=True)
self.w2 = nn.Linear(d_ff, d_model, bias=True)
def forward(self, x):
return self.w2(F.silu(self.w1(x)))
# Create experts with shared configuration
d_model = 256
d_ff = 512
num_experts = 8
experts = nn.ModuleList([Expert(d_model, d_ff) for _ in range(num_experts)])Each expert is an independent FFN with its own weight matrices and . The nn.ModuleList container ensures that PyTorch registers all expert parameters for gradient tracking and optimizer updates. Note that creating experts inside a ModuleList is needed: if you used a plain Python list, the expert parameters would not appear in model.parameters() and would not receive gradient updates during training.
Let's examine the parameter counts to verify our earlier analysis:
def count_parameters(module):
return sum(p.numel() for p in module.parameters())
single_expert_params = count_parameters(experts[0])
total_expert_params = count_parameters(experts)Parameters per expert: 262,912 Total expert parameters: 2,103,296 Expansion factor: 8x over single FFN
The total parameter count (approx. 2.1 million) reflects the sum of all 8 experts, but the active parameters per token remain small (approx. 260,000, since only 2 experts activate per token in a typical top-2 routing scheme). This is the MoE tradeoff made concrete: 8 times the stored parameters, but only 2 times the per-token compute.
Now let's implement capacity-constrained expert processing. This simulates how tokens are assigned to experts with overflow handling:
def process_with_capacity(
tokens, # (batch_size * seq_len, d_model)
expert_assignments, # (num_tokens,) - which expert each token goes to
experts, # list of expert modules
capacity_factor=1.25,
):
"""
Process tokens through experts with capacity constraints.
Returns processed tokens and drop statistics.
"""
num_tokens = tokens.shape[0]
num_experts = len(experts)
d_model = tokens.shape[1]
# Calculate capacity per expert
base_capacity = num_tokens // num_experts
capacity = int(capacity_factor * base_capacity)
# Track assignments and outputs
output = torch.zeros_like(tokens)
expert_counts = torch.zeros(num_experts, dtype=torch.long)
dropped_counts = torch.zeros(num_experts, dtype=torch.long)
# Process each token
for i in range(num_tokens):
expert_idx = expert_assignments[i].item()
if expert_counts[expert_idx] < capacity:
# Expert has capacity: process the token
with torch.no_grad(): # For demonstration only
output[i] = experts[expert_idx](tokens[i : i + 1]).squeeze(0)
expert_counts[expert_idx] += 1
else:
# Expert at capacity: token is dropped (uses residual)
output[i] = tokens[i] # Pass through unchanged
dropped_counts[expert_idx] += 1
return output, expert_counts, dropped_countsThis implementation processes tokens sequentially to clearly show the capacity-checking logic. In a production implementation, you would batch all tokens for each expert together and process them simultaneously, but the sequential version makes the capacity constraint logic transparent. Notice that dropped tokens receive tokens[i] as their output: this simulates the residual connection carrying the token representation unchanged past the MoE layer.
Let's simulate imbalanced routing to see capacity constraints in action:
import torch
import torch.nn as nn
import torch.nn.functional as F
# Recreate experts for this cell (needed since earlier cell has output: false)
d_model = 256
d_ff = 512
num_experts = 8
class Expert(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.w1 = nn.Linear(d_model, d_ff, bias=True)
self.w2 = nn.Linear(d_ff, d_model, bias=True)
def forward(self, x):
return self.w2(F.silu(self.w1(x)))
experts = nn.ModuleList([Expert(d_model, d_ff) for _ in range(num_experts)])
# Generate random tokens
torch.manual_seed(42)
batch_tokens = 256
tokens = torch.randn(batch_tokens, d_model)
# Create imbalanced routing: some experts more "popular"
# Expert 0 and 1 get most tokens
probs = torch.tensor([0.3, 0.25, 0.1, 0.1, 0.08, 0.07, 0.05, 0.05])
expert_assignments = torch.multinomial(probs, batch_tokens, replacement=True)
# Define capacity factor
capacity_factor = 1.25
# Process with capacity constraint
output, counts, dropped = process_with_capacity(
tokens, expert_assignments, experts, capacity_factor=capacity_factor
)
total_dropped = dropped.sum().item()
drop_rate = 100 * total_dropped / batch_tokens
# Calculate per-expert statistics
expert_stats = []
for i in range(num_experts):
assigned = (expert_assignments == i).sum().item()
processed = counts[i].item()
dropped_count = dropped[i].item()
expert_stats.append((i, assigned, processed, dropped_count))The routing probabilities here are intentionally skewed: experts 0 and 1 receive over half of all tokens, while experts 6 and 7 receive only 5% each. This kind of imbalanced routing is what the capacity mechanism must handle without letting the popular experts to become bottlenecks.
Expert utilization with capacity_factor=1.25: Base capacity: 32 tokens per expert Actual capacity: 40 tokens per expert Expert 0: 82 assigned, 40 processed, 42 dropped Expert 1: 65 assigned, 40 processed, 25 dropped Expert 2: 28 assigned, 28 processed, 0 dropped Expert 3: 22 assigned, 22 processed, 0 dropped Expert 4: 22 assigned, 22 processed, 0 dropped Expert 5: 15 assigned, 15 processed, 0 dropped Expert 6: 10 assigned, 10 processed, 0 dropped Expert 7: 12 assigned, 12 processed, 0 dropped Total drop rate: 26.2%

The imbalanced routing causes popular experts to drop tokens. Let's visualize how different capacity factors affect drop rates:
capacity_factors = [1.0, 1.1, 1.25, 1.5, 1.75, 2.0]
drop_rates = []
for cf in capacity_factors:
_, _, dropped = process_with_capacity(
tokens, expert_assignments, experts, cf
)
drop_rate = 100 * dropped.sum().item() / batch_tokens
drop_rates.append(drop_rate)
The plot demonstrates that increasing the capacity factor reduces the drop rate. In this simulation, the heavy skew in routing (30% of tokens going to a single expert) means some dropping persists even at . This is an important lesson: capacity factors are a management tool, not a cure. If the routing mechanism produces severely imbalanced assignments, capacity alone cannot compensate. You need both appropriate capacity and good routing, which is exactly why load-balancing auxiliary losses (covered in upcoming chapters) are so important for making MoE training work.
The diversity across experts also becomes apparent when we process the same token through all 8 experts. In an untrained model, the outputs differ simply because the weight matrices are randomly initialized. After training, this diversity becomes meaningful: each expert has learned to produce outputs optimized for its assigned token distribution.
import torch
# Simulated hidden representations for different token types
# (In practice, these come from earlier transformer layers)
code_token = torch.randn(1, d_model) # Represents "def"
english_token = torch.randn(1, d_model) # Represents "the"
math_token = torch.randn(1, d_model) # Represents a math symbol
# Each expert produces different outputs
# due to their independent weights
with torch.no_grad():
code_outputs = [experts[i](code_token) for i in range(num_experts)]
english_outputs = [experts[i](english_token) for i in range(num_experts)]
# Measure output diversity across experts
code_var = torch.stack(code_outputs).var(dim=0).mean().item()
english_var = torch.stack(english_outputs).var(dim=0).mean().item()Code token output variance across experts: 0.0396 English token output variance across experts: 0.0416
Each expert transforms the same input differently. After training, a well-functioning MoE learns to route tokens to experts whose transformations are most appropriate for that token type. The router, which we will explore in the next chapter, learns to make these assignments based on the input representation.
