Kernels:
Trusted publisher
Download tests/test_triton_moe.py from kernels-community/triton-moe: direct link, hf CLI and curl.
- Browser
- Download file 35.4 kB
-
https://huggingface.co/kernels/kernels-community/triton-moe/resolve/main/tests/test_triton_moe.py
- Command line
-
hf download hf://kernels-community/triton-moe/tests/test_triton_moe.py
-
curl -L -o test_triton_moe.py https://huggingface.co/kernels/kernels-community/triton-moe/resolve/main/tests/test_triton_moe.py
35.4 kB
| import torch | |
| import torch.nn.functional as F | |
| import torch.nn as nn | |
| import time | |
| from triton_moe.layers import MoE | |
| # Copied from transformers.models.openai.modeling_openai | |
| class OpenaiExperts(nn.Module): | |
| def __init__(self, config): | |
| super().__init__() | |
| self.num_experts = config.num_local_experts | |
| self.intermediate_size = config.intermediate_size | |
| self.hidden_size = config.hidden_size | |
| self.expert_dim = self.intermediate_size | |
| self.gate_up_proj = nn.Parameter( | |
| torch.empty(self.num_experts, self.hidden_size, 2 * self.expert_dim) | |
| ) | |
| self.gate_up_proj_bias = nn.Parameter( | |
| torch.empty(self.num_experts, 2 * self.expert_dim) | |
| ) | |
| self.down_proj = nn.Parameter( | |
| torch.empty((self.num_experts, self.expert_dim, self.hidden_size)) | |
| ) | |
| self.down_proj_bias = nn.Parameter( | |
| torch.empty(self.num_experts, self.hidden_size) | |
| ) | |
| self.alpha = 1.702 | |
| def forward( | |
| self, hidden_states: torch.Tensor, router_indices=None, routing_weights=None | |
| ) -> torch.Tensor: | |
| """ | |
| When training is is more efficient to just loop over the experts and compute the output for each expert | |
| as otherwise the memory would explode. | |
| For inference we can sacrifice some memory and compute the output for all experts at once. By repeating the inputs. | |
| Args: | |
| hidden_states (torch.Tensor): (batch_size * token_num, hidden_size) | |
| selected_experts (torch.Tensor): (batch_size * token_num, top_k) | |
| routing_weights (torch.Tensor): (batch_size * token_num, top_k) | |
| Returns: | |
| torch.Tensor | |
| """ | |
| if self.training: | |
| next_states = torch.zeros_like( | |
| hidden_states, dtype=hidden_states.dtype, device=hidden_states.device | |
| ) | |
| with torch.no_grad(): | |
| expert_mask = torch.nn.functional.one_hot( | |
| router_indices, num_classes=self.num_experts | |
| ).permute(2, 1, 0) | |
| expert_hitted = torch.greater( | |
| expert_mask.sum(dim=(-1, -2)), 0 | |
| ).nonzero() | |
| for expert_idx in expert_hitted: | |
| with torch.no_grad(): | |
| idx, top_x = torch.where( | |
| expert_mask[expert_idx][0] | |
| ) # idx: top-1/top-2 indicator, top_x: token indices | |
| current_state = hidden_states[top_x] # (num_tokens, hidden_dim) | |
| gate_up = ( | |
| current_state @ self.gate_up_proj[expert_idx] | |
| + self.gate_up_proj_bias[expert_idx] | |
| ) # (num_tokens, 2 * interm_dim) | |
| gate, up = gate_up.chunk(2, dim=-1) # (num_tokens, interm_dim) | |
| glu = gate * torch.sigmoid( | |
| gate * self.alpha | |
| ) # (num_tokens, interm_dim) | |
| gated_output = (up + 1) * glu # (num_tokens, interm_dim) | |
| out = ( | |
| gated_output @ self.down_proj[expert_idx] | |
| + self.down_proj_bias[expert_idx] | |
| ) # (num_tokens, hidden_dim) | |
| weighted_output = ( | |
| out * routing_weights[top_x, idx, None] | |
| ) # (num_tokens, hidden_dim) | |
| next_states.index_add_( | |
| 0, top_x, weighted_output.to(hidden_states.dtype)[0] | |
| ) | |
| else: | |
| hidden_states = hidden_states.repeat(self.num_experts, 1) | |
| hidden_states = hidden_states.view(self.num_experts, -1, self.hidden_size) | |
| gate_up = ( | |
| torch.bmm(hidden_states, self.gate_up_proj) | |
| + self.gate_up_proj_bias[..., None, :] | |
| ) | |
| gate, up = gate_up.chunk(2, dim=-1) # not supported for DTensors | |
| glu = gate * torch.sigmoid(gate * self.alpha) | |
| next_states = ( | |
| torch.bmm(((up + 1) * glu), self.down_proj) | |
| + self.down_proj_bias[..., None, :] | |
| ) | |
| next_states = next_states.view(-1, self.hidden_size) | |
| return next_states | |
| def test_moe_forward(): | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| requires_grad=True, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) # Normalize | |
| # Initialize parameters | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| ref_layer.gate_up_proj = gate_up_proj | |
| ref_layer.gate_up_proj_bias = gate_up_proj_bias | |
| ref_layer.down_proj = down_proj | |
| ref_layer.down_proj_bias = down_proj_bias | |
| with torch.no_grad(): | |
| old_output = ref_layer(hidden_states, router_idx, router_wt) | |
| output = layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| assert old_output.shape == output.shape, "Output shapes do not match" | |
| diff = (old_output - output).abs() | |
| avg_diff = diff.mean() | |
| print(f"Average difference: {avg_diff.item()}") | |
| # Average difference: 0.009219333529472351 | |
| print(f"Max difference: {diff.max().item()}") | |
| # Max difference: 0.09375 | |
| # TODO: Improve the precision | |
| assert torch.allclose( | |
| # old_output, output, rtol=1e-3, atol=1e-3 | |
| old_output, | |
| output, | |
| rtol=1e-1, | |
| atol=1e-1, | |
| ), "Outputs do not match between the two implementations" | |
| def test_moe_backward_grad(): | |
| """Simple backward test comparing gradients between MoE and OpenaiExperts.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| requires_grad=True, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) # Normalize | |
| # Initialize parameters (shared between both implementations) | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| output = layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| # Backward pass | |
| loss = output.sum() | |
| loss.backward() | |
| # Check that gradients exist | |
| print(f"gate_up_proj.grad exists: {gate_up_proj.grad is not None}") | |
| print(f"gate_up_proj_bias.grad exists: {gate_up_proj_bias.grad is not None}") | |
| print(f"down_proj.grad exists: {down_proj.grad is not None}") | |
| print(f"down_proj_bias.grad exists: {down_proj_bias.grad is not None}") | |
| # Simple check: if gradients exist, the backward pass worked | |
| assert ( | |
| gate_up_proj.grad is not None | |
| ), "gate_up_proj gradient is None - custom MoE not using this parameter" | |
| assert ( | |
| gate_up_proj_bias.grad is not None | |
| ), "gate_up_proj_bias gradient is None - custom MoE not using this parameter" | |
| assert down_proj.grad is not None, "down_proj gradient is None" | |
| assert down_proj_bias.grad is not None, "down_proj_bias gradient is None" | |
| def test_moe_backward(): | |
| """Simple backward test comparing gradients between MoE and OpenaiExperts.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| requires_grad=True, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) # Normalize | |
| # Initialize parameters (shared between both implementations) | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| # Set reference layer parameters and clone to avoid mutating the original parameters | |
| ref_layer.gate_up_proj = nn.Parameter( | |
| gate_up_proj.clone().detach().requires_grad_(True) | |
| ) | |
| ref_layer.gate_up_proj_bias = nn.Parameter( | |
| gate_up_proj_bias.clone().detach().requires_grad_(True) | |
| ) | |
| ref_layer.down_proj = nn.Parameter(down_proj.clone().detach().requires_grad_(True)) | |
| ref_layer.down_proj_bias = nn.Parameter( | |
| down_proj_bias.clone().detach().requires_grad_(True) | |
| ) | |
| # Forward pass | |
| ref_output = ref_layer(hidden_states, router_idx, router_wt) | |
| output = layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| # Backward pass | |
| ref_loss = ref_output.sum() | |
| loss = output.sum() | |
| ref_loss.backward(retain_graph=True) | |
| loss.backward() | |
| # Check that gradients exist | |
| print(f"gate_up_proj.grad exists: {gate_up_proj.grad is not None}") | |
| print(f"gate_up_proj_bias.grad exists: {gate_up_proj_bias.grad is not None}") | |
| print(f"down_proj.grad exists: {down_proj.grad is not None}") | |
| print(f"down_proj_bias.grad exists: {down_proj_bias.grad is not None}") | |
| print(f"hidden_states.grad exists: {hidden_states.grad is not None}") | |
| # Simple check: if gradients exist, the backward pass worked | |
| assert ( | |
| gate_up_proj.grad is not None | |
| ), "gate_up_proj gradient is None - custom MoE not using this parameter" | |
| assert ( | |
| gate_up_proj_bias.grad is not None | |
| ), "gate_up_proj_bias gradient is None - custom MoE not using this parameter" | |
| assert down_proj.grad is not None, "down_proj gradient is None" | |
| assert down_proj_bias.grad is not None, "down_proj_bias gradient is None" | |
| assert hidden_states.grad is not None, "hidden_states gradient is None" | |
| print("✓ Backward test passed - all parameters have gradients") | |
| # Sanity check on gate up gradients | |
| print("10 elements from gate_up_proj gradients:") | |
| print(gate_up_proj.grad.flatten()[:10]) | |
| print("10 elements from ref_layer.gate_up_proj gradients:") | |
| print(ref_layer.gate_up_proj.grad.flatten()[:10]) | |
| # Compare the values and ensure they are close enough | |
| assert torch.allclose( | |
| ref_layer.gate_up_proj.grad, | |
| gate_up_proj.grad, | |
| rtol=1e-1, | |
| atol=1e-1, | |
| ), "gate_up_proj gradients do not match between implementations" | |
| assert torch.allclose( | |
| ref_layer.gate_up_proj_bias.grad, | |
| gate_up_proj_bias.grad, | |
| rtol=1e-1, | |
| atol=1e-1, | |
| ), "gate_up_proj_bias gradients do not match between implementations" | |
| assert torch.allclose( | |
| ref_layer.down_proj.grad, | |
| down_proj.grad, | |
| rtol=1e-1, | |
| atol=1e-1, | |
| ), "down_proj gradients do not match between implementations" | |
| assert torch.allclose( | |
| ref_layer.down_proj_bias.grad, | |
| down_proj_bias.grad, | |
| rtol=1e-1, | |
| atol=1e-1, | |
| ), "down_proj_bias gradients do not match between implementations" | |
| def test_moe_backward_benchmark(): | |
| """Benchmark backward pass performance between MoE and OpenaiExperts.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| num_warmup = 5 | |
| num_runs = 20 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| requires_grad=True, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) # Normalize | |
| # Initialize parameters | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| # Set reference layer parameters | |
| ref_layer.gate_up_proj = gate_up_proj | |
| ref_layer.gate_up_proj_bias = gate_up_proj_bias | |
| ref_layer.down_proj = down_proj | |
| ref_layer.down_proj_bias = down_proj_bias | |
| def benchmark_ref_backward(): | |
| """Benchmark reference implementation backward pass.""" | |
| hidden_states_copy = hidden_states.clone().detach().requires_grad_(True) | |
| # Clear gradients | |
| for param in [gate_up_proj, gate_up_proj_bias, down_proj, down_proj_bias]: | |
| if param.grad is not None: | |
| param.grad.zero_() | |
| # Forward + Backward | |
| output = ref_layer(hidden_states_copy, router_idx, router_wt) | |
| loss = output.sum() | |
| loss.backward() | |
| return loss.item() | |
| def benchmark_custom_backward(): | |
| """Benchmark custom implementation backward pass.""" | |
| hidden_states_copy = hidden_states.clone().detach().requires_grad_(True) | |
| # Clear gradients | |
| for param in [gate_up_proj, gate_up_proj_bias, down_proj, down_proj_bias]: | |
| if param.grad is not None: | |
| param.grad.zero_() | |
| # Forward + Backward | |
| output = layer( | |
| hidden_states_copy, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| loss = output.sum() | |
| loss.backward() | |
| return loss.item() | |
| # Warmup | |
| print("Warming up...") | |
| for _ in range(num_warmup): | |
| benchmark_ref_backward() | |
| benchmark_custom_backward() | |
| # Clear cache and synchronize | |
| torch.cuda.empty_cache() | |
| torch.cuda.synchronize() | |
| # Benchmark reference implementation | |
| print(f"Benchmarking reference implementation ({num_runs} runs)...") | |
| ref_times = [] | |
| for i in range(num_runs): | |
| torch.cuda.synchronize() | |
| start_time = time.perf_counter() | |
| benchmark_ref_backward() | |
| torch.cuda.synchronize() | |
| end_time = time.perf_counter() | |
| ref_times.append((end_time - start_time) * 1000) # Convert to ms | |
| if (i + 1) % 5 == 0: | |
| print(f" Completed {i + 1}/{num_runs} runs") | |
| # Clear cache and synchronize | |
| torch.cuda.empty_cache() | |
| torch.cuda.synchronize() | |
| # Benchmark custom implementation | |
| print(f"Benchmarking custom implementation ({num_runs} runs)...") | |
| custom_times = [] | |
| for i in range(num_runs): | |
| torch.cuda.synchronize() | |
| start_time = time.perf_counter() | |
| benchmark_custom_backward() | |
| torch.cuda.synchronize() | |
| end_time = time.perf_counter() | |
| custom_times.append((end_time - start_time) * 1000) # Convert to ms | |
| if (i + 1) % 5 == 0: | |
| print(f" Completed {i + 1}/{num_runs} runs") | |
| # Calculate statistics | |
| ref_mean = sum(ref_times) / len(ref_times) | |
| ref_std = (sum((t - ref_mean) ** 2 for t in ref_times) / len(ref_times)) ** 0.5 | |
| ref_min = min(ref_times) | |
| ref_max = max(ref_times) | |
| custom_mean = sum(custom_times) / len(custom_times) | |
| custom_std = ( | |
| sum((t - custom_mean) ** 2 for t in custom_times) / len(custom_times) | |
| ) ** 0.5 | |
| custom_min = min(custom_times) | |
| custom_max = max(custom_times) | |
| speedup = ref_mean / custom_mean | |
| # Print results | |
| print("\n" + "=" * 80) | |
| print("BACKWARD PASS BENCHMARK RESULTS") | |
| print("=" * 80) | |
| print(f"Configuration:") | |
| print(f" - Experts: {num_experts}") | |
| print(f" - Hidden size: {hidden_size}") | |
| print(f" - Expert dim: {expert_dim}") | |
| print(f" - Batch tokens: {batch_tokens}") | |
| print(f" - Top-k: {topk}") | |
| print(f" - Runs: {num_runs}") | |
| print() | |
| print(f"Reference Implementation (OpenaiExperts):") | |
| print(f" - Mean: {ref_mean:.3f} ms") | |
| print(f" - Std: {ref_std:.3f} ms") | |
| print(f" - Min: {ref_min:.3f} ms") | |
| print(f" - Max: {ref_max:.3f} ms") | |
| print() | |
| print(f"Custom Implementation (MoE):") | |
| print(f" - Mean: {custom_mean:.3f} ms") | |
| print(f" - Std: {custom_std:.3f} ms") | |
| print(f" - Min: {custom_min:.3f} ms") | |
| print(f" - Max: {custom_max:.3f} ms") | |
| print() | |
| print(f"Speedup: {speedup:.2f}x") | |
| if speedup > 1.0: | |
| print(f"✓ Custom implementation is {speedup:.2f}x faster") | |
| else: | |
| print(f"✗ Custom implementation is {1/speedup:.2f}x slower") | |
| print("=" * 80) | |
| # Optional: Save detailed timing data | |
| print(f"\nDetailed timings (ms):") | |
| print(f"Reference: {ref_times}") | |
| print(f"Custom: {custom_times}") | |
| def test_moe_backward_benchmark_memory(): | |
| """Benchmark memory usage during backward pass.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| requires_grad=True, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) | |
| # Initialize parameters | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| # Set reference layer parameters | |
| ref_layer.gate_up_proj = gate_up_proj | |
| ref_layer.gate_up_proj_bias = gate_up_proj_bias | |
| ref_layer.down_proj = down_proj | |
| ref_layer.down_proj_bias = down_proj_bias | |
| # Measure memory for reference implementation | |
| torch.cuda.empty_cache() | |
| torch.cuda.reset_peak_memory_stats() | |
| hidden_states_copy = hidden_states.clone().detach().requires_grad_(True) | |
| output = ref_layer(hidden_states_copy, router_idx, router_wt) | |
| loss = output.sum() | |
| loss.backward() | |
| ref_memory = torch.cuda.max_memory_allocated() / 1024**3 # GB | |
| # Measure memory for custom implementation | |
| torch.cuda.empty_cache() | |
| torch.cuda.reset_peak_memory_stats() | |
| hidden_states_copy = hidden_states.clone().detach().requires_grad_(True) | |
| output = layer( | |
| hidden_states_copy, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| loss = output.sum() | |
| loss.backward() | |
| custom_memory = torch.cuda.max_memory_allocated() / 1024**3 # GB | |
| print("\n" + "=" * 60) | |
| print("MEMORY USAGE BENCHMARK") | |
| print("=" * 60) | |
| print(f"Reference implementation: {ref_memory:.3f} GB") | |
| print(f"Custom implementation: {custom_memory:.3f} GB") | |
| print(f"Memory ratio: {custom_memory/ref_memory:.3f}x") | |
| if custom_memory < ref_memory: | |
| print(f"✓ Custom uses {(1 - custom_memory/ref_memory)*100:.1f}% less memory") | |
| else: | |
| print(f"✗ Custom uses {(custom_memory/ref_memory - 1)*100:.1f}% more memory") | |
| print("=" * 60) | |
| ################# | |
| def test_moe_forward_benchmark(): | |
| """Benchmark forward pass performance between MoE and OpenaiExperts.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| num_warmup = 5 | |
| num_runs = 50 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) # Normalize | |
| # Initialize parameters | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| # Set reference layer parameters | |
| ref_layer.gate_up_proj = gate_up_proj | |
| ref_layer.gate_up_proj_bias = gate_up_proj_bias | |
| ref_layer.down_proj = down_proj | |
| ref_layer.down_proj_bias = down_proj_bias | |
| def benchmark_ref_forward(): | |
| """Benchmark reference implementation forward pass.""" | |
| with torch.no_grad(): | |
| output = ref_layer(hidden_states, router_idx, router_wt) | |
| return output | |
| def benchmark_custom_forward(): | |
| """Benchmark custom implementation forward pass.""" | |
| with torch.no_grad(): | |
| output = layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| return output | |
| # Warmup | |
| print("Warming up...") | |
| for _ in range(num_warmup): | |
| benchmark_ref_forward() | |
| benchmark_custom_forward() | |
| # Clear cache and synchronize | |
| torch.cuda.empty_cache() | |
| torch.cuda.synchronize() | |
| # Benchmark reference implementation | |
| print(f"Benchmarking reference implementation ({num_runs} runs)...") | |
| ref_times = [] | |
| for i in range(num_runs): | |
| torch.cuda.synchronize() | |
| start_time = time.perf_counter() | |
| benchmark_ref_forward() | |
| torch.cuda.synchronize() | |
| end_time = time.perf_counter() | |
| ref_times.append((end_time - start_time) * 1000) # Convert to ms | |
| if (i + 1) % 10 == 0: | |
| print(f" Completed {i + 1}/{num_runs} runs") | |
| # Clear cache and synchronize | |
| torch.cuda.empty_cache() | |
| torch.cuda.synchronize() | |
| # Benchmark custom implementation | |
| print(f"Benchmarking custom implementation ({num_runs} runs)...") | |
| custom_times = [] | |
| for i in range(num_runs): | |
| torch.cuda.synchronize() | |
| start_time = time.perf_counter() | |
| benchmark_custom_forward() | |
| torch.cuda.synchronize() | |
| end_time = time.perf_counter() | |
| custom_times.append((end_time - start_time) * 1000) # Convert to ms | |
| if (i + 1) % 10 == 0: | |
| print(f" Completed {i + 1}/{num_runs} runs") | |
| # Calculate statistics | |
| ref_mean = sum(ref_times) / len(ref_times) | |
| ref_std = (sum((t - ref_mean) ** 2 for t in ref_times) / len(ref_times)) ** 0.5 | |
| ref_min = min(ref_times) | |
| ref_max = max(ref_times) | |
| custom_mean = sum(custom_times) / len(custom_times) | |
| custom_std = ( | |
| sum((t - custom_mean) ** 2 for t in custom_times) / len(custom_times) | |
| ) ** 0.5 | |
| custom_min = min(custom_times) | |
| custom_max = max(custom_times) | |
| speedup = ref_mean / custom_mean | |
| # Print results | |
| print("\n" + "=" * 80) | |
| print("FORWARD PASS BENCHMARK RESULTS") | |
| print("=" * 80) | |
| print(f"Configuration:") | |
| print(f" - Experts: {num_experts}") | |
| print(f" - Hidden size: {hidden_size}") | |
| print(f" - Expert dim: {expert_dim}") | |
| print(f" - Batch tokens: {batch_tokens}") | |
| print(f" - Top-k: {topk}") | |
| print(f" - Runs: {num_runs}") | |
| print() | |
| print(f"Reference Implementation (OpenaiExperts):") | |
| print(f" - Mean: {ref_mean:.3f} ms") | |
| print(f" - Std: {ref_std:.3f} ms") | |
| print(f" - Min: {ref_min:.3f} ms") | |
| print(f" - Max: {ref_max:.3f} ms") | |
| print() | |
| print(f"Custom Implementation (MoE):") | |
| print(f" - Mean: {custom_mean:.3f} ms") | |
| print(f" - Std: {custom_std:.3f} ms") | |
| print(f" - Min: {custom_min:.3f} ms") | |
| print(f" - Max: {custom_max:.3f} ms") | |
| print() | |
| print(f"Speedup: {speedup:.2f}x") | |
| if speedup > 1.0: | |
| print(f"✓ Custom implementation is {speedup:.2f}x faster") | |
| else: | |
| print(f"✗ Custom implementation is {1/speedup:.2f}x slower") | |
| print("=" * 80) | |
| # Optional: Save detailed timing data | |
| print(f"\nDetailed timings (ms):") | |
| print(f"Reference: {ref_times}") | |
| print(f"Custom: {custom_times}") | |
| def test_moe_forward_benchmark_memory(): | |
| """Benchmark memory usage during forward pass.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) | |
| # Initialize parameters | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| # Set reference layer parameters | |
| ref_layer.gate_up_proj = gate_up_proj | |
| ref_layer.gate_up_proj_bias = gate_up_proj_bias | |
| ref_layer.down_proj = down_proj | |
| ref_layer.down_proj_bias = down_proj_bias | |
| # Measure memory for reference implementation | |
| torch.cuda.empty_cache() | |
| torch.cuda.reset_peak_memory_stats() | |
| with torch.no_grad(): | |
| output = ref_layer(hidden_states, router_idx, router_wt) | |
| ref_memory = torch.cuda.max_memory_allocated() / 1024**3 # GB | |
| # Measure memory for custom implementation | |
| torch.cuda.empty_cache() | |
| torch.cuda.reset_peak_memory_stats() | |
| with torch.no_grad(): | |
| output = layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| custom_memory = torch.cuda.max_memory_allocated() / 1024**3 # GB | |
| print("\n" + "=" * 60) | |
| print("FORWARD MEMORY USAGE BENCHMARK") | |
| print("=" * 60) | |
| print(f"Reference implementation: {ref_memory:.3f} GB") | |
| print(f"Custom implementation: {custom_memory:.3f} GB") | |
| print(f"Memory ratio: {custom_memory/ref_memory:.3f}x") | |
| if custom_memory < ref_memory: | |
| print(f"✓ Custom uses {(1 - custom_memory/ref_memory)*100:.1f}% less memory") | |
| else: | |
| print(f"✗ Custom uses {(custom_memory/ref_memory - 1)*100:.1f}% more memory") | |
| print("=" * 60) | |
| def test_moe_forward_benchmark_throughput(): | |
| """Benchmark throughput (tokens/second) for forward pass.""" | |
| # Test configuration | |
| num_experts = 128 | |
| hidden_size = 1024 | |
| expert_dim = 512 | |
| batch_tokens = 4096 | |
| topk = 2 | |
| num_runs = 100 | |
| torch.manual_seed(1337) | |
| torch.cuda.manual_seed(1337) | |
| # Generate test data | |
| hidden_states = torch.randn( | |
| batch_tokens, | |
| hidden_size, | |
| device="cuda", | |
| dtype=torch.float32, | |
| ) | |
| router_idx = torch.randint(0, num_experts, (batch_tokens, topk), device="cuda") | |
| router_wt = torch.rand(batch_tokens, topk, device="cuda") | |
| router_wt = router_wt / router_wt.sum(dim=-1, keepdim=True) | |
| # Initialize parameters | |
| gate_up_proj = nn.Parameter( | |
| torch.randn(num_experts, hidden_size, 2 * expert_dim, device="cuda") | |
| ) | |
| gate_up_proj_bias = nn.Parameter( | |
| torch.randn(num_experts, 2 * expert_dim, device="cuda") | |
| ) | |
| down_proj = nn.Parameter( | |
| torch.randn(num_experts, expert_dim, hidden_size, device="cuda") | |
| ) | |
| down_proj_bias = nn.Parameter(torch.randn(num_experts, hidden_size, device="cuda")) | |
| alpha = 1.702 | |
| # Create Layers | |
| layer = MoE() | |
| ref_layer = OpenaiExperts( | |
| config=type( | |
| "Config", | |
| (object,), | |
| { | |
| "num_local_experts": num_experts, | |
| "intermediate_size": expert_dim, | |
| "hidden_size": hidden_size, | |
| }, | |
| ) | |
| ) | |
| # Set reference layer parameters | |
| ref_layer.gate_up_proj = gate_up_proj | |
| ref_layer.gate_up_proj_bias = gate_up_proj_bias | |
| ref_layer.down_proj = down_proj | |
| ref_layer.down_proj_bias = down_proj_bias | |
| # Warmup | |
| print("Warming up for throughput test...") | |
| for _ in range(10): | |
| with torch.no_grad(): | |
| ref_layer(hidden_states, router_idx, router_wt) | |
| layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| # Benchmark reference throughput | |
| torch.cuda.synchronize() | |
| start_time = time.perf_counter() | |
| for _ in range(num_runs): | |
| with torch.no_grad(): | |
| ref_layer(hidden_states, router_idx, router_wt) | |
| torch.cuda.synchronize() | |
| ref_total_time = time.perf_counter() - start_time | |
| # Benchmark custom throughput | |
| torch.cuda.synchronize() | |
| start_time = time.perf_counter() | |
| for _ in range(num_runs): | |
| with torch.no_grad(): | |
| layer( | |
| hidden_states, | |
| router_idx, | |
| router_wt, | |
| alpha, | |
| gate_up_proj, | |
| gate_up_proj_bias, | |
| down_proj, | |
| down_proj_bias, | |
| ) | |
| torch.cuda.synchronize() | |
| custom_total_time = time.perf_counter() - start_time | |
| # Calculate throughput | |
| total_tokens_processed = batch_tokens * num_runs | |
| ref_throughput = total_tokens_processed / ref_total_time | |
| custom_throughput = total_tokens_processed / custom_total_time | |
| print("\n" + "=" * 70) | |
| print("FORWARD THROUGHPUT BENCHMARK") | |
| print("=" * 70) | |
| print( | |
| f"Configuration: {batch_tokens} tokens/batch × {num_runs} runs = {total_tokens_processed:,} tokens" | |
| ) | |
| print() | |
| print(f"Reference Implementation:") | |
| print(f" - Total time: {ref_total_time:.3f} seconds") | |
| print(f" - Throughput: {ref_throughput:,.0f} tokens/second") | |
| print() | |
| print(f"Custom Implementation:") | |
| print(f" - Total time: {custom_total_time:.3f} seconds") | |
| print(f" - Throughput: {custom_throughput:,.0f} tokens/second") | |
| print() | |
| print(f"Throughput improvement: {custom_throughput/ref_throughput:.2f}x") | |
| if custom_throughput > ref_throughput: | |
| print( | |
| f"✓ Custom processes {((custom_throughput/ref_throughput - 1)*100):.1f}% more tokens/second" | |
| ) | |
| else: | |
| print( | |
| f"✗ Custom processes {((1 - custom_throughput/ref_throughput)*100):.1f}% fewer tokens/second" | |
| ) | |
| print("=" * 70) | |