| """ |
| Test Mixture of Experts Implementation |
| """ |
|
|
| import sys |
| sys.path.insert(0, '.') |
|
|
| import torch |
| from angstrom_nano import AngstromNanoConfig |
| from angstrom_nano.model import AngstromNanoSparseMoEBlock |
|
|
|
|
| def test_moe_forward(): |
| print("\n[Testing MoE Forward Pass]") |
| config = AngstromNanoConfig() |
| moe = AngstromNanoSparseMoEBlock(config) |
| |
| batch_size, seq_len = 2, 128 |
| hidden_states = torch.randn(batch_size, seq_len, config.hidden_size) |
| |
| output, router_logits = moe(hidden_states) |
| |
| print(f" Input shape: {hidden_states.shape}") |
| print(f" Output shape: {output.shape}") |
| print(f" Router logits shape: {router_logits.shape}") |
| assert output.shape == hidden_states.shape |
| assert router_logits.shape == (batch_size * seq_len, config.num_local_experts) |
| print(" [PASS]") |
| |
| return output, router_logits |
|
|
|
|
| def test_expert_utilization(): |
| print("\n[Testing Expert Utilization]") |
| config = AngstromNanoConfig() |
| moe = AngstromNanoSparseMoEBlock(config) |
| |
| batch_size, seq_len = 4, 256 |
| hidden_states = torch.randn(batch_size, seq_len, config.hidden_size) |
| |
| _, router_logits = moe(hidden_states) |
| |
| |
| routing_weights = torch.softmax(router_logits, dim=-1) |
| top_experts = torch.topk(routing_weights, config.num_experts_per_tok, dim=-1).indices |
| |
| |
| expert_counts = torch.zeros(config.num_local_experts) |
| for i in range(config.num_local_experts): |
| expert_counts[i] = (top_experts == i).sum().item() |
| |
| total_selections = batch_size * seq_len * config.num_experts_per_tok |
| expert_percentages = expert_counts / total_selections * 100 |
| |
| print(f" Total tokens: {batch_size * seq_len}") |
| print(f" Top-k: {config.num_experts_per_tok}") |
| print(f" Total expert selections: {total_selections}") |
| print(f" Expert usage distribution:") |
| for i, (count, pct) in enumerate(zip(expert_counts, expert_percentages)): |
| print(f" Expert {i}: {int(count):4d} selections ({pct:5.2f}%)") |
| |
| |
| max_usage = expert_percentages.max().item() |
| min_usage = expert_percentages.min().item() |
| print(f" Max usage: {max_usage:.2f}%, Min usage: {min_usage:.2f}%") |
| |
| if max_usage < 25 and min_usage > 5: |
| print(" [PASS] Expert utilization is balanced") |
| else: |
| print(" [WARNING] Expert utilization may be imbalanced") |
| |
| return expert_counts |
|
|
|
|
| def test_load_balancing_loss(): |
| print("\n[Testing Load Balancing Loss]") |
| config = AngstromNanoConfig() |
| moe = AngstromNanoSparseMoEBlock(config) |
| |
| batch_size, seq_len = 2, 128 |
| hidden_states = torch.randn(batch_size, seq_len, config.hidden_size) |
| |
| output, router_logits = moe(hidden_states) |
| |
| |
| routing_weights = torch.softmax(router_logits, dim=-1) |
| |
| |
| avg_routing_prob = routing_weights.mean(dim=0) |
| |
| |
| top_k_indices = torch.topk(routing_weights, config.num_experts_per_tok, dim=-1).indices |
| expert_mask = torch.nn.functional.one_hot(top_k_indices, config.num_local_experts).float() |
| expert_usage = expert_mask.sum(dim=1).mean(dim=0) |
| expert_usage = expert_usage / expert_usage.sum() |
| |
| |
| aux_loss = (avg_routing_prob * expert_usage).sum() * config.num_local_experts |
| |
| print(f" Aux loss: {aux_loss.item():.6f}") |
| print(f" Weighted aux loss: {aux_loss.item() * config.router_aux_loss_coef:.6f}") |
| assert aux_loss.item() > 0 |
| print(" [PASS]") |
| |
| return aux_loss |
|
|
|
|
| def test_gradient_flow(): |
| print("\n[Testing Gradient Flow Through MoE]") |
| config = AngstromNanoConfig() |
| moe = AngstromNanoSparseMoEBlock(config) |
| |
| batch_size, seq_len = 2, 64 |
| hidden_states = torch.randn(batch_size, seq_len, config.hidden_size, requires_grad=True) |
| |
| output, router_logits = moe(hidden_states) |
| |
| |
| loss = output.mean() |
| loss.backward() |
| |
| print(f" Loss: {loss.item():.6f}") |
| print(f" Input gradient exists: {hidden_states.grad is not None}") |
| print(f" Input gradient norm: {hidden_states.grad.norm().item():.6f}") |
| |
| |
| num_experts_with_grad = sum( |
| 1 for expert in moe.experts |
| if expert.gate_proj.weight.grad is not None |
| ) |
| print(f" Experts with gradients: {num_experts_with_grad}/{config.num_local_experts}") |
| |
| assert hidden_states.grad is not None |
| assert num_experts_with_grad > 0 |
| print(" [PASS]") |
|
|
|
|
| def main(): |
| print("=" * 80) |
| print("Testing Mixture of Experts Implementation") |
| print("=" * 80) |
| |
| torch.manual_seed(42) |
| |
| test_moe_forward() |
| test_expert_utilization() |
| test_load_balancing_loss() |
| test_gradient_flow() |
| |
| print("\n" + "=" * 80) |
| print("All MoE tests passed!") |
| print("=" * 80) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|